Feat: Improve recall efficiency (#38)

* Feat: Improve recall efficiency
Feat: add extension search API

* Feat: add summary to extension
This commit is contained in:
Manoj 2025-08-11 16:52:14 +05:30 committed by GitHub
parent 2fc3d20cf1
commit 026e2f2cbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 897 additions and 159 deletions

View File

@ -0,0 +1,139 @@
import { z } from "zod";
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
import { SearchService } from "~/services/search.server";
import { makeModelCall } from "~/lib/model.server";
import { json } from "@remix-run/node";
import type { CoreMessage } from "ai";
export const ExtensionSearchBodyRequest = z.object({
input: z.string().min(1, "Input text is required"),
limit: z.number().optional().default(20),
maxBfsDepth: z.number().optional(),
includeInvalidated: z.boolean().optional(),
entityTypes: z.array(z.string()).optional(),
scoreThreshold: z.number().optional(),
minResults: z.number().optional(),
});
const searchService = new SearchService();
/**
* Generate multiple search queries from user input using LLM
*/
async function generateSearchQueries(userInput: string): Promise<string[]> {
const messages: CoreMessage[] = [
{
role: "system",
content: `You are my personal memory assistant. I'm writing something and need you to help me recall relevant information from my past conversations, notes, and experiences that might be useful for what I'm currently working on.
Based on what I'm typing, think about what information from my memory would be most helpful:
- What have I discussed before that relates to this topic?
- What context, decisions, or insights might I need to remember?
- What related work, people, or concepts should I be aware of?
- What problems or solutions have I encountered that are similar?
- What background information would help me with this task?
Generate 3-5 specific search queries that will help me find the most relevant memories and context for my current work. Think like you're helping me remember things I might have forgotten or overlooked.
Return the JSON array of strings wrapped in <output></output> tags. Each string should be a search query.
Format: <output>["query1", "query2", "query3"]</output>
Example input: "working on the user authentication feature"
Example output: ["user authentication implementation", "login flow discussion", "authentication security concerns", "user session management", "auth token handling"]`,
},
{
role: "user",
content: userInput,
},
];
try {
const response = await makeModelCall(
false,
messages,
() => {}, // onFinish callback
{ temperature: 0.3 }
);
// Extract content from <output> tags and parse JSON
const outputMatch = (response as string).match(/<output>(.*?)<\/output>/s);
if (!outputMatch) {
throw new Error("No output tags found in LLM response");
}
const queries = JSON.parse(outputMatch[1].trim());
// Validate that we got an array of strings
if (!Array.isArray(queries) || !queries.every(q => typeof q === 'string')) {
throw new Error("Invalid response format from LLM");
}
return queries.slice(0, 5); // Limit to max 5 queries
} catch (error) {
console.error("Error generating search queries:", error);
// Fallback: use the original input as a single query
return [userInput];
}
}
/**
* Deduplicate facts and episodes from multiple search results
*/
function deduplicateResults(results: Array<{ episodes: string[]; facts: string[] }>) {
const uniqueFacts = new Set<string>();
const uniqueEpisodes = new Set<string>();
for (const result of results) {
result.facts.forEach(fact => uniqueFacts.add(fact));
result.episodes.forEach(episode => uniqueEpisodes.add(episode));
}
return {
facts: Array.from(uniqueFacts),
episodes: Array.from(uniqueEpisodes),
};
}
const { action, loader } = createActionApiRoute(
{
body: ExtensionSearchBodyRequest,
allowJWT: true,
authorization: {
action: "search",
},
corsStrategy: "all",
},
async ({ body, authentication }) => {
// Generate multiple search queries from user input
const searchQueries = await generateSearchQueries(body.input);
// Execute all search queries in parallel
const searchResults = await Promise.all(
searchQueries.map(query =>
searchService.search(query, authentication.userId, {
limit: Math.ceil(body.limit / searchQueries.length), // Distribute limit across queries
maxBfsDepth: body.maxBfsDepth,
includeInvalidated: body.includeInvalidated,
entityTypes: body.entityTypes,
scoreThreshold: body.scoreThreshold,
minResults: body.minResults,
})
)
);
// Deduplicate and combine results
const combinedResults = deduplicateResults(searchResults);
// Limit final results if they exceed the requested limit
const finalResults = {
facts: combinedResults.facts.slice(0, body.limit),
episodes: combinedResults.episodes.slice(0, body.limit),
queries_used: searchQueries, // Include the generated queries for debugging
};
return json(finalResults);
},
);
export { action, loader };

View File

@ -0,0 +1,216 @@
import { z } from "zod";
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
import { makeModelCall } from "~/lib/model.server";
import { json } from "@remix-run/node";
import type { CoreMessage } from "ai";
import * as cheerio from 'cheerio';
export const ExtensionSummaryBodyRequest = z.object({
html: z.string().min(1, "HTML content is required"),
url: z.string().url("Valid URL is required"),
title: z.string().optional(),
});
export type PageType = "text" | "video";
interface ContentExtractionResult {
pageType: PageType;
title: string;
content: string;
metadata: {
url: string;
wordCount: number;
};
supported: boolean;
}
/**
* Detect if page contains video content
*/
function isVideoPage(url: string, $: cheerio.CheerioAPI): boolean {
const hostname = new URL(url).hostname.toLowerCase();
// Known video platforms
if (hostname.includes('youtube.com') || hostname.includes('youtu.be') ||
hostname.includes('vimeo.com') || hostname.includes('twitch.tv') ||
hostname.includes('tiktok.com')) {
return true;
}
// Generic video content detection
const videoElements = $('video').length;
const videoPlayers = $('.video-player, [class*="video-player"], [data-testid*="video"]').length;
// If there are multiple video indicators, likely a video-focused page
return videoElements > 0 || videoPlayers > 2;
}
/**
* Extract all text content from any webpage
*/
function extractTextContent($: cheerio.CheerioAPI, url: string): ContentExtractionResult {
// Extract title from multiple possible locations
const title = $('title').text() ||
$('meta[property="og:title"]').attr('content') ||
$('meta[name="title"]').attr('content') ||
$('h1').first().text() ||
'Untitled Page';
// Check if this is primarily a video page
const isVideo = isVideoPage(url, $);
const pageType: PageType = isVideo ? "video" : "text";
let content = '';
if (isVideo) {
// For video pages, try to get description/transcript text
content = $('#description, .video-description, .description').text() ||
$('meta[name="description"]').attr('content') ||
$('[class*="transcript"], [class*="caption"]').text() ||
'Video content detected - text summarization not available';
} else {
// Simple universal text extraction
// Remove non-content elements
$('script, style, noscript, nav, header, footer').remove();
// Get all text content
const allText = $('body').text();
// Split into sentences and filter for meaningful content
const sentences = allText
.split(/[.!?]+/)
.map(s => s.trim())
.filter(s => s.length > 20) // Keep sentences with substance
.filter(s => !/^(click|menu|button|nav|home|search|login|signup|subscribe)$/i.test(s.toLowerCase())) // Remove UI text
.filter(s => s.split(' ').length > 3); // Keep sentences with multiple words
content = sentences.join('. ').slice(0, 10000);
}
// Clean up whitespace and normalize text
content = content.replace(/\s+/g, ' ').trim();
const wordCount = content.split(/\s+/).filter(word => word.length > 0).length;
const supported = !isVideo && content.length > 50;
return {
pageType,
title: title.trim(),
content: content.slice(0, 10000), // Limit content size for processing
metadata: {
url,
wordCount,
},
supported,
};
}
/**
* Generate summary using LLM
*/
async function generateSummary(title: string, content: string): Promise<string> {
const messages: CoreMessage[] = [
{
role: "system",
content: `You are a helpful assistant that creates concise summaries of web content.
Create a clear, informative summary that captures the key points and main ideas from the provided content. The summary should:
- Focus on the most important information and key takeaways
- Be concise but comprehensive
- Maintain the original context and meaning
- Be useful for someone who wants to quickly understand the content
Extract the essential information while preserving important details, facts, or insights.`,
},
{
role: "user",
content: `Title: ${title}
Content: ${content}
Please provide a concise summary of this content.`,
},
];
try {
const response = await makeModelCall(
false,
messages,
() => {}, // onFinish callback
{ temperature: 0.3 }
);
return response as string;
} catch (error) {
console.error("Error generating summary:", error);
return "Unable to generate summary at this time.";
}
}
const { action, loader } = createActionApiRoute(
{
body: ExtensionSummaryBodyRequest,
allowJWT: true,
authorization: {
action: "search",
},
corsStrategy: "all",
},
async ({ body }) => {
try {
const $ = cheerio.load(body.html);
// Extract content from any webpage
const extraction = extractTextContent($, body.url);
// Override title if provided
if (body.title) {
extraction.title = body.title;
}
let summary = '';
if (extraction.supported && extraction.content.length > 0) {
// Generate summary for text content
summary = await generateSummary(extraction.title, extraction.content);
} else {
// Handle unsupported content types
if (extraction.pageType === "video") {
summary = "Video content detected. Text summarization not available for video-focused pages.";
} else {
summary = "Unable to extract sufficient text content for summarization.";
}
}
const response = {
success: true,
pageType: extraction.pageType,
title: extraction.title,
summary,
content: extraction.content.slice(0, 1000), // Return first 1000 chars of content
supported: extraction.supported,
metadata: extraction.metadata,
};
return json(response);
} catch (error) {
console.error("Error processing extension summary request:", error);
return json({
success: false,
error: "Failed to process page content",
pageType: "text" as PageType,
title: body.title || "Error",
summary: "Unable to process this page content.",
content: "",
supported: false,
metadata: {
url: body.url,
wordCount: 0,
},
}, { status: 500 });
}
},
);
export { action, loader };

View File

@ -205,24 +205,24 @@ export async function updateStatementsWithNewEntity(
const queries = [
// Update statements where old entity is the subject
`
MATCH (oldEntity:Entity {uuid: $oldEntityUUID})-[:SUBJECT]->(statement:Statement)
MATCH (oldEntity:Entity {uuid: $oldEntityUUID})-[r:SUBJECT]->(statement:Statement)
MATCH (newEntity:Entity {uuid: $newEntityUUID})
DELETE oldEntity-[:SUBJECT]->statement
CREATE newEntity-[:SUBJECT]->statement
DELETE r
CREATE (newEntity)-[:SUBJECT]->(statement)
`,
// Update statements where old entity is the predicate
`
MATCH (oldEntity:Entity {uuid: $oldEntityUUID})-[:PREDICATE]->(statement:Statement)
MATCH (oldEntity:Entity {uuid: $oldEntityUUID})-[r:PREDICATE]->(statement:Statement)
MATCH (newEntity:Entity {uuid: $newEntityUUID})
DELETE oldEntity-[:PREDICATE]->statement
CREATE newEntity-[:PREDICATE]->statement
DELETE r
CREATE (newEntity)-[:PREDICATE]->(statement)
`,
// Update statements where old entity is the object
`
MATCH (oldEntity:Entity {uuid: $oldEntityUUID})-[:OBJECT]->(statement:Statement)
MATCH (oldEntity:Entity {uuid: $oldEntityUUID})-[r:OBJECT]->(statement:Statement)
MATCH (newEntity:Entity {uuid: $newEntityUUID})
DELETE oldEntity-[:OBJECT]->statement
CREATE newEntity-[:OBJECT]->statement
DELETE r
CREATE (newEntity)-[:OBJECT]->(statement)
`,
];

View File

@ -1,6 +1,11 @@
import type { EpisodicNode, StatementNode } from "@core/types";
import { logger } from "./logger.service";
import { applyCrossEncoderReranking, applyWeightedRRF } from "./search/rerank";
import {
applyCrossEncoderReranking,
applyMultiFactorReranking,
applyMultiFactorMMRReranking,
applyWeightedRRF,
} from "./search/rerank";
import {
getEpisodesByStatements,
performBfsSearch,
@ -118,6 +123,12 @@ export class SearchService {
score = (result as any).crossEncoderScore;
} else if ((result as any).finalScore !== undefined) {
score = (result as any).finalScore;
} else if ((result as any).multifactorScore !== undefined) {
score = (result as any).multifactorScore;
} else if ((result as any).combinedScore !== undefined) {
score = (result as any).combinedScore;
} else if ((result as any).mmrScore !== undefined) {
score = (result as any).mmrScore;
}
return { result, score };
@ -141,19 +152,27 @@ export class SearchService {
let threshold = 0;
if (isRRF || scoreRange < 0.01) {
// For RRF or other compressed score ranges, use a percentile-based approach
// Keep top 70% (or whatever is specified in options) of results
const keepPercentage = 1 - (options.scoreThreshold || 0.3);
const keepCount = Math.max(
1,
Math.ceil(scoredResults.length * keepPercentage),
);
// For RRF scores, use a more lenient adaptive approach
// Calculate median score and use a dynamic threshold based on score distribution
const sortedScores = [...scores].sort((a, b) => b - a);
const medianIndex = Math.floor(sortedScores.length / 2);
const medianScore = sortedScores[medianIndex];
// Set threshold to the score of the last item we want to keep
threshold =
keepCount < scoredResults.length
? scoredResults[keepCount - 1].score
: 0;
// Use the smaller of: 20% of max score or 50% of median score
// This is more lenient for broad queries while still filtering noise
const maxBasedThreshold = maxScore * 0.2;
const medianBasedThreshold = medianScore * 0.5;
threshold = Math.min(maxBasedThreshold, medianBasedThreshold);
// Ensure we keep at least minResults if available
const minResultsCount = Math.min(
options.minResults,
scoredResults.length,
);
if (scoredResults.length >= minResultsCount) {
const minResultsThreshold = scoredResults[minResultsCount - 1].score;
threshold = Math.min(threshold, minResultsThreshold);
}
} else {
// For normal score distributions, use the relative threshold approach
const relativeThreshold = options.scoreThreshold || 0.3;
@ -216,8 +235,11 @@ export class SearchService {
return applyCrossEncoderReranking(query, results);
}
// Otherwise use weighted RRF for multiple sources
return applyWeightedRRF(results);
// Otherwise use combined MultiFactorReranking + MMR for multiple sources
return applyMultiFactorMMRReranking(results, {
lambda: 0.7, // Balance relevance (0.7) vs diversity (0.3)
maxResults: options.limit > 0 ? options.limit * 2 : 100, // Get more results for filtering
});
}
private async logRecallAsync(

View File

@ -4,6 +4,21 @@ import { type CoreMessage } from "ai";
import { makeModelCall } from "~/lib/model.server";
import { logger } from "../logger.service";
// Utility function to safely convert BigInt values to Number
function safeNumber(value: any): number {
if (typeof value === 'bigint') {
return Number(value);
}
if (typeof value === 'number') {
return value;
}
if (typeof value === 'string') {
const parsed = parseFloat(value);
return isNaN(parsed) ? 0 : parsed;
}
return 0;
}
/**
* Apply Weighted Reciprocal Rank Fusion to combine results
*/
@ -59,6 +74,134 @@ export function applyWeightedRRF(results: {
return sortedResults;
}
/**
* Apply MMR (Maximal Marginal Relevance) reranking to reduce redundancy while maintaining relevance
* MMR balances relevance and diversity to prevent redundant fact statements in results
*/
export function applyMMRReranking(
statements: StatementNode[],
lambda: number = 0.7, // Balance between relevance (1.0) and diversity (0.0)
maxResults: number = 50
): StatementNode[] {
if (statements.length === 0) return [];
// Extract relevance scores and embeddings
const candidates = statements.map((statement) => {
let relevanceScore = 0;
// Use existing scores from MultiFactorReranking or other sources
if ((statement as any).multifactorScore !== undefined) {
relevanceScore = safeNumber((statement as any).multifactorScore);
} else if ((statement as any).rrfScore !== undefined) {
relevanceScore = safeNumber((statement as any).rrfScore);
} else if ((statement as any).crossEncoderScore !== undefined) {
relevanceScore = safeNumber((statement as any).crossEncoderScore);
} else if ((statement as any).finalScore !== undefined) {
relevanceScore = safeNumber((statement as any).finalScore);
}
return {
statement,
relevanceScore,
embedding: statement.factEmbedding || [],
selected: false
};
});
// Sort by relevance score (descending)
candidates.sort((a, b) => b.relevanceScore - a.relevanceScore);
const selectedCandidates: typeof candidates = [];
const remainingCandidates = [...candidates];
// Pre-filter candidates with no embeddings for faster processing
const candidatesWithEmbeddings = remainingCandidates.filter(c => c.embedding.length > 0);
const candidatesWithoutEmbeddings = remainingCandidates.filter(c => c.embedding.length === 0);
// MMR Selection Algorithm with optimizations
while (selectedCandidates.length < maxResults && remainingCandidates.length > 0) {
let bestCandidate = null;
let bestScore = -Infinity;
let bestIndex = -1;
// Early termination: if we have enough high-relevance items, stop diversity checking
const relevanceThreshold = selectedCandidates.length > 0 ?
selectedCandidates[selectedCandidates.length - 1].relevanceScore * 0.5 : 0;
for (let i = 0; i < remainingCandidates.length; i++) {
const candidate = remainingCandidates[i];
// Skip similarity calculation for very low relevance items
if (candidate.relevanceScore < relevanceThreshold && selectedCandidates.length > 3) {
continue;
}
let maxSimilarityToSelected = 0;
// Only calculate similarity if candidate has embedding and we have selected items
if (selectedCandidates.length > 0 && candidate.embedding.length > 0) {
// Optimization: only check similarity with most recent selected items (last 5)
const recentSelected = selectedCandidates.slice(-Math.min(5, selectedCandidates.length));
for (const selected of recentSelected) {
if (selected.embedding.length > 0) {
const similarity = cosineSimilarity(candidate.embedding, selected.embedding);
maxSimilarityToSelected = Math.max(maxSimilarityToSelected, similarity);
// Early exit: if similarity is very high, no need to check more
if (similarity > 0.95) break;
}
}
}
// MMR Score: λ * relevance - (1-λ) * max_similarity_to_selected
const mmrScore = lambda * candidate.relevanceScore - (1 - lambda) * maxSimilarityToSelected;
if (mmrScore > bestScore) {
bestScore = mmrScore;
bestCandidate = candidate;
bestIndex = i;
}
}
if (bestCandidate && bestIndex !== -1) {
selectedCandidates.push(bestCandidate);
remainingCandidates.splice(bestIndex, 1);
} else {
// No more candidates to select
break;
}
}
// Return selected statements with MMR scores
return selectedCandidates.map((item, index) => ({
...item.statement,
mmrScore: item.relevanceScore, // Keep original relevance score
mmrRank: index + 1
}));
}
/**
* Calculate cosine similarity between two vectors
*/
function cosineSimilarity(a: number[], b: number[]): number {
if (a.length !== b.length || a.length === 0) return 0;
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
if (normA === 0 || normB === 0) return 0;
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
/**
* Apply Cross-Encoder reranking to results
* This is particularly useful when results come from a single source
@ -115,3 +258,154 @@ export async function applyCrossEncoderReranking(
return finalStatements;
}
/**
* Apply combined MultiFactorReranking + MMR for optimal relevance and diversity
* First applies MultiFactorReranking for authority/popularity/temporal scoring,
* then applies MMR to reduce redundancy while maintaining relevance
*/
export function applyMultiFactorMMRReranking(results: {
bm25: StatementNode[];
vector: StatementNode[];
bfs: StatementNode[];
}, options?: {
lambda?: number; // MMR balance parameter (default: 0.7)
maxResults?: number; // Maximum results to return (default: 50)
}): StatementNode[] {
const { lambda = 0.7, maxResults = 50 } = options || {};
// Step 1: Apply MultiFactorReranking to get relevance/authority/popularity scores
const multiFactorResults = applyMultiFactorReranking(results);
// Step 2: Apply MMR to reduce redundancy while maintaining relevance
const mmrResults = applyMMRReranking(multiFactorResults, lambda, maxResults);
// Add combined score for debugging
return mmrResults.map((statement) => ({
...statement,
combinedScore: safeNumber((statement as any).mmrScore), // MMR preserves MultiFactorScore
rerankerUsed: 'multifactor+mmr'
}));
}
/**
* Apply Multi-Factor Reranking combining semantic, structural, temporal, and provenance signals
*/
export function applyMultiFactorReranking(results: {
bm25: StatementNode[];
vector: StatementNode[];
bfs: StatementNode[];
}): StatementNode[] {
// Map to store combined scores and metadata
const scores: Record<
string,
{
score: number;
statement: StatementNode;
signals: { bm25: number; vector: number; bfs: number };
}
> = {};
// Extract original scores when available (handle BigInt)
const getOriginalScore = (statement: any) => {
const rawScore = statement.similarity || statement.score || statement.bm25Score || 0;
return safeNumber(rawScore);
};
// Process BM25 results - preserve original BM25 scores
results.bm25.forEach((statement, rank) => {
const uuid = statement.uuid;
const originalScore = getOriginalScore(statement);
const normalizedScore = Math.max(originalScore, 1 / (rank + 1)); // Rank fallback
scores[uuid] = scores[uuid] || {
score: 0,
statement,
signals: { bm25: 0, vector: 0, bfs: 0 },
};
scores[uuid].signals.bm25 = normalizedScore;
});
// Process vector similarity results - preserve semantic scores
results.vector.forEach((statement, rank) => {
const uuid = statement.uuid;
const originalScore = getOriginalScore(statement);
const normalizedScore = Math.max(originalScore, 1 / (rank + 1));
scores[uuid] = scores[uuid] || {
score: 0,
statement,
signals: { bm25: 0, vector: 0, bfs: 0 },
};
scores[uuid].signals.vector = normalizedScore;
});
// Process BFS traversal results - structural relevance
results.bfs.forEach((statement, rank) => {
const uuid = statement.uuid;
const originalScore = getOriginalScore(statement);
const normalizedScore = Math.max(originalScore, 1 / (rank + 1));
scores[uuid] = scores[uuid] || {
score: 0,
statement,
signals: { bm25: 0, vector: 0, bfs: 0 },
};
scores[uuid].signals.bfs = normalizedScore;
});
// Calculate final scores using adaptive weights
Object.values(scores).forEach((item) => {
const { bm25, vector, bfs } = item.signals;
// Adaptive weights based on signal strength
const totalSignals =
(bm25 > 0 ? 1 : 0) + (vector > 0 ? 1 : 0) + (bfs > 0 ? 1 : 0);
// Multi-signal bonus: statements appearing in multiple sources get higher weights
const multiSignalBonus = totalSignals > 1 ? 1.2 : 1.0;
// Dynamic weights: stronger for queries that benefit from each signal type
const weights = {
bm25: bm25 > 0 ? 1.0 : 0, // Keyword matching
vector: vector > 0 ? 0.9 : 0, // Semantic similarity
bfs: bfs > 0 ? 0.6 : 0, // Graph connectivity
};
// Temporal recency bonus (newer statements get slight boost)
const createdAt = new Date(item.statement.createdAt).getTime();
const now = Date.now();
const daysSince = (now - createdAt) / (1000 * 60 * 60 * 24);
const recencyBonus = Math.max(0.9, 1.0 - (daysSince / 365) * 0.1); // Max 10% decay over 1 year
// Popularity bonus based on recall count (log-scaled to prevent dominance)
const recallCount = safeNumber(item.statement.recallCount);
const popularityBonus = 1.0 + (Math.log(1 + recallCount) * 0.15); // Up to ~30% boost for frequently recalled facts
// Provenance authority bonus based on multiple source episodes
const provenanceCount = Math.max(1, safeNumber(item.statement.provenanceCount));
const authorityBonus = 1.0 + (Math.log(provenanceCount) * 0.2); // Up to ~35% boost for multi-source facts
// Final weighted score with all bonuses
item.score =
(weights.bm25 * bm25 + weights.vector * vector + weights.bfs * bfs) *
multiSignalBonus *
recencyBonus *
popularityBonus *
authorityBonus;
});
// Convert to array and sort by final score
const sortedResults = Object.values(scores)
.sort((a, b) => b.score - a.score)
.map((item) => {
// Add the reranking score and signal breakdown for debugging
return {
...item.statement,
multifactorScore: item.score,
signals: item.signals,
};
});
return sortedResults;
}

View File

@ -31,14 +31,16 @@ export async function performBM25Search(
`;
}
// Use Neo4j's built-in fulltext search capabilities
// Use Neo4j's built-in fulltext search capabilities with provenance count
const cypher = `
CALL db.index.fulltext.queryNodes("statement_fact_index", $query)
YIELD node AS s, score
WHERE
(s.userId = $userId)
${timeframeCondition}
RETURN s, score
OPTIONAL MATCH (episode:Episode)-[:HAS_PROVENANCE]->(s)
WITH s, score, count(episode) as provenanceCount
RETURN s, score, provenanceCount
ORDER BY score DESC
`;
@ -50,7 +52,15 @@ export async function performBM25Search(
};
const records = await runQuery(cypher, params);
return records.map((record) => record.get("s").properties as StatementNode);
return records.map((record) => {
const statement = record.get("s").properties as StatementNode;
const provenanceCountValue = record.get("provenanceCount");
statement.provenanceCount =
typeof provenanceCountValue === "bigint"
? Number(provenanceCountValue)
: (provenanceCountValue?.toNumber?.() ?? provenanceCountValue ?? 0);
return statement;
});
} catch (error) {
logger.error("BM25 search error:", { error });
return [];
@ -101,7 +111,7 @@ export async function performVectorSearch(
`;
}
// 1. Search for similar statements using Neo4j vector search
// 1. Search for similar statements using Neo4j vector search with provenance count
const cypher = `
MATCH (s:Statement)
WHERE
@ -109,7 +119,9 @@ export async function performVectorSearch(
${timeframeCondition}
WITH s, vector.similarity.cosine(s.factEmbedding, $embedding) AS score
WHERE score > 0.7
RETURN s, score
OPTIONAL MATCH (episode:Episode)-[:HAS_PROVENANCE]->(s)
WITH s, score, count(episode) as provenanceCount
RETURN s, score, provenanceCount
ORDER BY score DESC
`;
@ -121,7 +133,15 @@ export async function performVectorSearch(
};
const records = await runQuery(cypher, params);
return records.map((record) => record.get("s").properties as StatementNode);
return records.map((record) => {
const statement = record.get("s").properties as StatementNode;
const provenanceCountValue = record.get("provenanceCount");
statement.provenanceCount =
typeof provenanceCountValue === "bigint"
? Number(provenanceCountValue)
: (provenanceCountValue?.toNumber?.() ?? provenanceCountValue ?? 0);
return statement;
});
} catch (error) {
logger.error("Vector search error:", { error });
return [];

View File

@ -79,6 +79,7 @@
"ai": "4.3.14",
"axios": "^1.10.0",
"bullmq": "^5.53.2",
"cheerio": "^1.1.2",
"class-transformer": "0.5.1",
"class-validator": "0.14.1",
"class-variance-authority": "^0.7.1",

View File

@ -54,6 +54,7 @@ export interface StatementNode {
userId: string;
space?: string;
recallCount?: number;
provenanceCount?: number;
}
/**

303
pnpm-lock.yaml generated
View File

@ -475,6 +475,9 @@ importers:
bullmq:
specifier: ^5.53.2
version: 5.53.2
cheerio:
specifier: ^1.1.2
version: 1.1.2
class-transformer:
specifier: 0.5.1
version: 0.5.1
@ -637,7 +640,7 @@ importers:
devDependencies:
'@remix-run/dev':
specifier: 2.16.7
version: 2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(typescript@5.8.3)(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))(yaml@2.8.0)
version: 2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))(yaml@2.8.0)
'@remix-run/eslint-config':
specifier: 2.16.7
version: 2.16.7(eslint@8.57.1)(react@18.3.1)(typescript@5.8.3)
@ -652,7 +655,7 @@ importers:
version: 0.5.16(tailwindcss@4.1.7)
'@tailwindcss/vite':
specifier: ^4.1.7
version: 4.1.9(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))
version: 4.1.9(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))
'@trigger.dev/build':
specifier: ^4.0.0-v4-beta.22
version: 4.0.0-v4-beta.22(typescript@5.8.3)
@ -736,7 +739,7 @@ importers:
version: 3.5.3
prettier-plugin-tailwindcss:
specifier: ^0.6.11
version: 0.6.12(@ianvs/prettier-plugin-sort-imports@4.1.1(@vue/compiler-sfc@3.3.4)(prettier@3.5.3))(prettier@3.5.3)
version: 0.6.12(@ianvs/prettier-plugin-sort-imports@4.1.1(prettier@3.5.3))(prettier@3.5.3)
tailwind-scrollbar:
specifier: ^4.0.2
version: 4.0.2(react@18.3.1)(tailwindcss@4.1.7)
@ -748,10 +751,10 @@ importers:
version: 5.8.3
vite:
specifier: ^6.0.0
version: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
version: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
vite-tsconfig-paths:
specifier: ^4.2.1
version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))
version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))
packages/database:
dependencies:
@ -820,7 +823,7 @@ importers:
version: 20.19.7
tsup:
specifier: ^8.0.1
version: 8.5.0(@swc/core@1.3.101(@swc/helpers@0.5.17))(jiti@2.4.2)(postcss@8.5.5)(tsx@4.17.0)(typescript@5.8.3)(yaml@2.8.0)
version: 8.5.0(@swc/core@1.3.101)(jiti@2.4.2)(postcss@8.5.5)(tsx@4.17.0)(typescript@5.8.3)(yaml@2.8.0)
typescript:
specifier: ^5.0.0
version: 5.8.3
@ -857,7 +860,7 @@ importers:
version: 6.0.1
tsup:
specifier: ^8.0.1
version: 8.5.0(@swc/core@1.3.101(@swc/helpers@0.5.17))(jiti@2.4.2)(postcss@8.5.5)(tsx@4.17.0)(typescript@5.8.3)(yaml@2.8.0)
version: 8.5.0(@swc/core@1.3.101)(jiti@2.4.2)(postcss@8.5.5)(tsx@4.17.0)(typescript@5.8.3)(yaml@2.8.0)
typescript:
specifier: ^5.3.0
version: 5.8.3
@ -5284,24 +5287,6 @@ packages:
'@vanilla-extract/private@1.0.8':
resolution: {integrity: sha512-oRAbUlq1SyTWCo7dQnTVm+xgJMqNl8K1dEempQHXzQvUuyEfBabMt0wNGf+VCHzvKbx/Bzr9p/2wy8WA9+2z2g==}
'@vue/compiler-core@3.3.4':
resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==}
'@vue/compiler-dom@3.3.4':
resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==}
'@vue/compiler-sfc@3.3.4':
resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==}
'@vue/compiler-ssr@3.3.4':
resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==}
'@vue/reactivity-transform@3.3.4':
resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==}
'@vue/shared@3.3.4':
resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
'@web3-storage/multipart-parser@1.0.0':
resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==}
@ -5638,6 +5623,9 @@ packages:
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
engines: {node: '>=18'}
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
bowser@2.11.0:
resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
@ -5773,6 +5761,13 @@ packages:
chardet@0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
cheerio-select@2.1.0:
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
cheerio@1.1.2:
resolution: {integrity: sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==}
engines: {node: '>=20.18.1'}
chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
@ -6087,6 +6082,9 @@ packages:
webpack:
optional: true
css-select@5.2.2:
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
css-styled@1.0.8:
resolution: {integrity: sha512-tCpP7kLRI8dI95rCh3Syl7I+v7PP+2JYOzWkl0bUEoSbJM+u8ITbutjlQVf0NC2/g4ULROJPi16sfwDIO8/84g==}
@ -6542,6 +6540,9 @@ packages:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
encoding-sniffer@0.2.1:
resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==}
encoding@0.1.13:
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
@ -6571,6 +6572,10 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
entities@6.0.1:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@ -6894,9 +6899,6 @@ packages:
estree-util-visit@1.2.1:
resolution: {integrity: sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==}
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
@ -7412,6 +7414,9 @@ packages:
html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
htmlparser2@10.0.0:
resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==}
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
@ -8790,6 +8795,9 @@ packages:
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
engines: {node: '>=18'}
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
num2fraction@1.2.2:
resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==}
@ -9026,6 +9034,15 @@ packages:
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
engines: {node: '>= 0.10'}
parse5-htmlparser2-tree-adapter@7.1.0:
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
parse5-parser-stream@7.1.2:
resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
parseley@0.12.1:
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
@ -10291,6 +10308,7 @@ packages:
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
deprecated: The work that was done in this beta branch won't be included in future versions
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
@ -10908,6 +10926,10 @@ packages:
resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==}
engines: {node: '>=18.17'}
undici@7.13.0:
resolution: {integrity: sha512-l+zSMssRqrzDcb3fjMkjjLGmuiiK2pMIcV++mJaAc9vhjSGpvM7h43QgP+OAMb1GImHmbPyG2tBXeuyG5iY4gA==}
engines: {node: '>=20.18.1'}
unicorn-magic@0.1.0:
resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
engines: {node: '>=18'}
@ -11209,6 +11231,14 @@ packages:
webpack-cli:
optional: true
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
@ -12778,7 +12808,7 @@ snapshots:
dependencies:
'@floating-ui/dom': 1.7.1
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
'@floating-ui/react-dom@2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
@ -12852,7 +12882,7 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {}
'@ianvs/prettier-plugin-sort-imports@4.1.1(@vue/compiler-sfc@3.3.4)(prettier@3.5.3)':
'@ianvs/prettier-plugin-sort-imports@4.1.1(prettier@3.5.3)':
dependencies:
'@babel/core': 7.27.4
'@babel/generator': 7.27.5
@ -12861,8 +12891,6 @@ snapshots:
'@babel/types': 7.27.6
prettier: 3.5.3
semver: 7.7.2
optionalDependencies:
'@vue/compiler-sfc': 3.3.4
transitivePeerDependencies:
- supports-color
optional: true
@ -13441,7 +13469,7 @@ snapshots:
dependencies:
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -13495,7 +13523,7 @@ snapshots:
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.47)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -13523,7 +13551,7 @@ snapshots:
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -13656,7 +13684,7 @@ snapshots:
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.47)(react@18.2.0)
'@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -13721,7 +13749,7 @@ snapshots:
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -13813,7 +13841,7 @@ snapshots:
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.47)(react@18.2.0)
aria-hidden: 1.2.6
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.5.7(@types/react@18.2.47)(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
@ -13855,7 +13883,7 @@ snapshots:
'@radix-ui/react-use-size': 1.1.0(@types/react@18.2.47)(react@18.2.0)
'@radix-ui/rect': 1.1.0
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -13890,7 +13918,7 @@ snapshots:
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -13918,7 +13946,7 @@ snapshots:
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.47)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -13944,7 +13972,7 @@ snapshots:
dependencies:
'@radix-ui/react-slot': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -13970,7 +13998,7 @@ snapshots:
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.47)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -14155,7 +14183,7 @@ snapshots:
'@radix-ui/react-toggle': 1.1.0(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -14166,7 +14194,7 @@ snapshots:
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.47)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -14186,7 +14214,7 @@ snapshots:
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.47)(react@18.2.0)
'@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -14338,7 +14366,7 @@ snapshots:
dependencies:
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.2.47
'@types/react-dom': 18.2.18
@ -14459,7 +14487,7 @@ snapshots:
html-to-text: 9.0.5
js-beautify: 1.15.4
react: 18.3.1
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
react-promise-suspense: 0.3.4
'@react-email/row@0.0.7(react@18.3.1)':
@ -14493,7 +14521,7 @@ snapshots:
transitivePeerDependencies:
- encoding
'@remix-run/dev@2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(typescript@5.8.3)(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))(yaml@2.8.0)':
'@remix-run/dev@2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))(yaml@2.8.0)':
dependencies:
'@babel/core': 7.27.4
'@babel/generator': 7.27.5
@ -14510,7 +14538,7 @@ snapshots:
'@remix-run/router': 1.23.0
'@remix-run/server-runtime': 2.16.7(typescript@5.8.3)
'@types/mdx': 2.0.13
'@vanilla-extract/integration': 6.5.0(@types/node@20.19.7)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)
'@vanilla-extract/integration': 6.5.0(@types/node@22.16.0)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)
arg: 5.0.2
cacache: 17.1.4
chalk: 4.1.2
@ -14550,12 +14578,12 @@ snapshots:
tar-fs: 2.1.3
tsconfig-paths: 4.2.0
valibot: 0.41.0(typescript@5.8.3)
vite-node: 3.2.3(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
vite-node: 3.2.3(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
ws: 7.5.10
optionalDependencies:
'@remix-run/serve': 2.16.7(typescript@5.8.3)
typescript: 5.8.3
vite: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@ -15294,12 +15322,12 @@ snapshots:
postcss-selector-parser: 6.0.10
tailwindcss: 4.1.7
'@tailwindcss/vite@4.1.9(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))':
'@tailwindcss/vite@4.1.9(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0))':
dependencies:
'@tailwindcss/node': 4.1.9
'@tailwindcss/oxide': 4.1.9
tailwindcss: 4.1.9
vite: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
'@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
@ -16268,7 +16296,7 @@ snapshots:
transitivePeerDependencies:
- babel-plugin-macros
'@vanilla-extract/integration@6.5.0(@types/node@20.19.7)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)':
'@vanilla-extract/integration@6.5.0(@types/node@22.16.0)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)':
dependencies:
'@babel/core': 7.27.4
'@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4)
@ -16281,8 +16309,8 @@ snapshots:
lodash: 4.17.21
mlly: 1.7.4
outdent: 0.8.0
vite: 5.4.19(@types/node@20.19.7)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)
vite-node: 1.6.1(@types/node@20.19.7)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)
vite: 5.4.19(@types/node@22.16.0)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)
vite-node: 1.6.1(@types/node@22.16.0)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@ -16297,52 +16325,6 @@ snapshots:
'@vanilla-extract/private@1.0.8': {}
'@vue/compiler-core@3.3.4':
dependencies:
'@babel/parser': 7.27.5
'@vue/shared': 3.3.4
estree-walker: 2.0.2
source-map-js: 1.2.1
optional: true
'@vue/compiler-dom@3.3.4':
dependencies:
'@vue/compiler-core': 3.3.4
'@vue/shared': 3.3.4
optional: true
'@vue/compiler-sfc@3.3.4':
dependencies:
'@babel/parser': 7.27.5
'@vue/compiler-core': 3.3.4
'@vue/compiler-dom': 3.3.4
'@vue/compiler-ssr': 3.3.4
'@vue/reactivity-transform': 3.3.4
'@vue/shared': 3.3.4
estree-walker: 2.0.2
magic-string: 0.30.17
postcss: 8.5.5
source-map-js: 1.2.1
optional: true
'@vue/compiler-ssr@3.3.4':
dependencies:
'@vue/compiler-dom': 3.3.4
'@vue/shared': 3.3.4
optional: true
'@vue/reactivity-transform@3.3.4':
dependencies:
'@babel/parser': 7.27.5
'@vue/compiler-core': 3.3.4
'@vue/shared': 3.3.4
estree-walker: 2.0.2
magic-string: 0.30.17
optional: true
'@vue/shared@3.3.4':
optional: true
'@web3-storage/multipart-parser@1.0.0': {}
'@webassemblyjs/ast@1.14.1':
@ -16750,6 +16732,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
boolbase@1.0.0: {}
bowser@2.11.0: {}
brace-expansion@1.1.12:
@ -16913,6 +16897,29 @@ snapshots:
chardet@0.7.0: {}
cheerio-select@2.1.0:
dependencies:
boolbase: 1.0.0
css-select: 5.2.2
css-what: 6.1.0
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.2.2
cheerio@1.1.2:
dependencies:
cheerio-select: 2.1.0
dom-serializer: 2.0.0
domhandler: 5.0.3
domutils: 3.2.2
encoding-sniffer: 0.2.1
htmlparser2: 10.0.0
parse5: 7.3.0
parse5-htmlparser2-tree-adapter: 7.1.0
parse5-parser-stream: 7.1.2
undici: 7.13.0
whatwg-mimetype: 4.0.0
chokidar@3.5.3:
dependencies:
anymatch: 3.1.3
@ -17232,6 +17239,14 @@ snapshots:
optionalDependencies:
webpack: 5.99.9(esbuild@0.25.5)
css-select@5.2.2:
dependencies:
boolbase: 1.0.0
css-what: 6.1.0
domhandler: 5.0.3
domutils: 3.2.2
nth-check: 2.1.1
css-styled@1.0.8:
dependencies:
'@daybrush/utils': 1.13.0
@ -17672,6 +17687,11 @@ snapshots:
encodeurl@2.0.0: {}
encoding-sniffer@0.2.1:
dependencies:
iconv-lite: 0.6.3
whatwg-encoding: 3.1.1
encoding@0.1.13:
dependencies:
iconv-lite: 0.6.3
@ -17723,6 +17743,8 @@ snapshots:
entities@4.5.0: {}
entities@6.0.1: {}
env-paths@2.2.1: {}
environment@1.1.0: {}
@ -18339,9 +18361,6 @@ snapshots:
'@types/estree-jsx': 1.0.5
'@types/unist': 2.0.11
estree-walker@2.0.2:
optional: true
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.8
@ -18650,7 +18669,7 @@ snapshots:
optionalDependencies:
'@emotion/is-prop-valid': 0.8.8
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
framework-utils@1.1.0: {}
@ -19002,6 +19021,13 @@ snapshots:
html-url-attributes@3.0.1: {}
htmlparser2@10.0.0:
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.2.2
entities: 6.0.1
htmlparser2@8.0.2:
dependencies:
domelementtype: 2.3.0
@ -20491,7 +20517,7 @@ snapshots:
graceful-fs: 4.2.11
postcss: 8.4.31
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
styled-jsx: 5.1.1(@babel/core@7.24.5)(react@18.2.0)
optionalDependencies:
'@next/swc-darwin-arm64': 14.1.4
@ -20650,6 +20676,10 @@ snapshots:
path-key: 4.0.0
unicorn-magic: 0.3.0
nth-check@2.1.1:
dependencies:
boolbase: 1.0.0
num2fraction@1.2.2: {}
nypm@0.5.4:
@ -20898,6 +20928,19 @@ snapshots:
parse-node-version@1.0.1:
optional: true
parse5-htmlparser2-tree-adapter@7.1.0:
dependencies:
domhandler: 5.0.3
parse5: 7.3.0
parse5-parser-stream@7.1.2:
dependencies:
parse5: 7.3.0
parse5@7.3.0:
dependencies:
entities: 6.0.1
parseley@0.12.1:
dependencies:
leac: 0.6.0
@ -21243,11 +21286,11 @@ snapshots:
prelude-ls@1.2.1: {}
prettier-plugin-tailwindcss@0.6.12(@ianvs/prettier-plugin-sort-imports@4.1.1(@vue/compiler-sfc@3.3.4)(prettier@3.5.3))(prettier@3.5.3):
prettier-plugin-tailwindcss@0.6.12(@ianvs/prettier-plugin-sort-imports@4.1.1(prettier@3.5.3))(prettier@3.5.3):
dependencies:
prettier: 3.5.3
optionalDependencies:
'@ianvs/prettier-plugin-sort-imports': 4.1.1(@vue/compiler-sfc@3.3.4)(prettier@3.5.3)
'@ianvs/prettier-plugin-sort-imports': 4.1.1(prettier@3.5.3)
prettier@2.8.8: {}
@ -21528,12 +21571,6 @@ snapshots:
react: 18.2.0
scheduler: 0.23.2
react-dom@18.2.0(react@18.3.1):
dependencies:
loose-envify: 1.4.0
react: 18.3.1
scheduler: 0.23.2
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
@ -21573,7 +21610,7 @@ snapshots:
postcss: 8.4.38
prism-react-renderer: 2.1.0(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
socket.io: 4.7.3
socket.io-client: 4.7.3
sonner: 1.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@ -22376,7 +22413,7 @@ snapshots:
sonner@1.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.3.1)
react-dom: 18.2.0(react@18.2.0)
source-map-js@1.0.2: {}
@ -22903,7 +22940,7 @@ snapshots:
tslib@2.8.1: {}
tsup@8.5.0(@swc/core@1.3.101(@swc/helpers@0.5.17))(jiti@2.4.2)(postcss@8.5.5)(tsx@4.17.0)(typescript@5.8.3)(yaml@2.8.0):
tsup@8.5.0(@swc/core@1.3.101)(jiti@2.4.2)(postcss@8.5.5)(tsx@4.17.0)(typescript@5.8.3)(yaml@2.8.0):
dependencies:
bundle-require: 5.1.0(esbuild@0.25.5)
cac: 6.7.14
@ -23081,6 +23118,8 @@ snapshots:
undici@6.21.3: {}
undici@7.13.0: {}
unicorn-magic@0.1.0: {}
unicorn-magic@0.3.0: {}
@ -23304,13 +23343,13 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.2
vite-node@1.6.1(@types/node@20.19.7)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0):
vite-node@1.6.1(@types/node@22.16.0)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0):
dependencies:
cac: 6.7.14
debug: 4.4.1(supports-color@10.0.0)
pathe: 1.1.2
picocolors: 1.1.1
vite: 5.4.19(@types/node@20.19.7)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)
vite: 5.4.19(@types/node@22.16.0)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)
transitivePeerDependencies:
- '@types/node'
- less
@ -23322,13 +23361,13 @@ snapshots:
- supports-color
- terser
vite-node@3.2.3(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0):
vite-node@3.2.3(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0):
dependencies:
cac: 6.7.14
debug: 4.4.1(supports-color@10.0.0)
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@ -23343,31 +23382,31 @@ snapshots:
- tsx
- yaml
vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)):
vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)):
dependencies:
debug: 4.4.1(supports-color@10.0.0)
globrex: 0.1.2
tsconfck: 3.1.6(typescript@5.8.3)
optionalDependencies:
vite: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
- typescript
vite@5.4.19(@types/node@20.19.7)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0):
vite@5.4.19(@types/node@22.16.0)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0):
dependencies:
esbuild: 0.21.5
postcss: 8.5.5
rollup: 4.43.0
optionalDependencies:
'@types/node': 20.19.7
'@types/node': 22.16.0
fsevents: 2.3.3
less: 4.4.0
lightningcss: 1.30.1
sass: 1.89.2
terser: 5.42.0
vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0):
vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0):
dependencies:
esbuild: 0.25.5
fdir: 6.4.6(picomatch@4.0.2)
@ -23376,7 +23415,7 @@ snapshots:
rollup: 4.43.0
tinyglobby: 0.2.14
optionalDependencies:
'@types/node': 20.19.7
'@types/node': 22.16.0
fsevents: 2.3.3
jiti: 2.4.2
less: 4.4.0
@ -23478,6 +23517,12 @@ snapshots:
- uglify-js
optional: true
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
whatwg-mimetype@4.0.0: {}
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3