+ )
+}
diff --git a/smoosense-gui/src/components/folder-browser/TreeNodeComponent.tsx b/smoosense-gui/src/components/folder-browser/TreeNodeComponent.tsx
index 3a74ba9..e948220 100644
--- a/smoosense-gui/src/components/folder-browser/TreeNodeComponent.tsx
+++ b/smoosense-gui/src/components/folder-browser/TreeNodeComponent.tsx
@@ -8,38 +8,7 @@ import { loadFolderContents, setViewingId, toggleNodeExpansion } from '@/lib/fea
import { cn } from '@/lib/utils'
import { getFileType, FileType } from '@/lib/utils/fileTypes'
import { ICONS } from '@/lib/utils/iconUtils'
-
-// Utility functions for formatting file info
-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 Math.round((bytes / Math.pow(k, i)) * 10) / 10 + ' ' + sizes[i]
-}
-
-function formatDate(timestamp: number): string {
- return new Date(timestamp).toLocaleDateString()
-}
-
-function formatRelativeTime(timestamp: number): string {
- const now = Date.now()
- const diff = now - timestamp
- const minute = 60 * 1000
- const hour = minute * 60
- const day = hour * 24
- const week = day * 7
- const month = day * 30
- const year = day * 365
-
- if (diff < minute) return 'now'
- if (diff < hour) return `${Math.floor(diff / minute)}m`
- if (diff < day) return `${Math.floor(diff / hour)}h`
- if (diff < week) return `${Math.floor(diff / day)}d`
- if (diff < month) return `${Math.floor(diff / week)}w`
- if (diff < year) return `${Math.floor(diff / month)}mo`
- return `${Math.floor(diff / year)}y`
-}
+import { formatDate, formatRelativeTime } from '@/lib/utils/timeUtils'
export interface ArboristNodeData {
id: string
@@ -60,6 +29,14 @@ interface TreeNodeComponentProps {
dragHandle?: (el: HTMLDivElement | null) => void
}
+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 Math.round((bytes / Math.pow(k, i)) * 10) / 10 + ' ' + sizes[i]
+}
+
export default function TreeNodeComponent({ node, style }: TreeNodeComponentProps) {
const dispatch = useAppDispatch()
const viewingId = useAppSelector(state => state.folderTree.viewingId)
diff --git a/smoosense-gui/src/components/folder-browser/previewers/ColumnarTablePreviewer.tsx b/smoosense-gui/src/components/folder-browser/previewers/ColumnarTablePreviewer.tsx
index bcfe50a..c8610bd 100644
--- a/smoosense-gui/src/components/folder-browser/previewers/ColumnarTablePreviewer.tsx
+++ b/smoosense-gui/src/components/folder-browser/previewers/ColumnarTablePreviewer.tsx
@@ -47,7 +47,7 @@ export default function ColumnarTablePreviewer({ item }: ColumnarTablePreviewerP
try {
const sqlKey = generateSqlKey('parquet_metadata')
- const result = await executeQueryAsListOfDict(query, sqlKey, dispatch)
+ const result = await executeQueryAsListOfDict(query, sqlKey, dispatch, 'duckdb', item.path)
setData(result)
} catch (err) {
console.error('Error executing parquet metadata query:', err)
@@ -58,7 +58,9 @@ export default function ColumnarTablePreviewer({ item }: ColumnarTablePreviewerP
}
fetchData()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [query, dispatch, isParquet])
+ // Note: item.path is not included because it's already captured in the query dependency
const renderContent = () => {
if (!isParquet) {
diff --git a/smoosense-gui/src/components/folder-browser/previewers/RowTablePreviewer.tsx b/smoosense-gui/src/components/folder-browser/previewers/RowTablePreviewer.tsx
index a7da707..94de6e7 100644
--- a/smoosense-gui/src/components/folder-browser/previewers/RowTablePreviewer.tsx
+++ b/smoosense-gui/src/components/folder-browser/previewers/RowTablePreviewer.tsx
@@ -29,7 +29,7 @@ export default function RowTablePreviewer({ item }: RowTablePreviewerProps) {
try {
const sqlKey = generateSqlKey('table_preview')
- const result = await executeQueryAsListOfDict(query, sqlKey, dispatch)
+ const result = await executeQueryAsListOfDict(query, sqlKey, dispatch, 'duckdb', item.path)
setData(result)
} catch (err) {
console.error('Error executing table preview query:', err)
@@ -40,7 +40,9 @@ export default function RowTablePreviewer({ item }: RowTablePreviewerProps) {
}
fetchData()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [query, dispatch])
+ // Note: item.path is not included because it's already captured in the query dependency
const renderContent = () => {
if (isLoading) {
diff --git a/smoosense-gui/src/components/providers/TableUrlParamsProvider.tsx b/smoosense-gui/src/components/providers/TableUrlParamsProvider.tsx
index f6ac263..bf95afe 100644
--- a/smoosense-gui/src/components/providers/TableUrlParamsProvider.tsx
+++ b/smoosense-gui/src/components/providers/TableUrlParamsProvider.tsx
@@ -29,12 +29,6 @@ function TableUrlParamsProviderInner({ children }: { children: React.ReactNode }
dispatch(uiSliceActions.setBaseUrl(baseUrl))
}
- // Handle queryEngine parameter
- const queryEngine = searchParams.get('queryEngine')
- if (queryEngine && (queryEngine === 'duckdb' || queryEngine === 'athena' || queryEngine === 'lance')) {
- dispatch(uiSliceActions.setQueryEngine(queryEngine))
- }
-
// Handle tablePath specifically
const urlTablePath = searchParams.get('tablePath')
if (urlTablePath !== currentTablePath) {
@@ -47,6 +41,15 @@ function TableUrlParamsProviderInner({ children }: { children: React.ReactNode }
}
}
+ // Handle queryEngine parameter
+ const queryEngine = searchParams.get('queryEngine')
+ if (queryEngine && (queryEngine === 'duckdb' || queryEngine === 'athena' || queryEngine === 'lance')) {
+ dispatch(uiSliceActions.setQueryEngine(queryEngine))
+ } else if (!queryEngine && urlTablePath && urlTablePath.endsWith('.lance')) {
+ // If queryEngine is not provided and tablePath ends with .lance, set queryEngine to lance
+ dispatch(uiSliceActions.setQueryEngine('lance'))
+ }
+
// Collect all other URL parameters and batch update UI slice
const batchUpdates: Record = {}
diff --git a/smoosense-gui/src/components/sql/SqlQueryPanel.tsx b/smoosense-gui/src/components/sql/SqlQueryPanel.tsx
index 037bddd..2e77b95 100644
--- a/smoosense-gui/src/components/sql/SqlQueryPanel.tsx
+++ b/smoosense-gui/src/components/sql/SqlQueryPanel.tsx
@@ -17,6 +17,7 @@ import { useTheme } from 'next-themes'
export default function SqlQueryPanel() {
const tablePath = useAppSelector((state) => state.ui.tablePath)
+ const queryEngine = useAppSelector((state) => state.ui.queryEngine)
const { theme, systemTheme } = useTheme()
const isDark = theme === 'dark' || (theme === 'system' && systemTheme === 'dark')
const sqlQuery = useAppSelector((state) => state.ui.sqlQuery)
@@ -72,12 +73,18 @@ export default function SqlQueryPanel() {
// Initialize default query when no query exists and tablePath is available
useEffect(() => {
if (tablePath && !sqlQuery) {
- dispatch(setSqlQuery(`SELECT * FROM '${tablePath}' LIMIT 10`))
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+ dispatch(setSqlQuery(`SELECT * FROM ${tableRef} LIMIT 10`))
}
- }, [tablePath, sqlQuery, dispatch])
+ }, [tablePath, queryEngine, sqlQuery, dispatch])
const handleExecuteQuery = async () => {
if (!sqlQuery.trim()) return
+ if (!queryEngine || !tablePath) {
+ console.error('queryEngine and tablePath are required')
+ return
+ }
const queryStartTime = Date.now()
setStartTime(queryStartTime)
@@ -85,14 +92,14 @@ export default function SqlQueryPanel() {
setIsLoading(true)
// Clear previous results to show loading state
setCurrentResult(null)
-
+
try {
const sqlKey = generateSqlKey('user_query')
- const result = await executeQuery(sqlQuery, sqlKey, dispatch)
-
+ const result = await executeQuery(sqlQuery, sqlKey, dispatch, queryEngine, tablePath)
+
// Save to Redux store
dispatch(setSqlResult(result))
-
+
// Update local state with raw result
setCurrentResult(result)
} catch (error) {
diff --git a/smoosense-gui/src/components/ui/accordion.tsx b/smoosense-gui/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..13edbab
--- /dev/null
+++ b/smoosense-gui/src/components/ui/accordion.tsx
@@ -0,0 +1,57 @@
+"use client"
+
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
{children}
+
+))
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/smoosense-gui/src/lib/api/__tests__/queries.test.ts b/smoosense-gui/src/lib/api/__tests__/queries.test.ts
index 9b459d6..341b1f3 100644
--- a/smoosense-gui/src/lib/api/__tests__/queries.test.ts
+++ b/smoosense-gui/src/lib/api/__tests__/queries.test.ts
@@ -35,7 +35,7 @@ describe('getColumnMetadata', () => {
json: async () => mockApiResponse,
} as Response)
- const result = await getColumnMetadata('/test/file.csv', mockDispatch)
+ const result = await getColumnMetadata('/test/file.csv', mockDispatch, 'duckdb')
expect(result).toHaveLength(5)
@@ -131,8 +131,8 @@ describe('getColumnMetadata', () => {
})
it('should handle empty file path', async () => {
- await expect(getColumnMetadata('', mockDispatch)).rejects.toThrow('Table path cannot be empty')
- await expect(getColumnMetadata(' ', mockDispatch)).rejects.toThrow('Table path cannot be empty')
+ await expect(getColumnMetadata('', mockDispatch, 'duckdb')).rejects.toThrow('Table path cannot be empty')
+ await expect(getColumnMetadata(' ', mockDispatch, 'duckdb')).rejects.toThrow('Table path cannot be empty')
})
it('should handle API errors', async () => {
@@ -149,7 +149,7 @@ describe('getColumnMetadata', () => {
json: async () => mockErrorResponse,
} as Response)
- await expect(getColumnMetadata('/nonexistent/file.csv', mockDispatch)).rejects.toThrow('Table not found')
+ await expect(getColumnMetadata('/nonexistent/file.csv', mockDispatch, 'duckdb')).rejects.toThrow('Table not found')
})
it('should call the correct SQL query', async () => {
@@ -165,7 +165,7 @@ describe('getColumnMetadata', () => {
json: async () => mockApiResponse,
} as Response)
- await getColumnMetadata('/test/file.csv', mockDispatch)
+ await getColumnMetadata('/test/file.csv', mockDispatch, 'duckdb')
expect(mockFetch).toHaveBeenCalledWith(`${API_PREFIX}/query`, {
method: 'POST',
@@ -173,7 +173,9 @@ describe('getColumnMetadata', () => {
'Content-Type': 'application/json',
},
body: JSON.stringify({
- query: "SELECT column_name, column_type FROM (DESCRIBE SELECT * FROM '/test/file.csv')"
+ query: "SELECT column_name, column_type FROM (DESCRIBE SELECT * FROM '/test/file.csv')",
+ queryEngine: 'duckdb',
+ tablePath: '/test/file.csv'
}),
})
})
@@ -196,7 +198,7 @@ describe('getColumnMetadata', () => {
json: async () => mockApiResponse,
} as Response)
- const result = await getColumnMetadata('/test/file.csv', mockDispatch)
+ const result = await getColumnMetadata('/test/file.csv', mockDispatch, 'duckdb')
// Should have original columns plus flattened struct fields
expect(result).toHaveLength(6) // 3 original + 3 flattened struct fields
@@ -252,7 +254,7 @@ describe('getColumnMetadata', () => {
json: async () => mockApiResponse,
} as Response)
- const result = await getColumnMetadata('/test/file.csv', mockDispatch)
+ const result = await getColumnMetadata('/test/file.csv', mockDispatch, 'duckdb')
expect(result).toHaveLength(4) // 1 original + 3 flattened fields
@@ -298,7 +300,7 @@ describe('getColumnMetadata', () => {
json: async () => mockStatsResponse,
} as Response)
- const result = await getColumnMetadata('/test/file.parquet', mockDispatch)
+ const result = await getColumnMetadata('/test/file.parquet', mockDispatch, 'duckdb')
expect(result).toHaveLength(2)
@@ -361,7 +363,7 @@ describe('getColumnMetadata', () => {
json: async () => mockMetadataResponse,
} as Response)
- const result = await getColumnMetadata('/test/file.csv', mockDispatch)
+ const result = await getColumnMetadata('/test/file.csv', mockDispatch, 'duckdb')
expect(result).toHaveLength(1)
expect(result[0].stats).toBeNull()
@@ -395,7 +397,7 @@ describe('getColumnMetadata', () => {
json: async () => mockStatsResponse,
} as Response)
- const result = await getColumnMetadata('/test/file.parquet', mockDispatch)
+ const result = await getColumnMetadata('/test/file.parquet', mockDispatch, 'duckdb')
expect(result[0].stats).toEqual({
min: 'active',
@@ -433,7 +435,7 @@ describe('getColumnMetadata', () => {
json: async () => mockStatsResponse,
} as Response)
- const result = await getColumnMetadata('/test/file.parquet', mockDispatch)
+ const result = await getColumnMetadata('/test/file.parquet', mockDispatch, 'duckdb')
expect(result[0].stats).toEqual({
min: null,
@@ -479,7 +481,7 @@ describe('getColumnMetadata', () => {
json: async () => mockStatsResponse,
} as Response)
- const result = await getColumnMetadata('/test/file.parquet', mockDispatch)
+ const result = await getColumnMetadata('/test/file.parquet', mockDispatch, 'duckdb')
expect(result[0].stats?.allNull).toBe(true) // col_all_null: 100 nulls out of 100
expect(result[1].stats?.allNull).toBe(false) // col_some_null: 25 nulls out of 100
@@ -512,7 +514,7 @@ describe('getColumnMetadata', () => {
json: async () => mockStatsErrorResponse,
} as Response)
- const result = await getColumnMetadata('/test/file.parquet', mockDispatch)
+ const result = await getColumnMetadata('/test/file.parquet', mockDispatch, 'duckdb')
expect(result).toHaveLength(1)
expect(result[0].stats).toBeNull()
diff --git a/smoosense-gui/src/lib/api/queries.ts b/smoosense-gui/src/lib/api/queries.ts
index e4edf21..9550317 100644
--- a/smoosense-gui/src/lib/api/queries.ts
+++ b/smoosense-gui/src/lib/api/queries.ts
@@ -39,7 +39,9 @@ interface ColumnMeta {
export async function executeQuery(
sqlQuery: string,
sqlKey: string,
- dispatch: AppDispatch
+ dispatch: AppDispatch,
+ queryEngine: string,
+ tablePath: string
): Promise {
if (!sqlQuery.trim()) {
throw new Error('Query cannot be empty')
@@ -54,7 +56,12 @@ export async function executeQuery(
}
dispatch(addExecution({ sqlKey, query: sqlQuery.trim(), result: runningResult }))
- const requestData = { query: sqlQuery.trim() }
+ const requestData = {
+ query: sqlQuery.trim(),
+ queryEngine,
+ tablePath
+ }
+
// Executing SQL query
try {
@@ -97,10 +104,12 @@ export async function executeQueryAsListOfDict(
sqlQuery: string,
sqlKey: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- dispatch: any
+ dispatch: any,
+ queryEngine: string,
+ tablePath: string
): Promise {
- const rawResult = await executeQuery(sqlQuery, sqlKey, dispatch)
-
+ const rawResult = await executeQuery(sqlQuery, sqlKey, dispatch, queryEngine, tablePath)
+
if (rawResult.status === 'error') {
throw new Error(rawResult.error || 'Query failed')
}
@@ -114,26 +123,73 @@ export async function executeQueryAsListOfDict(
export async function executeQueryAsDictOfList(
sqlQuery: string,
sqlKey: string,
- dispatch: AppDispatch
+ dispatch: AppDispatch,
+ queryEngine: string,
+ tablePath: string
): Promise {
- const rawResult = await executeQuery(sqlQuery, sqlKey, dispatch)
-
+ const rawResult = await executeQuery(sqlQuery, sqlKey, dispatch, queryEngine, tablePath)
+
if (rawResult.status === 'error') {
throw new Error(rawResult.error || 'Query failed')
}
return _.zipObject(
- rawResult.column_names,
+ rawResult.column_names,
_.zip(...rawResult.rows)
) as DictOfList
}
-async function getParquetStats(tablePath: string, dispatch: AppDispatch): Promise | null> {
+async function getLanceStats(tablePath: string, dispatch: AppDispatch, queryEngine: string): Promise | null> {
+ // Only get stats for Lance tables
+ if (queryEngine !== 'lance') {
+ return null
+ }
+
+ try {
+ const statsQuery = `SUMMARIZE SELECT * FROM lance_table`
+
+ const rows = await executeQueryAsListOfDict(statsQuery, `lance_stats`, dispatch, queryEngine, tablePath)
+ const statsMap: Record = {}
+
+ for (const row of rows) {
+ const columnName = String(row.column_name)
+ const min = row.min === null || typeof row.min === 'boolean' ? null : row.min
+ const max = row.max === null || typeof row.max === 'boolean' ? null : row.max
+ const count = Number(row.count || 0)
+ const nullPercentage = Number(row.null_percentage || 0)
+ const cntNull = Math.round((nullPercentage / 100) * count)
+ const cntAll = count
+
+ statsMap[columnName] = {
+ min,
+ max,
+ cntAll,
+ cntNull,
+ hasNull: cntNull > 0,
+ singleValue: min !== null && max !== null && min === max,
+ allNull: cntNull === cntAll
+ }
+ }
+
+ return statsMap
+ } catch (error) {
+ // Failed to get Lance stats
+ console.error(error)
+ return null
+ }
+}
+
+async function getParquetStats(tablePath: string, dispatch: AppDispatch, queryEngine: string): Promise | null> {
// Check if the file is a Parquet file
if (!tablePath.toLowerCase().endsWith('.parquet')) {
return null
}
+ // Don't get parquet stats for Lance tables
+ if (queryEngine === 'lance') {
+ return null
+ }
+
try {
const statsQuery = `
SELECT
@@ -142,14 +198,14 @@ async function getParquetStats(tablePath: string, dispatch: AppDispatch): Promis
MIN(stats_min_value) AS min,
MAX(stats_max_value) AS max,
-- Sometimes parquet metadata may be wrong for columns with all null values
- (CASE WHEN (MIN(stats_min_value) IS NULL AND MAX(stats_max_value) IS NULL)
- THEN SUM(num_values)
+ (CASE WHEN (MIN(stats_min_value) IS NULL AND MAX(stats_max_value) IS NULL)
+ THEN SUM(num_values)
ELSE SUM(stats_null_count) END) AS cntNull
FROM parquet_metadata('${tablePath}')
GROUP BY path_in_schema
`
- const rows = await executeQueryAsListOfDict(statsQuery, `parquet_stats`, dispatch)
+ const rows = await executeQueryAsListOfDict(statsQuery, `parquet_stats`, dispatch, queryEngine, tablePath)
const statsMap: Record = {}
for (const row of rows) {
@@ -158,7 +214,7 @@ async function getParquetStats(tablePath: string, dispatch: AppDispatch): Promis
const max = row.max === null || typeof row.max === 'boolean' ? null : row.max
const cntAll = Number(row.cntAll)
const cntNull = Number(row.cntNull)
-
+
statsMap[columnName] = {
min,
max,
@@ -179,45 +235,50 @@ async function getParquetStats(tablePath: string, dispatch: AppDispatch): Promis
}
export async function getColumnMetadata(
- tablePath: string,
+ tablePath: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- dispatch: any
+ dispatch: any,
+ queryEngine: string
): Promise {
if (!tablePath.trim()) {
throw new Error('Table path cannot be empty')
}
- const metaQuery = `SELECT column_name, column_type FROM (DESCRIBE SELECT * FROM '${tablePath}')`
- const rows = await executeQueryAsListOfDict(metaQuery, `column_metadata`, dispatch)
-
- // Get Parquet stats if available
- const parquetStats = await getParquetStats(tablePath, dispatch)
-
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+ const metaQuery = `SELECT column_name, column_type FROM (DESCRIBE SELECT * FROM ${tableRef})`
+ const rows = await executeQueryAsListOfDict(metaQuery, `column_metadata`, dispatch, queryEngine, tablePath)
+
+ // Get stats if available (Lance or Parquet)
+ const lanceStats = await getLanceStats(tablePath, dispatch, queryEngine)
+ const parquetStats = await getParquetStats(tablePath, dispatch, queryEngine)
+ const stats = lanceStats || parquetStats
+
const columns: ColumnMeta[] = []
-
+
for (const row of rows) {
const columnName = String(row.column_name)
const duckdbType = String(row.column_type)
-
+
// Add the original column
columns.push({
column_name: columnName,
duckdbType,
typeShortcuts: computeTypeShortcuts(duckdbType),
- stats: parquetStats?.[columnName] || null
+ stats: stats?.[columnName] || null
})
-
+
// If it's a struct type, flatten the fields and add them as separate columns
if (isStructType(duckdbType)) {
try {
const flattenedFields = flattenStructFields(columnName, duckdbType)
-
+
for (const field of flattenedFields) {
columns.push({
column_name: field.column_name,
duckdbType: field.duckdbType,
typeShortcuts: computeTypeShortcuts(field.duckdbType),
- stats: parquetStats?.[field.column_name] || null
+ stats: stats?.[field.column_name] || null
})
}
} catch (error) {
diff --git a/smoosense-gui/src/lib/features/balanceMap/balanceMapSlice.ts b/smoosense-gui/src/lib/features/balanceMap/balanceMapSlice.ts
index 630565c..95d0813 100644
--- a/smoosense-gui/src/lib/features/balanceMap/balanceMapSlice.ts
+++ b/smoosense-gui/src/lib/features/balanceMap/balanceMapSlice.ts
@@ -31,6 +31,7 @@ interface FetchBalanceMapParams {
bubblePlotYColumn: string
bubblePlotBreakdownColumn: string // Required - BalanceMap needs breakdown column for color mapping
tablePath: string
+ queryEngine: string
filterCondition: string | null
xBin: {
min: number
@@ -55,6 +56,7 @@ const fetchBalanceMapFunction = async (
bubblePlotYColumn,
bubblePlotBreakdownColumn,
tablePath,
+ queryEngine,
filterCondition,
xBin,
yBin
@@ -64,6 +66,9 @@ const fetchBalanceMapFunction = async (
const whereClause = filterCondition ? `WHERE ${filterCondition}` : ''
const additionalWhere = whereClause ? `${whereClause} AND` : 'WHERE'
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+
// Build query
const query = `
WITH filtered AS (
@@ -71,7 +76,7 @@ const fetchBalanceMapFunction = async (
${sanitizeName(bubblePlotXColumn)} AS x,
${sanitizeName(bubblePlotYColumn)} AS y,
${sanitizeName(bubblePlotBreakdownColumn)} AS breakdown
- FROM '${tablePath}'
+ FROM ${tableRef}
${additionalWhere} x IS NOT NULL AND y IS NOT NULL
), binned AS (
SELECT
@@ -91,7 +96,7 @@ const fetchBalanceMapFunction = async (
ORDER BY 1, 2
`
- const data = await executeQueryAsListOfDict(query, 'balanceMap', dispatch)
+ const data = await executeQueryAsListOfDict(query, 'balanceMap', dispatch, queryEngine, tablePath)
// Process data into a single balance map group (no grouping by breakdown anymore)
const items = data as unknown as BalanceMapDataPoint[]
diff --git a/smoosense-gui/src/lib/features/balanceMap/useBalanceMap.ts b/smoosense-gui/src/lib/features/balanceMap/useBalanceMap.ts
index f2f701c..658c8aa 100644
--- a/smoosense-gui/src/lib/features/balanceMap/useBalanceMap.ts
+++ b/smoosense-gui/src/lib/features/balanceMap/useBalanceMap.ts
@@ -14,6 +14,7 @@ interface UseBalanceMapResult {
export function useBalanceMap(): UseBalanceMapResult {
const tablePath = useAppSelector((state) => state.ui.tablePath)
+ const queryEngine = useAppSelector((state) => state.ui.queryEngine)
const bubblePlotXColumn = useAppSelector((state) => state.ui.bubblePlotXColumn)
const bubblePlotYColumn = useAppSelector((state) => state.ui.bubblePlotYColumn)
const bubblePlotBreakdownColumn = useAppSelector((state) => state.ui.bubblePlotBreakdownColumn)
@@ -25,7 +26,7 @@ export function useBalanceMap(): UseBalanceMapResult {
// Build parameters for balance map fetching
const params = useMemo(() => {
- if (!tablePath || !bubblePlotXColumn || !bubblePlotYColumn || !bubblePlotBreakdownColumn || !xStatsData || !yStatsData) {
+ if (!tablePath || !queryEngine || !bubblePlotXColumn || !bubblePlotYColumn || !bubblePlotBreakdownColumn || !xStatsData || !yStatsData) {
return null
}
@@ -43,11 +44,12 @@ export function useBalanceMap(): UseBalanceMapResult {
bubblePlotYColumn,
bubblePlotBreakdownColumn,
tablePath,
+ queryEngine,
filterCondition,
xBin,
yBin
}
- }, [tablePath, bubblePlotXColumn, bubblePlotYColumn, bubblePlotBreakdownColumn, filterCondition, xStatsData, yStatsData])
+ }, [tablePath, queryEngine, bubblePlotXColumn, bubblePlotYColumn, bubblePlotBreakdownColumn, filterCondition, xStatsData, yStatsData])
const { data, loading, error, setNeedRefresh } = useAsyncData({
stateSelector: (state) => state.balanceMap,
diff --git a/smoosense-gui/src/lib/features/boxplot/boxPlotSlice.ts b/smoosense-gui/src/lib/features/boxplot/boxPlotSlice.ts
index 211f510..b9c31e0 100644
--- a/smoosense-gui/src/lib/features/boxplot/boxPlotSlice.ts
+++ b/smoosense-gui/src/lib/features/boxplot/boxPlotSlice.ts
@@ -16,6 +16,7 @@ interface FetchBoxPlotParams {
boxPlotColumns: string[]
boxPlotBreakdownColumn: string | null
tablePath: string
+ queryEngine: string
filterCondition: string | null
}
@@ -25,7 +26,7 @@ const fetchBoxPlotFunction = async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dispatch: any
): Promise => {
- const { boxPlotColumns, boxPlotBreakdownColumn, tablePath, filterCondition } = params
+ const { boxPlotColumns, boxPlotBreakdownColumn, tablePath, queryEngine, filterCondition } = params
if (boxPlotColumns.length === 0) {
return []
@@ -46,16 +47,19 @@ const fetchBoxPlotFunction = async (
} AS ${e}`
}
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+
// Build FROM clause
- const fromClause = `FROM '${tablePath}'`
-
+ const fromClause = `FROM ${tableRef}`
+
// Build WHERE clause
const whereClause = filterCondition ? `WHERE ${filterCondition}` : ''
// Build query
const query = `
WITH filtered AS (
- SELECT ${isNil(boxPlotBreakdownColumn) ? 'NULL' : sanitizeName(boxPlotBreakdownColumn)} AS breakdown,
+ SELECT ${isNil(boxPlotBreakdownColumn) ? 'NULL' : sanitizeName(boxPlotBreakdownColumn)} AS breakdown,
${boxPlotColumns.map(sanitizeName).join(', ')}
${fromClause}
${whereClause}
@@ -64,7 +68,7 @@ const fetchBoxPlotFunction = async (
GROUP BY breakdown
`
- const data = await executeQueryAsListOfDict(query, 'boxPlot', dispatch)
+ const data = await executeQueryAsListOfDict(query, 'boxPlot', dispatch, queryEngine, tablePath)
// Return raw data directly without grouping
return data as unknown as BoxPlotDataPoint[]
diff --git a/smoosense-gui/src/lib/features/bubblePlot/bubblePlotSlice.ts b/smoosense-gui/src/lib/features/bubblePlot/bubblePlotSlice.ts
index 696b850..24c177a 100644
--- a/smoosense-gui/src/lib/features/bubblePlot/bubblePlotSlice.ts
+++ b/smoosense-gui/src/lib/features/bubblePlot/bubblePlotSlice.ts
@@ -28,6 +28,7 @@ interface FetchBubblePlotParams {
bubblePlotYColumn: string
bubblePlotBreakdownColumn: string | null // Optional - BubblePlot can work without breakdown column
tablePath: string
+ queryEngine: string
filterCondition: string | null
xBin: {
min: number
@@ -47,11 +48,12 @@ const fetchBubblePlotFunction = async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dispatch: any
): Promise => {
- const {
- bubblePlotXColumn,
- bubblePlotYColumn,
- bubblePlotBreakdownColumn,
- tablePath,
+ const {
+ bubblePlotXColumn,
+ bubblePlotYColumn,
+ bubblePlotBreakdownColumn,
+ tablePath,
+ queryEngine,
filterCondition,
xBin,
yBin
@@ -61,14 +63,17 @@ const fetchBubblePlotFunction = async (
const whereClause = filterCondition ? `WHERE ${filterCondition}` : ''
const additionalWhere = whereClause ? `${whereClause} AND` : 'WHERE'
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+
// Build query
const query = `
WITH filtered AS (
- SELECT
+ SELECT
${sanitizeName(bubblePlotXColumn)} AS x,
${sanitizeName(bubblePlotYColumn)} AS y,
${isNil(bubblePlotBreakdownColumn) ? 'NULL' : sanitizeName(bubblePlotBreakdownColumn)} AS breakdown
- FROM '${tablePath}'
+ FROM ${tableRef}
${additionalWhere} x IS NOT NULL AND y IS NOT NULL
), binned AS (
SELECT
@@ -87,7 +92,7 @@ const fetchBubblePlotFunction = async (
ORDER BY 1, 2, 3
`
- const data = await executeQueryAsListOfDict(query, 'bubblePlot', dispatch)
+ const data = await executeQueryAsListOfDict(query, 'bubblePlot', dispatch, queryEngine, tablePath)
// Process data into bubble plot groups
const grouped = _(data as unknown as BubblePlotDataPoint[])
diff --git a/smoosense-gui/src/lib/features/cardinality/cardinalitySlice.ts b/smoosense-gui/src/lib/features/cardinality/cardinalitySlice.ts
index 07ff0cb..f5a0eda 100644
--- a/smoosense-gui/src/lib/features/cardinality/cardinalitySlice.ts
+++ b/smoosense-gui/src/lib/features/cardinality/cardinalitySlice.ts
@@ -1,7 +1,7 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
import { executeQuery, generateSqlKey } from '@/lib/api/queries'
import type { ColumnMeta } from '@/lib/api/queries'
-import type { AppDispatch } from '@/lib/store'
+import type { AppDispatch, RootState } from '@/lib/store'
import { sanitizeName } from '@/lib/utils/sql/helpers'
// Cardinality levels
@@ -89,37 +89,41 @@ export function inferCardinalityFromMetadata(column: ColumnMeta): ColumnCardinal
export const queryCardinality = createAsyncThunk<
{ columnName: string; cardinality: ColumnCardinality },
{ columnName: string; tablePath: string },
- { dispatch: AppDispatch }
+ { dispatch: AppDispatch; state: RootState }
>(
'cardinality/queryCardinality',
- async ({ columnName, tablePath }, { dispatch }) => {
+ async ({ columnName, tablePath }, { dispatch, getState }) => {
+ const state = getState()
+ const queryEngine = state.ui.queryEngine
const cutoff = 1000
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
const sqlQuery = `
- SELECT
+ SELECT
approx_count_distinct(${sanitizeName(columnName)}) AS approxCntD,
- approxCntD / COUNT(*) AS distinctRatio,
+ approxCntD / COUNT(*) AS distinctRatio,
CASE
WHEN approx_count_distinct(${sanitizeName(columnName)}) <= ${cutoff} THEN COUNT(DISTINCT ${sanitizeName(columnName)})
ELSE NULL
END AS cntD,
- CASE
- WHEN approx_count_distinct(${sanitizeName(columnName)}) <= ${cutoff} THEN 'low'
- ELSE 'high'
+ CASE
+ WHEN approx_count_distinct(${sanitizeName(columnName)}) <= ${cutoff} THEN 'low'
+ ELSE 'high'
END AS cardinality
- FROM '${tablePath}'
+ FROM ${tableRef}
WHERE ${sanitizeName(columnName)} IS NOT NULL
`.trim()
// Set up timeout controller
const controller = new AbortController()
-
+
try {
const timeoutId = setTimeout(() => {
controller.abort()
}, 5000) // 5 second timeout
const sqlKey = generateSqlKey(`cardinality_${columnName}`)
- const result = await executeQuery(sqlQuery, sqlKey, dispatch)
+ const result = await executeQuery(sqlQuery, sqlKey, dispatch, queryEngine, tablePath)
clearTimeout(timeoutId)
if (controller.signal.aborted) {
diff --git a/smoosense-gui/src/lib/features/colStats/colBaseStatsSlice.ts b/smoosense-gui/src/lib/features/colStats/colBaseStatsSlice.ts
index 06319f8..9034ca4 100644
--- a/smoosense-gui/src/lib/features/colStats/colBaseStatsSlice.ts
+++ b/smoosense-gui/src/lib/features/colStats/colBaseStatsSlice.ts
@@ -33,13 +33,26 @@ export const queryBaseColumnStats = createAsyncThunk<
{ dispatch: AppDispatch; state: RootState }
>(
'colBaseStats/queryBaseColumnStats',
- async ({ columnName, sqlQuery, filterType }, { dispatch }) => {
+ async ({ columnName, sqlQuery, filterType }, { dispatch, getState }) => {
+ const state = getState()
+ const queryEngine = state.ui.queryEngine
+ const tablePath = state.ui.tablePath
+
+ if (!queryEngine) {
+ throw new Error('queryEngine is required')
+ }
+ if (!tablePath) {
+ throw new Error('tablePath is required')
+ }
+
return queryColumnStats({
columnName,
dispatch,
keyPrefix: 'colstats',
sqlQuery,
- filterType
+ filterType,
+ queryEngine,
+ tablePath
})
}
)
diff --git a/smoosense-gui/src/lib/features/colStats/colFilteredStatsSlice.ts b/smoosense-gui/src/lib/features/colStats/colFilteredStatsSlice.ts
index 6341141..dfca092 100644
--- a/smoosense-gui/src/lib/features/colStats/colFilteredStatsSlice.ts
+++ b/smoosense-gui/src/lib/features/colStats/colFilteredStatsSlice.ts
@@ -29,13 +29,26 @@ export const queryFilteredColumnStats = createAsyncThunk<
{ dispatch: AppDispatch; state: RootState }
>(
'colFilteredStats/queryFilteredColumnStats',
- async ({ columnName, sqlQuery, filterType }, { dispatch }) => {
+ async ({ columnName, sqlQuery, filterType }, { dispatch, getState }) => {
+ const state = getState()
+ const queryEngine = state.ui.queryEngine
+ const tablePath = state.ui.tablePath
+
+ if (!queryEngine) {
+ throw new Error('queryEngine is required')
+ }
+ if (!tablePath) {
+ throw new Error('tablePath is required')
+ }
+
return queryColumnStats({
columnName,
dispatch,
keyPrefix: 'colFilteredStats',
sqlQuery,
- filterType
+ filterType,
+ queryEngine,
+ tablePath
})
}
)
diff --git a/smoosense-gui/src/lib/features/colStats/queryBuilders.ts b/smoosense-gui/src/lib/features/colStats/queryBuilders.ts
index c170d46..07cbd0c 100644
--- a/smoosense-gui/src/lib/features/colStats/queryBuilders.ts
+++ b/smoosense-gui/src/lib/features/colStats/queryBuilders.ts
@@ -6,15 +6,17 @@ import { INVALID_COLUMN_NAME } from '@/lib/utils/columnNameUtils'
// Helper function to build categorical stats query
export function buildCategoricalStatsQuery(
- columnName: string,
- tablePath: string,
- filterCondition?: string | null
+ columnName: string,
+ tablePath: string,
+ filterCondition: string | null | undefined,
+ queryEngine: string
): string {
const whereClause = filterCondition ? `WHERE ${filterCondition}` : ''
-
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+
return `
WITH filtered AS (
- SELECT * FROM '${tablePath}' ${whereClause}
+ SELECT * FROM ${tableRef} ${whereClause}
), stats AS (
SELECT
COUNT(*) AS cnt_all,
@@ -45,16 +47,18 @@ export function buildCategoricalStatsQuery(
// Helper function to build histogram stats query
export function buildHistogramStatsQuery(
- columnName: string,
- tablePath: string,
- histogramNumberOfBins: number,
- filterCondition?: string | null
+ columnName: string,
+ tablePath: string,
+ histogramNumberOfBins: number,
+ filterCondition: string | null | undefined,
+ queryEngine: string
): string {
const whereClause = filterCondition ? `WHERE ${filterCondition}` : ''
-
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+
return `
WITH filtered AS (
- SELECT * FROM '${tablePath}' ${whereClause}
+ SELECT * FROM ${tableRef} ${whereClause}
), stats AS (
SELECT
COUNT(*) AS cnt_all,
@@ -103,14 +107,16 @@ export function buildHistogramStatsQuery(
// Helper function to build text stats query
export function buildTextStatsQuery(
- columnName: string,
- tablePath: string,
- filterCondition?: string | null
+ columnName: string,
+ tablePath: string,
+ filterCondition: string | null | undefined,
+ queryEngine: string
): string {
const whereClause = filterCondition ? `WHERE ${filterCondition}` : ''
-
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+
return `
- SELECT
+ SELECT
STRUCT_PACK(
min := MIN(${sanitizeName(columnName)}),
max := MAX(${sanitizeName(columnName)})
@@ -119,7 +125,7 @@ export function buildTextStatsQuery(
COUNT(*) AS cnt_all,
COUNT_IF(${sanitizeName(columnName)} IS NULL) AS cnt_null,
COUNT(*) - COUNT_IF(${sanitizeName(columnName)} IS NULL) AS cnt_not_null
- FROM '${tablePath}'
+ FROM ${tableRef}
${whereClause}
`.trim()
}
@@ -145,13 +151,16 @@ export function buildColStatsQueryFromState({
throw new Error('No table path found in state')
}
+ // Get query engine from UI state
+ const queryEngine = state.ui.queryEngine
+
// Get histogram bins from UI state
let histogramNumberOfBins = state.ui.histogramNumberOfBins
// Get distinct count from cardinality data if available
const cardinalityData = state.columns.cardinality[columnName]?.data
const distinctCount = cardinalityData?.cntD
-
+
// Use the smaller value between distinctCount and histogramNumberOfBins for histogram queries
if (distinctCount !== null && distinctCount !== undefined && distinctCount > 0) {
histogramNumberOfBins = Math.min(histogramNumberOfBins, distinctCount)
@@ -165,11 +174,11 @@ export function buildColStatsQueryFromState({
// Build and return appropriate query
if (filterType === FilterType.ENUM) {
- return buildCategoricalStatsQuery(columnName, tablePath, whereClause)
+ return buildCategoricalStatsQuery(columnName, tablePath, whereClause, queryEngine)
} else if (filterType === FilterType.RANGE) {
- return buildHistogramStatsQuery(columnName, tablePath, histogramNumberOfBins, whereClause)
+ return buildHistogramStatsQuery(columnName, tablePath, histogramNumberOfBins, whereClause, queryEngine)
} else {
// TEXT or NONE - default to text query
- return buildTextStatsQuery(columnName, tablePath, whereClause)
+ return buildTextStatsQuery(columnName, tablePath, whereClause, queryEngine)
}
}
\ No newline at end of file
diff --git a/smoosense-gui/src/lib/features/colStats/statsUtils.ts b/smoosense-gui/src/lib/features/colStats/statsUtils.ts
index ea7a726..d7732ab 100644
--- a/smoosense-gui/src/lib/features/colStats/statsUtils.ts
+++ b/smoosense-gui/src/lib/features/colStats/statsUtils.ts
@@ -75,13 +75,17 @@ export async function queryColumnStats({
dispatch,
keyPrefix,
sqlQuery,
- filterType
+ filterType,
+ queryEngine,
+ tablePath
}: {
columnName: string
dispatch: AppDispatch
keyPrefix: string
sqlQuery: string
filterType: FilterType
+ queryEngine: string
+ tablePath: string
}): Promise<{ columnName: string; stats: ColumnStats }> {
// Set up timeout controller
const controller = new AbortController()
@@ -92,7 +96,7 @@ export async function queryColumnStats({
}, 15000) // 15 second timeout
const sqlKey = generateSqlKey(`${keyPrefix}_${columnName}`)
- const result = await executeQueryAsListOfDict(sqlQuery, sqlKey, dispatch)
+ const result = await executeQueryAsListOfDict(sqlQuery, sqlKey, dispatch, queryEngine, tablePath)
clearTimeout(timeoutId)
if (controller.signal.aborted) {
diff --git a/smoosense-gui/src/lib/features/columnMeta/columnMetaSlice.ts b/smoosense-gui/src/lib/features/columnMeta/columnMetaSlice.ts
index a474a1f..a50b072 100644
--- a/smoosense-gui/src/lib/features/columnMeta/columnMetaSlice.ts
+++ b/smoosense-gui/src/lib/features/columnMeta/columnMetaSlice.ts
@@ -1,6 +1,7 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
import type { ColumnMeta } from '@/lib/api/queries'
import { getColumnMetadata } from '@/lib/api/queries'
+import type { RootState } from '@/lib/store'
export interface ColumnMetaState {
data: ColumnMeta[] | null
@@ -17,8 +18,10 @@ const initialState: ColumnMetaState = {
// Async thunk for fetching column metadata
export const fetchColumnMetadata = createAsyncThunk(
'columnMeta/fetchColumnMetadata',
- async (tablePath: string, { dispatch }) => {
- const result = await getColumnMetadata(tablePath, dispatch)
+ async (tablePath: string, { dispatch, getState }) => {
+ const state = getState() as RootState
+ const queryEngine = state.ui.queryEngine
+ const result = await getColumnMetadata(tablePath, dispatch, queryEngine)
return result
},
{
diff --git a/smoosense-gui/src/lib/features/heatmap/heatmapSlice.ts b/smoosense-gui/src/lib/features/heatmap/heatmapSlice.ts
index 01b2d77..37d653f 100644
--- a/smoosense-gui/src/lib/features/heatmap/heatmapSlice.ts
+++ b/smoosense-gui/src/lib/features/heatmap/heatmapSlice.ts
@@ -32,6 +32,7 @@ interface FetchHeatmapParams {
heatmapXColumn: string
heatmapYColumn: string
tablePath: string
+ queryEngine: string
filterCondition: string | null
}
@@ -80,7 +81,7 @@ const fetchHeatmapFunction = async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dispatch: any
): Promise => {
- const { heatmapXColumn, heatmapYColumn, tablePath, filterCondition } = params
+ const { heatmapXColumn, heatmapYColumn, tablePath, queryEngine, filterCondition } = params
if (!heatmapXColumn || !heatmapYColumn || !tablePath) {
throw new Error('Missing required parameters for heatmap')
@@ -90,19 +91,22 @@ const fetchHeatmapFunction = async (
const whereClause = filterCondition ? `WHERE ${filterCondition}` : ''
const additionalWhere = whereClause ? `${whereClause} AND` : 'WHERE'
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+
const query = `
WITH filtered AS (
- SELECT
- ${sanitizeName(heatmapXColumn)} AS x,
+ SELECT
+ ${sanitizeName(heatmapXColumn)} AS x,
${sanitizeName(heatmapYColumn)} AS y
- FROM '${tablePath}'
+ FROM ${tableRef}
${additionalWhere} x IS NOT NULL AND y IS NOT NULL
- ) SELECT x, y, COUNT(*) AS cnt
+ ) SELECT x, y, COUNT(*) AS cnt
FROM filtered
GROUP BY x, y
`
- const data = (await executeQueryAsListOfDict(query, 'heatMap', dispatch) as unknown) as HeatmapDataPoint[]
+ const data = (await executeQueryAsListOfDict(query, 'heatMap', dispatch, queryEngine, tablePath) as unknown) as HeatmapDataPoint[]
const heatMap = pivotData(data)
return heatMap
diff --git a/smoosense-gui/src/lib/features/histogram/histogramSlice.ts b/smoosense-gui/src/lib/features/histogram/histogramSlice.ts
index 17de036..a80509e 100644
--- a/smoosense-gui/src/lib/features/histogram/histogramSlice.ts
+++ b/smoosense-gui/src/lib/features/histogram/histogramSlice.ts
@@ -24,6 +24,7 @@ interface FetchHistogramParams {
histogramColumn: string
histogramBreakdownColumn: string | null
tablePath: string
+ queryEngine: string
filterCondition: string | null
histogramStatsData: {
bin: {
@@ -40,7 +41,7 @@ const fetchHistogramFunction = async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dispatch: any
): Promise => {
- const { histogramColumn, histogramBreakdownColumn, tablePath, filterCondition, histogramStatsData } = params
+ const { histogramColumn, histogramBreakdownColumn, tablePath, queryEngine, filterCondition, histogramStatsData } = params
if (!histogramStatsData?.bin) {
throw new Error('Missing histogram bin data')
@@ -52,23 +53,26 @@ const fetchHistogramFunction = async (
const whereClause = filterCondition ? `WHERE ${filterCondition}` : ''
const additionalWhere = whereClause ? `${whereClause} AND` : 'WHERE'
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+
// Build query
const query = `
WITH filtered AS (
- SELECT
- ${sanitizeName(histogramColumn)} AS value,
+ SELECT
+ ${sanitizeName(histogramColumn)} AS value,
${isNil(histogramBreakdownColumn) ? 'NULL' : sanitizeName(histogramBreakdownColumn)} AS breakdown
- FROM '${tablePath}'
- ${additionalWhere} value IS NOT NULL
+ FROM ${tableRef}
+ ${additionalWhere} value IS NOT NULL
)
- SELECT breakdown, FLOOR((value - ${min}) / ${step}) AS binIdx,
+ SELECT breakdown, FLOOR((value - ${min}) / ${step}) AS binIdx,
COUNT(*) AS cnt
FROM filtered
GROUP BY 1, 2
ORDER BY 1, 2
`
- const data = await executeQueryAsListOfDict(query, 'histogram', dispatch)
+ const data = await executeQueryAsListOfDict(query, 'histogram', dispatch, queryEngine, tablePath)
// Process data into histogram groups
const grouped = _(data as unknown as HistogramDataPoint[])
diff --git a/smoosense-gui/src/lib/features/rowData/rowDataSlice.ts b/smoosense-gui/src/lib/features/rowData/rowDataSlice.ts
index 5d54146..f400508 100644
--- a/smoosense-gui/src/lib/features/rowData/rowDataSlice.ts
+++ b/smoosense-gui/src/lib/features/rowData/rowDataSlice.ts
@@ -7,16 +7,18 @@ export type RowDataState = BaseAsyncDataState[]>
interface FetchRowDataParams {
query: string
+ tablePath: string
+ queryEngine: string
}
// Row data fetch function
const fetchRowDataFunction = async (
- { query }: FetchRowDataParams,
+ { query, tablePath, queryEngine }: FetchRowDataParams,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dispatch: any
): Promise[]> => {
// Fetch the raw row data only
- const rowData = await executeQueryAsListOfDict(query, 'rowData', dispatch)
+ const rowData = await executeQueryAsListOfDict(query, 'rowData', dispatch, queryEngine, tablePath)
return rowData
}
diff --git a/smoosense-gui/src/lib/hooks/useBoxPlot.ts b/smoosense-gui/src/lib/hooks/useBoxPlot.ts
index ada34e7..2672e9d 100644
--- a/smoosense-gui/src/lib/hooks/useBoxPlot.ts
+++ b/smoosense-gui/src/lib/hooks/useBoxPlot.ts
@@ -17,6 +17,7 @@ interface UseBoxPlotResult {
*/
export function useBoxPlot(): UseBoxPlotResult {
const tablePath = useAppSelector((state) => state.ui.tablePath)
+ const queryEngine = useAppSelector((state) => state.ui.queryEngine)
const boxPlotColumns = useAppSelector((state) => state.ui.boxPlotColumns)
const boxPlotBreakdownColumn = useAppSelector((state) => state.ui.boxPlotBreakdownColumn)
const filterCondition = useAppSelector((state) => extractSqlFilterFromState(state))
@@ -31,9 +32,10 @@ export function useBoxPlot(): UseBoxPlotResult {
boxPlotColumns,
boxPlotBreakdownColumn,
tablePath,
+ queryEngine,
filterCondition
}
- }, [tablePath, boxPlotColumns, boxPlotBreakdownColumn, filterCondition])
+ }, [tablePath, queryEngine, boxPlotColumns, boxPlotBreakdownColumn, filterCondition])
const { data, loading, error, setNeedRefresh } = useAsyncData({
stateSelector: (state) => state.boxPlot,
diff --git a/smoosense-gui/src/lib/hooks/useBubblePlot.ts b/smoosense-gui/src/lib/hooks/useBubblePlot.ts
index 4e20cbb..e6c56c0 100644
--- a/smoosense-gui/src/lib/hooks/useBubblePlot.ts
+++ b/smoosense-gui/src/lib/hooks/useBubblePlot.ts
@@ -18,6 +18,7 @@ interface UseBubblePlotResult {
*/
export function useBubblePlot(): UseBubblePlotResult {
const tablePath = useAppSelector((state) => state.ui.tablePath)
+ const queryEngine = useAppSelector((state) => state.ui.queryEngine)
const bubblePlotXColumn = useAppSelector((state) => state.ui.bubblePlotXColumn)
const bubblePlotYColumn = useAppSelector((state) => state.ui.bubblePlotYColumn)
const bubblePlotBreakdownColumn = useAppSelector((state) => state.ui.bubblePlotBreakdownColumn)
@@ -47,11 +48,12 @@ export function useBubblePlot(): UseBubblePlotResult {
bubblePlotYColumn,
bubblePlotBreakdownColumn,
tablePath,
+ queryEngine,
filterCondition,
xBin,
yBin
}
- }, [tablePath, bubblePlotXColumn, bubblePlotYColumn, bubblePlotBreakdownColumn, filterCondition, xStatsData, yStatsData])
+ }, [tablePath, queryEngine, bubblePlotXColumn, bubblePlotYColumn, bubblePlotBreakdownColumn, filterCondition, xStatsData, yStatsData])
const { data, loading, error, setNeedRefresh } = useAsyncData({
stateSelector: (state) => state.bubblePlot,
diff --git a/smoosense-gui/src/lib/hooks/useFileInfo.ts b/smoosense-gui/src/lib/hooks/useFileInfo.ts
index 328f10d..67ca404 100644
--- a/smoosense-gui/src/lib/hooks/useFileInfo.ts
+++ b/smoosense-gui/src/lib/hooks/useFileInfo.ts
@@ -24,6 +24,7 @@ interface UseFileInfoResult {
export function useFileInfo(): UseFileInfoResult {
const tablePath = useAppSelector((state) => state.ui.tablePath)
+ const queryEngine = useAppSelector((state) => state.ui.queryEngine)
const dispatch = useAppDispatch()
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
@@ -50,7 +51,7 @@ export function useFileInfo(): UseFileInfoResult {
try {
const metadataQuery = `SELECT CAST(key AS VARCHAR) AS key, CAST(value AS VARCHAR) AS value FROM parquet_kv_metadata('${tablePath}')`
const sqlKey = generateSqlKey('parquet_kv_metadata')
- const metadataResult = await executeQueryAsListOfDict(metadataQuery, sqlKey, dispatch)
+ const metadataResult = await executeQueryAsListOfDict(metadataQuery, sqlKey, dispatch, queryEngine, tablePath)
// Convert the result to metadata object
for (const row of metadataResult) {
@@ -89,7 +90,7 @@ export function useFileInfo(): UseFileInfoResult {
}
fetchFileInfo()
- }, [tablePath, dispatch])
+ }, [tablePath, queryEngine, dispatch])
return { data, loading, error }
}
\ No newline at end of file
diff --git a/smoosense-gui/src/lib/hooks/useHeatMap.ts b/smoosense-gui/src/lib/hooks/useHeatMap.ts
index 432021a..8006e78 100644
--- a/smoosense-gui/src/lib/hooks/useHeatMap.ts
+++ b/smoosense-gui/src/lib/hooks/useHeatMap.ts
@@ -17,6 +17,7 @@ interface UseHeatMapResult {
*/
export function useHeatMap(): UseHeatMapResult {
const tablePath = useAppSelector((state) => state.ui.tablePath)
+ const queryEngine = useAppSelector((state) => state.ui.queryEngine)
const heatmapXColumn = useAppSelector((state) => state.ui.heatmapXColumn)
const heatmapYColumn = useAppSelector((state) => state.ui.heatmapYColumn)
const filterCondition = useAppSelector((state) => extractSqlFilterFromState(state))
@@ -31,9 +32,10 @@ export function useHeatMap(): UseHeatMapResult {
heatmapXColumn,
heatmapYColumn,
tablePath,
+ queryEngine,
filterCondition
}
- }, [tablePath, heatmapXColumn, heatmapYColumn, filterCondition])
+ }, [tablePath, queryEngine, heatmapXColumn, heatmapYColumn, filterCondition])
const { data, loading, error, setNeedRefresh } = useAsyncData({
stateSelector: (state) => state.heatmap,
diff --git a/smoosense-gui/src/lib/hooks/useHistogram.ts b/smoosense-gui/src/lib/hooks/useHistogram.ts
index 4aa3019..c5868cb 100644
--- a/smoosense-gui/src/lib/hooks/useHistogram.ts
+++ b/smoosense-gui/src/lib/hooks/useHistogram.ts
@@ -18,6 +18,7 @@ interface UseHistogramResult {
*/
export function useHistogram(): UseHistogramResult {
const tablePath = useAppSelector((state) => state.ui.tablePath)
+ const queryEngine = useAppSelector((state) => state.ui.queryEngine)
const histogramColumn = useAppSelector((state) => state.ui.histogramColumn)
const histogramBreakdownColumn = useAppSelector((state) => state.ui.histogramBreakdownColumn)
const filterCondition = useAppSelector((state) => extractSqlFilterFromState(state))
@@ -45,12 +46,13 @@ export function useHistogram(): UseHistogramResult {
histogramColumn,
histogramBreakdownColumn,
tablePath,
+ queryEngine,
filterCondition,
histogramStatsData: {
bin: histogramStats.bin
}
}
- }, [tablePath, histogramColumn, histogramBreakdownColumn, filterCondition, histogramStatsData])
+ }, [tablePath, queryEngine, histogramColumn, histogramBreakdownColumn, filterCondition, histogramStatsData])
const { data, loading, error, setNeedRefresh } = useAsyncData({
stateSelector: (state) => state.histogram,
diff --git a/smoosense-gui/src/lib/hooks/useRowData.ts b/smoosense-gui/src/lib/hooks/useRowData.ts
index 7e97ecb..e8d3712 100644
--- a/smoosense-gui/src/lib/hooks/useRowData.ts
+++ b/smoosense-gui/src/lib/hooks/useRowData.ts
@@ -14,6 +14,7 @@ interface UseRowDataResult {
export function useRowData(): UseRowDataResult {
const tablePath = useAppSelector((state) => state.ui.tablePath)
+ const queryEngine = useAppSelector((state) => state.ui.queryEngine)
const pageSize = useAppSelector((state) => state.viewing.pageSize)
const pageNumber = useAppSelector((state) => state.viewing.pageNumber)
const sorting = useAppSelector((state) => state.ag.sorting)
@@ -24,8 +25,11 @@ export function useRowData(): UseRowDataResult {
const query = useMemo(() => {
if (!tablePath) return null
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
+
const offset = (pageNumber - 1) * pageSize
-
+
// Combine SQL condition with sampling condition using AND
let combinedCondition = ''
if (sqlCondition && samplingCondition) {
@@ -35,32 +39,32 @@ export function useRowData(): UseRowDataResult {
} else if (samplingCondition) {
combinedCondition = ` WHERE ${samplingCondition}`
}
-
+
// When samplingCondition is not null, use reservoir sampling
if (samplingCondition !== null) {
return `SELECT *
- FROM (SELECT * FROM '${tablePath}'${combinedCondition})
+ FROM (SELECT * FROM ${tableRef}${combinedCondition})
ORDER BY random() --- Changed from reservoir to random. Reservoir does not work for large dataset.
LIMIT ${pageSize}`
}
-
+
// Normal query with sorting when samplingCondition is null
let orderByClause = ''
if (sorting && sorting.length > 0) {
const sortClauses = sorting.map(sort => `${sanitizeName(sort.field)} ${sort.direction.toUpperCase()}`)
orderByClause = ` ORDER BY ${sortClauses.join(', ')}`
}
-
- return `SELECT * FROM '${tablePath}'${combinedCondition}${orderByClause} LIMIT ${pageSize} OFFSET ${offset}`
- }, [tablePath, pageSize, pageNumber, sqlCondition, samplingCondition, sorting])
+
+ return `SELECT * FROM ${tableRef}${combinedCondition}${orderByClause} LIMIT ${pageSize} OFFSET ${offset}`
+ }, [tablePath, queryEngine, pageSize, pageNumber, sqlCondition, samplingCondition, sorting])
const { data, loading, error, setNeedRefresh } = useAsyncData({
stateSelector: (state) => state.rowData,
fetchAction: fetchRowData,
setNeedRefreshAction: setNeedRefreshAction,
buildParams: () => {
- if (!query) return null
- return { query }
+ if (!query || !tablePath || !queryEngine) return null
+ return { query, tablePath, queryEngine }
},
dependencies: [query]
})
diff --git a/smoosense-gui/src/lib/hooks/useTotalRows.ts b/smoosense-gui/src/lib/hooks/useTotalRows.ts
index eb82c1c..28a884f 100644
--- a/smoosense-gui/src/lib/hooks/useTotalRows.ts
+++ b/smoosense-gui/src/lib/hooks/useTotalRows.ts
@@ -7,22 +7,25 @@ import { extractSqlFilterFromState } from '@/lib/utils/state/filterUtils'
export function useTotalRows(): number | null {
const dispatch = useAppDispatch()
const tablePath = useAppSelector((state) => state.ui.tablePath)
+ const queryEngine = useAppSelector((state) => state.ui.queryEngine)
const totalRows = useAppSelector((state) => state.viewing.totalRows)
const filterCondition = useAppSelector((state) => extractSqlFilterFromState(state))
-
+
useEffect(() => {
const fetchTotalRows = async () => {
- if (!tablePath) {
- // Clear total rows if no table path
+ if (!tablePath || !queryEngine) {
+ // Clear total rows if no table path or query engine
dispatch(setTotalRows(null))
return
}
-
+
try {
// Build COUNT query with filter conditions
+ // Use lance_table when queryEngine is lance, otherwise use tablePath
+ const tableRef = queryEngine === 'lance' ? 'lance_table' : `'${tablePath}'`
const whereCondition = filterCondition ? ` WHERE ${filterCondition}` : ''
- const countQuery = `SELECT COUNT(*) as total FROM '${tablePath}'${whereCondition}`
- const result = await executeQueryAsListOfDict(countQuery, 'totalRows', dispatch)
+ const countQuery = `SELECT COUNT(*) as total FROM ${tableRef}${whereCondition}`
+ const result = await executeQueryAsListOfDict(countQuery, 'totalRows', dispatch, queryEngine, tablePath)
if (result && result.length > 0) {
const total = Number(result[0].total)
@@ -35,9 +38,9 @@ export function useTotalRows(): number | null {
}
}
- // Fetch total rows whenever tablePath or filter conditions change
+ // Fetch total rows whenever tablePath, queryEngine, or filter conditions change
fetchTotalRows()
- }, [tablePath, filterCondition, dispatch])
+ }, [tablePath, queryEngine, filterCondition, dispatch])
return totalRows
}
\ No newline at end of file
diff --git a/smoosense-gui/src/lib/utils/timeUtils.ts b/smoosense-gui/src/lib/utils/timeUtils.ts
new file mode 100644
index 0000000..e881f44
--- /dev/null
+++ b/smoosense-gui/src/lib/utils/timeUtils.ts
@@ -0,0 +1,34 @@
+/**
+ * Format a timestamp as a relative time string (e.g., "5m ago", "2h ago")
+ *
+ * @param timestamp - Unix timestamp in milliseconds
+ * @returns Relative time string
+ */
+export function formatRelativeTime(timestamp: number): string {
+ const now = Date.now()
+ const diff = now - timestamp
+ const minute = 60 * 1000
+ const hour = minute * 60
+ const day = hour * 24
+ const week = day * 7
+ const month = day * 30
+ const year = day * 365
+
+ if (diff < minute) return 'now'
+ if (diff < hour) return `${Math.floor(diff / minute)}m`
+ if (diff < day) return `${Math.floor(diff / hour)}h`
+ if (diff < week) return `${Math.floor(diff / day)}d`
+ if (diff < month) return `${Math.floor(diff / week)}w`
+ if (diff < year) return `${Math.floor(diff / month)}mo`
+ return `${Math.floor(diff / year)}y`
+}
+
+/**
+ * Format a timestamp as a date string
+ *
+ * @param timestamp - Unix timestamp in milliseconds
+ * @returns Formatted date string
+ */
+export function formatDate(timestamp: number): string {
+ return new Date(timestamp).toLocaleDateString()
+}
diff --git a/smoosense-py/app_dev.py b/smoosense-py/app_dev.py
index 0a7c0dc..f2606ab 100644
--- a/smoosense-py/app_dev.py
+++ b/smoosense-py/app_dev.py
@@ -54,4 +54,4 @@ def configure_rich_logging() -> None:
"Work": "~/Work",
"S3 bucket": "s3://sense-table-demo",
},
- ).run(threaded=True, debug=True)
+ ).run(threaded=False, debug=True)
diff --git a/smoosense-py/dummy_data/various_types.py b/smoosense-py/dummy_data/various_types.py
index fa6a2ab..6d6c4b9 100644
--- a/smoosense-py/dummy_data/various_types.py
+++ b/smoosense-py/dummy_data/various_types.py
@@ -1,13 +1,21 @@
import os
import random
+import shutil
import string
+import sys
from datetime import datetime, timedelta
+import lancedb
import numpy as np
import pyarrow as pa
import pyarrow.parquet as pq
+# Add parent directory to path to import my_logging
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+from tests.my_logging import getLogger
+
PWD = os.path.dirname(os.path.abspath(__file__))
+logger = getLogger(__name__)
class DummyDataGenerator:
@@ -25,6 +33,7 @@ def __init__(self, n_rows=200, seed=42):
self.seed = seed
self.output_dir = os.path.join(PWD, "../../data")
self._set_random_seed()
+ logger.info(f"Initialized DummyDataGenerator with n_rows={n_rows}, seed={seed}")
def _set_random_seed(self):
"""Set random seeds for reproducibility"""
@@ -189,21 +198,82 @@ def generate_arrays(self):
def create_table(self):
"""Create PyArrow table from generated arrays"""
+ logger.info("Generating arrays...")
arrays = self.generate_arrays()
+ logger.info(f"Creating table with {len(arrays)} columns and {self.n_rows} rows")
return pa.Table.from_arrays(list(arrays.values()), names=list(arrays.keys()))
def ensure_output_directory(self):
"""Create output directory if it doesn't exist"""
os.makedirs(self.output_dir, exist_ok=True)
+ def save_lance(self, table, filename):
+ """Save the data as Lance database with multiple versions"""
+ lance_path = os.path.join(self.output_dir, "lance")
+
+ # Clear the target folder if it exists
+ if os.path.exists(lance_path):
+ logger.info(f"Removing existing Lance directory: {lance_path}")
+ shutil.rmtree(lance_path)
+
+ logger.info(f"Writing Lance database to {lance_path}")
+ # Filter out struct and map columns for Lance
+ lance_columns = []
+ lance_column_names = []
+ for i, field in enumerate(table.schema):
+ if not (pa.types.is_struct(field.type) or pa.types.is_map(field.type)):
+ lance_columns.append(table.column(i))
+ lance_column_names.append(field.name)
+ else:
+ logger.info(
+ f"Skipping column '{field.name}' (type: {field.type}) for Lance - unsupported type"
+ )
+ lance_table = pa.Table.from_arrays(lance_columns, names=lance_column_names)
+
+ # Create initial table
+ db = lancedb.connect(lance_path)
+ lance_db_table = db.create_table(filename, data=lance_table, mode="overwrite")
+ logger.info(f"Lance database written successfully with table name: {filename}")
+
+ # Make an update to create a new version
+ logger.info("Creating version 2: updating rows where bool=true")
+ lance_db_table.update(where="bool=true", values={"string": "updated"})
+ logger.info("Version 2 created successfully")
+
+ # Add more rows to create another version
+ logger.info("Creating version 3: adding first 3 rows")
+ df = lance_table.to_pandas()
+ additional_data = df.head(3)
+ lance_db_table.add(additional_data)
+ logger.info("Version 3 created successfully")
+
+ # Add a new column to create another version
+ logger.info("Creating version 4: adding new_column with default value 'foo'")
+ lance_db_table.add_columns({"new_column": "'foo'"})
+ logger.info("Version 4 created successfully")
+
+ logger.info(f"Lance database with {len(lance_db_table.list_versions())} versions created")
+
def save_files(self, filename="dummy_data_various_types"):
- """Save the generated data as a parquet file"""
+ """Save the generated data as parquet, csv, and lance files"""
+ logger.info(f"Saving files with base name: {filename}")
self.ensure_output_directory()
table = self.create_table()
+
+ # Save as Parquet
parquet_path = os.path.join(self.output_dir, f"{filename}.parquet")
+ logger.info(f"Writing Parquet file to {parquet_path}")
pq.write_table(table, parquet_path)
+ logger.info("Parquet file written successfully")
+
+ # Save as CSV
csv_path = os.path.join(self.output_dir, f"{filename}.csv")
+ logger.info(f"Writing CSV file to {csv_path}")
table.to_pandas().to_csv(csv_path, index=False)
+ logger.info("CSV file written successfully")
+
+ # Save as Lance
+ self.save_lance(table, filename)
def get_schema(self):
"""Get the schema of the generated data"""
@@ -212,11 +282,12 @@ def get_schema(self):
if __name__ == "__main__":
# Example usage
+ logger.info("Starting dummy data generation")
generator = DummyDataGenerator(n_rows=200, seed=42)
# Generate and save data
generator.save_files()
# Print schema
- print("\nSchema:")
- print(generator.get_schema())
+ logger.info("Data generation complete")
+ logger.info(f"Schema: \n{generator.get_schema()}")
diff --git a/smoosense-py/intests/screenshots/folder_browser_csv_dark.png b/smoosense-py/intests/screenshots/folder_browser_csv_dark.png
index 6fa643b..c9dd865 100644
Binary files a/smoosense-py/intests/screenshots/folder_browser_csv_dark.png and b/smoosense-py/intests/screenshots/folder_browser_csv_dark.png differ
diff --git a/smoosense-py/intests/screenshots/folder_browser_csv_light.png b/smoosense-py/intests/screenshots/folder_browser_csv_light.png
index 3f48b47..73428e1 100644
Binary files a/smoosense-py/intests/screenshots/folder_browser_csv_light.png and b/smoosense-py/intests/screenshots/folder_browser_csv_light.png differ
diff --git a/smoosense-py/intests/screenshots/folder_browser_parquet_dark.png b/smoosense-py/intests/screenshots/folder_browser_parquet_dark.png
index 77abee5..9b31e13 100644
Binary files a/smoosense-py/intests/screenshots/folder_browser_parquet_dark.png and b/smoosense-py/intests/screenshots/folder_browser_parquet_dark.png differ
diff --git a/smoosense-py/intests/screenshots/folder_browser_parquet_light.png b/smoosense-py/intests/screenshots/folder_browser_parquet_light.png
index 1518325..11700d1 100644
Binary files a/smoosense-py/intests/screenshots/folder_browser_parquet_light.png and b/smoosense-py/intests/screenshots/folder_browser_parquet_light.png differ
diff --git a/smoosense-py/pyproject.toml b/smoosense-py/pyproject.toml
index 5788e48..e335180 100644
--- a/smoosense-py/pyproject.toml
+++ b/smoosense-py/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "smoosense"
-version = "0.1.0"
+version = "0.1.1"
description = "Smoothly make sense of your large multi-modal datasets"
readme = "README.md"
requires-python = ">=3.9"
@@ -9,6 +9,7 @@ dependencies = [
"click>=8.1.8",
"duckdb>=1.2.1",
"flask>=3.1.0",
+ "lancedb>=0.25.2",
"pydantic>=2.11.7",
"pytz>=2025.2",
"requests>=2.32.3",
@@ -40,6 +41,7 @@ dev = [
"mypy>=1.17.1",
"types-requests>=2.31.0.6",
"pandas-stubs>=2.0.0",
+ "line-profiler>=5.0.0",
]
[tool.setuptools]
@@ -106,8 +108,9 @@ warn_unreachable = true
strict_equality = true
exclude = [
"app_dev.py", # Development-only file, not included in package
+ "tests/", # Test files don't need strict typing
]
[[tool.mypy.overrides]]
-module = ["boto3.*", "botocore.*", "pandas.*", "IPython.*", "daft.*"]
+module = ["boto3.*", "botocore.*", "pandas.*", "IPython.*", "daft.*", "lancedb.*", "pyarrow.*"]
ignore_missing_imports = true
diff --git a/smoosense-py/smoosense/app.py b/smoosense-py/smoosense/app.py
index 453ae71..d9f4233 100644
--- a/smoosense-py/smoosense/app.py
+++ b/smoosense-py/smoosense/app.py
@@ -9,6 +9,7 @@
from pydantic import ConfigDict, validate_call
from smoosense.handlers.fs import fs_bp
+from smoosense.handlers.lance import lance_bp
from smoosense.handlers.pages import pages_bp
from smoosense.handlers.query import query_bp
from smoosense.handlers.s3 import s3_bp
@@ -64,6 +65,7 @@ def create_app(self) -> Flask:
# Register blueprints with url_prefix
app.register_blueprint(query_bp, url_prefix=f"{self.url_prefix}/api")
app.register_blueprint(fs_bp, url_prefix=f"{self.url_prefix}/api")
+ app.register_blueprint(lance_bp, url_prefix=f"{self.url_prefix}/api")
app.register_blueprint(pages_bp, url_prefix=self.url_prefix)
app.register_blueprint(s3_bp, url_prefix=f"{self.url_prefix}/api")
diff --git a/smoosense-py/smoosense/handlers/lance.py b/smoosense-py/smoosense/handlers/lance.py
new file mode 100644
index 0000000..953a731
--- /dev/null
+++ b/smoosense-py/smoosense/handlers/lance.py
@@ -0,0 +1,83 @@
+import logging
+
+from flask import Blueprint, jsonify
+from werkzeug.wrappers import Response
+
+from smoosense.exceptions import InvalidInputException
+from smoosense.lance.db_client import LanceDBClient
+from smoosense.lance.table_client import LanceTableClient
+from smoosense.utils.api import handle_api_errors, require_arg
+
+logger = logging.getLogger(__name__)
+lance_bp = Blueprint("lance", __name__)
+
+
+@lance_bp.get("/lance/list-tables")
+@handle_api_errors
+def list_tables() -> Response:
+ """List all tables in a Lance database directory."""
+ root_folder = require_arg("rootFolder")
+
+ try:
+ client = LanceDBClient(root_folder)
+ tables_info = client.list_tables()
+ return jsonify([table.model_dump() for table in tables_info])
+ except ValueError as e:
+ raise InvalidInputException(str(e)) from e
+ except Exception as e:
+ logger.error(f"Failed to list tables from {root_folder}: {e}")
+ raise InvalidInputException(f"Failed to list Lance tables: {e}") from e
+
+
+@lance_bp.get("/lance/list-versions")
+@handle_api_errors
+def list_versions() -> Response:
+ """List all versions of a table in a Lance database."""
+ root_folder = require_arg("rootFolder")
+ table_name = require_arg("tableName")
+
+ try:
+ client = LanceTableClient(root_folder, table_name)
+ versions_info = client.list_versions()
+ return jsonify([version.model_dump() for version in versions_info])
+ except ValueError as e:
+ raise InvalidInputException(str(e)) from e
+ except Exception as e:
+ logger.error(f"Failed to list versions for table {table_name}: {e}")
+ raise InvalidInputException(f"Failed to list versions: {e}") from e
+
+
+@lance_bp.get("/lance/list-indices")
+@handle_api_errors
+def list_indices() -> Response:
+ """List all indices of a table in a Lance database."""
+ root_folder = require_arg("rootFolder")
+ table_name = require_arg("tableName")
+
+ try:
+ client = LanceTableClient(root_folder, table_name)
+ indices_info = client.list_indices()
+ return jsonify([index.model_dump() for index in indices_info])
+ except ValueError as e:
+ raise InvalidInputException(str(e)) from e
+ except Exception as e:
+ logger.error(f"Failed to list indices for table {table_name}: {e}")
+ raise InvalidInputException(f"Failed to list indices: {e}") from e
+
+
+@lance_bp.get("/lance/list-columns")
+@handle_api_errors
+def list_columns() -> Response:
+ """List all columns of a table in a Lance database."""
+ root_folder = require_arg("rootFolder")
+ table_name = require_arg("tableName")
+
+ try:
+ client = LanceTableClient(root_folder, table_name)
+ columns_info = client.list_columns()
+ return jsonify([column.model_dump() for column in columns_info])
+ except ValueError as e:
+ raise InvalidInputException(str(e)) from e
+ except Exception as e:
+ logger.error(f"Failed to list columns for table {table_name}: {e}")
+ raise InvalidInputException(f"Failed to list columns: {e}") from e
diff --git a/smoosense-py/smoosense/handlers/pages.py b/smoosense-py/smoosense/handlers/pages.py
index e8a2522..db249cb 100644
--- a/smoosense-py/smoosense/handlers/pages.py
+++ b/smoosense-py/smoosense/handlers/pages.py
@@ -65,6 +65,11 @@ def get_tabular_slice_dice() -> Response:
return serve_static_html("Table")
+@pages_bp.get("/DB")
+def get_db() -> Response:
+ return serve_static_html("DB")
+
+
@pages_bp.get("/MiniTable")
def get_mini_table() -> Response:
return serve_static_html("MiniTable")
diff --git a/smoosense-py/smoosense/handlers/query.py b/smoosense-py/smoosense/handlers/query.py
index dd3b49c..d6ec913 100644
--- a/smoosense-py/smoosense/handlers/query.py
+++ b/smoosense-py/smoosense/handlers/query.py
@@ -3,6 +3,7 @@
from flask import Blueprint, Response, current_app, jsonify, request
+from smoosense.lance.table_client import LanceTableClient
from smoosense.utils.api import handle_api_errors
from smoosense.utils.duckdb_connections import check_permissions
from smoosense.utils.serialization import serialize
@@ -14,25 +15,45 @@
@query_bp.post("/query")
@handle_api_errors
def run_query() -> Response:
- connection_maker = current_app.config["DUCKDB_CONNECTION_MAKER"]
- con = connection_maker()
time_start = default_timer()
- query = request.json["query"] if request.json else None
+
+ if not request.json:
+ raise ValueError("JSON body is required")
+
+ query = request.json.get("query")
if not query:
raise ValueError("query is required in JSON body")
check_permissions(query)
- column_names = []
- rows = []
+ query_engine = request.json.get("queryEngine", "duckdb")
+
+ column_names: list[str] = []
+ rows: list[tuple] = []
error = None
+
try:
- result = con.execute(query)
- column_names = [desc[0] for desc in result.description]
- rows = result.fetchall()
+ if query_engine == "lance":
+ # Lance query engine using DuckDB integration
+ table_path = request.json.get("tablePath")
+ if not table_path:
+ raise ValueError("tablePath is required when using lance query engine")
+
+ # Create Lance table client and execute query
+ lance_client = LanceTableClient.from_table_path(table_path)
+ column_names, rows = lance_client.run_duckdb_sql(query)
+
+ else:
+ # DuckDB query engine (default)
+ connection_maker = current_app.config["DUCKDB_CONNECTION_MAKER"]
+ con = connection_maker()
+ result = con.execute(query)
+ column_names = [desc[0] for desc in result.description] if result.description else []
+ rows = result.fetchall()
except Exception as e:
error = str(e)
+ logger.error(f"Query execution failed: {error}")
return jsonify(
{
diff --git a/smoosense-py/smoosense/lance/__init__.py b/smoosense-py/smoosense/lance/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/smoosense-py/smoosense/lance/db_client.py b/smoosense-py/smoosense/lance/db_client.py
new file mode 100644
index 0000000..e2d3eb8
--- /dev/null
+++ b/smoosense-py/smoosense/lance/db_client.py
@@ -0,0 +1,74 @@
+import logging
+import os
+
+import lancedb
+from pydantic import validate_call
+
+from smoosense.lance.models import TableInfo
+
+logger = logging.getLogger(__name__)
+
+
+class LanceDBClient:
+ """Client for interacting with a Lance database."""
+
+ def __init__(self, root_folder: str):
+ """
+ Initialize the Lance database client.
+
+ Args:
+ root_folder: Path to the Lance database directory
+ """
+ if root_folder.startswith("~"):
+ root_folder = os.path.expanduser(root_folder)
+
+ if not os.path.exists(root_folder):
+ raise ValueError(f"Directory does not exist: {root_folder}")
+
+ if not os.path.isdir(root_folder):
+ raise ValueError(f"Path is not a directory: {root_folder}")
+
+ self.root_folder = root_folder
+ self.db = lancedb.connect(root_folder)
+ logger.info(f"Connected to Lance database at {root_folder}")
+
+ @validate_call
+ def list_tables(self) -> list[TableInfo]:
+ """
+ List all tables in the database.
+
+ Returns:
+ List of TableInfo models
+ """
+ table_names = self.db.table_names()
+ logger.info(f"Found {len(table_names)} tables: {table_names}")
+
+ tables_info: list[TableInfo] = []
+ for table_name in table_names:
+ try:
+ table = self.db.open_table(table_name)
+ cnt_versions = len(table.list_versions())
+ cnt_indices = len(table.list_indices())
+
+ tables_info.append(
+ TableInfo(
+ name=table_name,
+ cnt_rows=table.count_rows(),
+ cnt_columns=len(table.schema),
+ cnt_versions=cnt_versions,
+ cnt_indices=cnt_indices,
+ )
+ )
+ except Exception as e:
+ logger.warning(f"Failed to get info for table {table_name}: {e}")
+ tables_info.append(
+ TableInfo(
+ name=table_name,
+ cnt_rows=None,
+ cnt_columns=None,
+ cnt_versions=None,
+ cnt_indices=None,
+ )
+ )
+
+ return tables_info
diff --git a/smoosense-py/smoosense/lance/models.py b/smoosense-py/smoosense/lance/models.py
new file mode 100644
index 0000000..61512e1
--- /dev/null
+++ b/smoosense-py/smoosense/lance/models.py
@@ -0,0 +1,54 @@
+from typing import Optional
+
+from pydantic import BaseModel, Field
+
+
+class TableInfo(BaseModel):
+ """Information about a Lance table."""
+
+ name: str = Field(..., description="Name of the table")
+ cnt_rows: Optional[int] = Field(None, description="Number of rows in the table")
+ cnt_columns: Optional[int] = Field(None, description="Number of columns in the table")
+ cnt_versions: Optional[int] = Field(None, description="Number of versions of the table")
+ cnt_indices: Optional[int] = Field(None, description="Number of indices of the table")
+
+
+class VersionInfo(BaseModel):
+ """Information about a table version."""
+
+ version: int = Field(..., description="Version number")
+ timestamp: int = Field(..., description="Unix timestamp (epoch) of the version")
+ total_data_files: Optional[int] = Field(
+ None, description="Total number of data files in this version"
+ )
+ total_rows: Optional[int] = Field(None, description="Total number of rows in this version")
+ rows_add: Optional[int] = Field(
+ None, description="Number of rows added compared to previous version"
+ )
+ rows_remove: Optional[int] = Field(
+ None, description="Number of rows deleted compared to previous version"
+ )
+ columns_add: list[str] = Field(
+ default_factory=list, description="New columns compared to previous version"
+ )
+ columns_remove: list[str] = Field(
+ default_factory=list, description="Columns removed compared to previous version"
+ )
+
+
+class IndexInfo(BaseModel):
+ """Information about a Lance index."""
+
+ name: str = Field(..., description="Name of the index")
+ index_type: str = Field(..., description="Type of the index (e.g., IVF_PQ, BTREE)")
+ columns: list[str] = Field(..., description="Columns included in the index")
+ num_unindexed_rows: Optional[int] = Field(
+ None, description="Number of unindexed rows in the index"
+ )
+
+
+class ColumnInfo(BaseModel):
+ """Information about a table column."""
+
+ name: str = Field(..., description="Name of the column")
+ type: str = Field(..., description="Data type of the column")
diff --git a/smoosense-py/smoosense/lance/table_client.py b/smoosense-py/smoosense/lance/table_client.py
new file mode 100644
index 0000000..8e4ad8b
--- /dev/null
+++ b/smoosense-py/smoosense/lance/table_client.py
@@ -0,0 +1,384 @@
+import logging
+import os
+from functools import lru_cache
+
+import duckdb
+import lancedb
+import pyarrow as pa
+from pydantic import validate_call
+
+from smoosense.lance.models import ColumnInfo, IndexInfo, VersionInfo
+
+logger = logging.getLogger(__name__)
+
+
+class LanceTableClient:
+ """Client for interacting with a Lance table."""
+
+ def __init__(self, root_folder: str, table_name: str):
+ """
+ Initialize the Lance table client.
+
+ Args:
+ root_folder: Path to the Lance database directory
+ table_name: Name of the table
+ """
+ if root_folder.startswith("~"):
+ root_folder = os.path.expanduser(root_folder)
+
+ if not os.path.exists(root_folder):
+ raise ValueError(f"Directory does not exist: {root_folder}")
+
+ if not os.path.isdir(root_folder):
+ raise ValueError(f"Path is not a directory: {root_folder}")
+
+ self.root_folder = root_folder
+ self.table_name = table_name
+ self.db = lancedb.connect(root_folder)
+ self.table = self.db.open_table(table_name)
+
+ logger.info(f"Connected to Lance table '{table_name}' at {root_folder}")
+
+ @staticmethod
+ def from_table_path(table_path: str) -> "LanceTableClient":
+ """
+ Create a LanceTableClient from a table path.
+
+ Args:
+ table_path: Path to the Lance table (e.g., /path/to/db/table_name.lance)
+
+ Returns:
+ LanceTableClient instance
+
+ Raises:
+ ValueError: If the table path is invalid or doesn't end with .lance
+ """
+ # Expand ~ if present
+ if table_path.startswith("~"):
+ table_path = os.path.expanduser(table_path)
+
+ # Validate path exists
+ if not os.path.exists(table_path):
+ raise ValueError(f"Table path does not exist: {table_path}")
+
+ # Validate path ends with .lance
+ if not table_path.endswith(".lance"):
+ raise ValueError(f"Table path must end with .lance: {table_path}")
+
+ # Extract root folder and table name
+ root_folder = os.path.dirname(table_path)
+ table_name = os.path.basename(table_path).replace(".lance", "")
+
+ return LanceTableClient(root_folder, table_name)
+
+ @staticmethod
+ def _filter_duckdb_incompatible_columns(arrow_table: pa.Table) -> tuple[pa.Table, list[str]]:
+ """
+ Filter out columns with DuckDB-incompatible Arrow types.
+
+ Args:
+ arrow_table: PyArrow table to filter
+
+ Returns:
+ Tuple of (filtered_arrow_table, incompatible_column_names)
+
+ Raises:
+ ValueError: If no compatible columns found
+ """
+ compatible_columns = []
+ incompatible_columns = []
+
+ for field in arrow_table.schema:
+ field_type = field.type
+
+ # Check for DuckDB-incompatible types
+ is_incompatible = (
+ # halffloat (float16) is not supported
+ pa.types.is_float16(field_type)
+ or
+ # Extension types are often not supported
+ isinstance(field_type, pa.ExtensionType)
+ or
+ # Duration types may not be supported
+ pa.types.is_duration(field_type)
+ or
+ # Large binary/string types (64-bit offsets)
+ pa.types.is_large_binary(field_type)
+ or pa.types.is_large_string(field_type)
+ or pa.types.is_large_list(field_type)
+ )
+
+ if is_incompatible:
+ incompatible_columns.append(field.name)
+ logger.warning(
+ f"Skipping column '{field.name}' with unsupported type: {field_type}"
+ )
+ else:
+ compatible_columns.append(field.name)
+
+ if not compatible_columns:
+ raise ValueError("No compatible columns found in Lance table for DuckDB")
+
+ # Select only compatible columns
+ filtered_arrow_table = arrow_table.select(compatible_columns)
+
+ return filtered_arrow_table, incompatible_columns
+
+ @staticmethod
+ @lru_cache(maxsize=3)
+ def _load_and_filter_arrow_table(table_path: str) -> tuple[pa.Table, list[str]]:
+ """
+ Load and filter Arrow table with class-level LRU cache.
+
+ This is cached at the class level with maxsize=3, meaning it persists
+ across different LanceTableClient instances for the same table path.
+
+ Args:
+ table_path: Full path to the Lance table (e.g., /path/to/db/table_name.lance)
+
+ Returns:
+ Tuple of (filtered_arrow_table, incompatible_column_names)
+
+ Raises:
+ ValueError: If no compatible columns found
+ """
+ logger.info(f"Loading Arrow table from {table_path} (cache miss)")
+
+ # Parse table path
+ root_folder = os.path.dirname(table_path)
+ table_name = os.path.basename(table_path).replace(".lance", "")
+
+ # Connect and load
+ db = lancedb.connect(root_folder)
+ table = db.open_table(table_name)
+ arrow_table = table.to_arrow()
+
+ # Filter incompatible columns
+ filtered_arrow_table, incompatible_columns = (
+ LanceTableClient._filter_duckdb_incompatible_columns(arrow_table)
+ )
+
+ if incompatible_columns:
+ logger.warning(
+ f"Filtered out {len(incompatible_columns)} incompatible column(s): {', '.join(incompatible_columns)}"
+ )
+
+ return filtered_arrow_table, incompatible_columns
+
+ def _get_filtered_arrow_table(self) -> pa.Table:
+ """
+ Get the filtered Arrow table with DuckDB-compatible columns only.
+
+ Uses class-level LRU cache keyed by table path to persist across instances.
+
+ Returns:
+ Filtered PyArrow table
+
+ Raises:
+ ValueError: If no compatible columns found
+ """
+ table_path = os.path.join(self.root_folder, f"{self.table_name}.lance")
+ filtered_arrow_table, _ = self._load_and_filter_arrow_table(table_path)
+ return filtered_arrow_table
+
+ def run_duckdb_sql(self, query: str) -> tuple[list[str], list[tuple]]:
+ """
+ Execute a SQL query against the Lance table using DuckDB.
+
+ The table is registered as 'lance_table' in DuckDB.
+ Uses cached Arrow table conversion to avoid repeated conversions on multiple queries.
+
+ Args:
+ query: SQL query to execute (use 'lance_table' to reference the table)
+
+ Returns:
+ Tuple of (column_names, rows) where:
+ - column_names is a list of column name strings
+ - rows is a list of tuples containing the row data
+
+ Raises:
+ ValueError: If no compatible columns found or query execution fails
+ """
+ logger.info(f"Executing DuckDB query on Lance table {self.table_name}")
+
+ # Get filtered Arrow table (uses cache if available)
+ filtered_arrow_table = self._get_filtered_arrow_table()
+
+ # Create DuckDB connection and register the filtered Arrow table
+ con = duckdb.connect()
+ con.register("lance_table", filtered_arrow_table)
+
+ # Execute the query
+ result = con.execute(query)
+ column_names = [desc[0] for desc in result.description] if result.description else []
+ rows = result.fetchall()
+
+ # Close the connection
+ con.close()
+
+ logger.info(f"Query executed successfully: {len(rows)} rows, {len(column_names)} columns")
+
+ return column_names, rows
+
+ @staticmethod
+ def _extract_int_from_metadata(metadata: dict, key: str, default: int = 0) -> int:
+ """
+ Extract an integer value from metadata, handling various data types.
+
+ Args:
+ metadata: Metadata dictionary
+ key: Key to extract
+ default: Default value if extraction fails
+
+ Returns:
+ Integer value or default
+ """
+ try:
+ return int(metadata.get(key, default) or default)
+ except (ValueError, TypeError):
+ return default
+
+ @validate_call
+ def list_versions(self) -> list[VersionInfo]:
+ """
+ List all versions of the table.
+
+ Returns:
+ List of VersionInfo models sorted by version number
+ """
+ logger.info(f"Fetching versions for table {self.table_name}")
+ version_list = self.table.list_versions()
+
+ # Ensure versions are sorted by version number increasingly
+ version_list = sorted(version_list, key=lambda v: int(v["version"]))
+
+ versions_info: list[VersionInfo] = []
+ prev_data_rows = 0
+ prev_deletion_rows = 0
+ prev_columns: set[str] = set()
+
+ for version in version_list:
+ timestamp = version["timestamp"]
+ # Convert datetime to Unix timestamp (epoch) if needed
+ if hasattr(timestamp, "timestamp"):
+ timestamp = int(timestamp.timestamp())
+ else:
+ timestamp = int(timestamp)
+
+ metadata = version.get("metadata", {})
+
+ # Extract fields from metadata using helper function
+ total_data_rows = self._extract_int_from_metadata(metadata, "total_data_file_rows")
+ total_deletion_rows = self._extract_int_from_metadata(
+ metadata, "total_deletion_file_rows"
+ )
+ total_data_files = self._extract_int_from_metadata(metadata, "total_data_files")
+
+ # Calculate diffs
+ rows_add = total_data_rows - prev_data_rows
+ rows_remove = total_deletion_rows - prev_deletion_rows
+
+ # Get schema for this version to calculate column differences
+ try:
+ # Use to_lance() to access the dataset at a specific version
+ dataset = self.table.to_lance()
+ version_dataset = dataset.checkout_version(version["version"])
+ current_columns = set(version_dataset.schema.names)
+ except Exception as e:
+ logger.warning(f"Failed to get schema for version {version['version']}: {e}")
+ current_columns = set()
+
+ # Calculate column differences
+ columns_add = list(current_columns - prev_columns) if prev_columns else []
+ columns_remove = list(prev_columns - current_columns) if prev_columns else []
+
+ versions_info.append(
+ VersionInfo(
+ version=version["version"],
+ timestamp=timestamp,
+ total_data_files=total_data_files,
+ total_rows=total_data_rows,
+ rows_add=rows_add,
+ rows_remove=rows_remove,
+ columns_add=columns_add,
+ columns_remove=columns_remove,
+ )
+ )
+
+ # Update previous values for next iteration
+ prev_data_rows = total_data_rows
+ prev_deletion_rows = total_deletion_rows
+ prev_columns = current_columns
+
+ logger.info(f"Found {len(versions_info)} versions for table {self.table_name}")
+ return versions_info
+
+ @validate_call
+ def list_indices(self) -> list[IndexInfo]:
+ """
+ List all indices of the table.
+
+ Returns:
+ List of IndexInfo models
+ """
+ logger.info(f"Fetching indices for table {self.table_name}")
+
+ try:
+ indices_list = self.table.list_indices()
+ except Exception as e:
+ logger.error(f"Failed to list indices for table {self.table_name}: {e}")
+ # If list_indices fails, return empty list
+ return []
+
+ indices_info: list[IndexInfo] = []
+
+ for idx in indices_list:
+ try:
+ # Get index stats to extract num_unindexed_rows
+ stats = self.table.index_stats(idx.name)
+ num_unindexed_rows = getattr(stats, "num_unindexed_rows", None)
+ except Exception as e:
+ logger.warning(f"Failed to get stats for index {idx.name}: {e}")
+ num_unindexed_rows = None
+
+ indices_info.append(
+ IndexInfo(
+ name=idx.name,
+ index_type=idx.index_type,
+ columns=idx.columns,
+ num_unindexed_rows=num_unindexed_rows,
+ )
+ )
+
+ logger.info(f"Found {len(indices_info)} indices for table {self.table_name}")
+ return indices_info
+
+ @validate_call
+ def list_columns(self) -> list[ColumnInfo]:
+ """
+ List all columns of the table with their types.
+
+ Returns:
+ List of ColumnInfo models
+ """
+ logger.info(f"Fetching columns for table {self.table_name}")
+
+ try:
+ # Get schema from the table
+ schema = self.table.schema
+ columns_info: list[ColumnInfo] = []
+
+ for field in schema:
+ columns_info.append(
+ ColumnInfo(
+ name=field.name,
+ type=str(field.type),
+ )
+ )
+
+ logger.info(f"Found {len(columns_info)} columns for table {self.table_name}")
+ return columns_info
+ except Exception as e:
+ logger.error(f"Failed to list columns for table {self.table_name}: {e}")
+ # If schema access fails, return empty list
+ return []
diff --git a/smoosense-py/smoosense/statics/404.html b/smoosense-py/smoosense/statics/404.html
index e1b8086..8707538 100644
--- a/smoosense-py/smoosense/statics/404.html
+++ b/smoosense-py/smoosense/statics/404.html
@@ -1 +1 @@
-404: This page could not be found.
404
This page could not be found.
\ No newline at end of file
+404: This page could not be found.
404
This page could not be found.
\ No newline at end of file
diff --git a/smoosense-py/smoosense/statics/DB.html b/smoosense-py/smoosense/statics/DB.html
new file mode 100644
index 0000000..4b382e1
--- /dev/null
+++ b/smoosense-py/smoosense/statics/DB.html
@@ -0,0 +1 @@
+
Loading...
\ No newline at end of file
diff --git a/smoosense-py/smoosense/statics/DB.txt b/smoosense-py/smoosense/statics/DB.txt
new file mode 100644
index 0000000..39fae38
--- /dev/null
+++ b/smoosense-py/smoosense/statics/DB.txt
@@ -0,0 +1,25 @@
+1:"$Sreact.fragment"
+2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
+3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
+4:I[84468,[],""]
+5:I[29906,[],""]
+6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
+7:I[1377,[],"ClientPageRoot"]
+8:I[4751,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","53","static/chunks/53-c1ea9e1118520cc0.js","990","static/chunks/990-4de2b100275dbb74.js","496","static/chunks/496-167b672ad715b8e4.js","213","static/chunks/213-929cdf2f2ab1ba92.js","73","static/chunks/app/DB/page-aa7000bdd78d40f7.js"],"default"]
+b:I[94484,[],"OutletBoundary"]
+d:I[78892,[],"AsyncMetadataOutlet"]
+f:I[94484,[],"ViewportBoundary"]
+11:I[94484,[],"MetadataBoundary"]
+12:"$Sreact.suspense"
+14:I[29872,[],""]
+:HL["https://cdn.smoosense.ai/_next/static/media/4cf2300e9c8272f7-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
+:HL["https://cdn.smoosense.ai/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
+:HL["https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","style"]
+0:{"P":null,"b":"Nw8PPXXNrI8fWtDB0HVFP","p":"https://cdn.smoosense.ai","c":["","DB"],"i":false,"f":[[["",{"children":["DB",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["DB",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
+9:{}
+a:"$0:f:0:1:2:children:2:children:1:props:children:0:props:params"
+10:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
+c:null
+15:I[25908,[],"IconMark"]
+e:{"metadata":[["$","link","0",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","$L15","1",{}]],"error":null,"digest":"$undefined"}
+13:"$e:metadata"
diff --git a/smoosense-py/smoosense/statics/FolderBrowser.html b/smoosense-py/smoosense/statics/FolderBrowser.html
index a52a9dd..ad73819 100644
--- a/smoosense-py/smoosense/statics/FolderBrowser.html
+++ b/smoosense-py/smoosense/statics/FolderBrowser.html
@@ -1 +1 @@
-
Loading...
\ No newline at end of file
+
Loading...
\ No newline at end of file
diff --git a/smoosense-py/smoosense/statics/FolderBrowser.txt b/smoosense-py/smoosense/statics/FolderBrowser.txt
index f0cc9f1..b76c7b7 100644
--- a/smoosense-py/smoosense/statics/FolderBrowser.txt
+++ b/smoosense-py/smoosense/statics/FolderBrowser.txt
@@ -1,11 +1,11 @@
1:"$Sreact.fragment"
-2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
-3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
+2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
+3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
4:I[84468,[],""]
5:I[29906,[],""]
-6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
+6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
7:I[1377,[],"ClientPageRoot"]
-8:I[71739,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","796","static/chunks/796-c55c1ea3dcac0342.js","286","static/chunks/286-48b8d52998d22749.js","496","static/chunks/496-aecfd3fab8435429.js","213","static/chunks/213-4d0b9f96539b2693.js","960","static/chunks/960-b7b352ba5538d2d3.js","381","static/chunks/app/FolderBrowser/page-bf6e6f84c1420889.js"],"default"]
+8:I[1217,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","53","static/chunks/53-c1ea9e1118520cc0.js","465","static/chunks/465-af44619db04e4529.js","496","static/chunks/496-167b672ad715b8e4.js","213","static/chunks/213-929cdf2f2ab1ba92.js","960","static/chunks/960-d460d3aba64ef202.js","381","static/chunks/app/FolderBrowser/page-bac992e9ba137d9f.js"],"default"]
b:I[94484,[],"OutletBoundary"]
d:I[78892,[],"AsyncMetadataOutlet"]
f:I[94484,[],"ViewportBoundary"]
@@ -14,8 +14,8 @@ f:I[94484,[],"ViewportBoundary"]
14:I[29872,[],""]
:HL["https://cdn.smoosense.ai/_next/static/media/4cf2300e9c8272f7-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["https://cdn.smoosense.ai/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
-:HL["https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","style"]
-0:{"P":null,"b":"EX7F0HqjKDNTM4zsVzu6J","p":"https://cdn.smoosense.ai","c":["","FolderBrowser"],"i":false,"f":[[["",{"children":["FolderBrowser",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["FolderBrowser",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
+:HL["https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","style"]
+0:{"P":null,"b":"Nw8PPXXNrI8fWtDB0HVFP","p":"https://cdn.smoosense.ai","c":["","FolderBrowser"],"i":false,"f":[[["",{"children":["FolderBrowser",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["FolderBrowser",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
9:{}
a:"$0:f:0:1:2:children:2:children:1:props:children:0:props:params"
10:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
diff --git a/smoosense-py/smoosense/statics/LiteTable.html b/smoosense-py/smoosense/statics/LiteTable.html
index 26be424..b590963 100644
--- a/smoosense-py/smoosense/statics/LiteTable.html
+++ b/smoosense-py/smoosense/statics/LiteTable.html
@@ -1 +1 @@
-
1 rows×9 columns
\ No newline at end of file
+
1 rows×9 columns
\ No newline at end of file
diff --git a/smoosense-py/smoosense/statics/LiteTable.txt b/smoosense-py/smoosense/statics/LiteTable.txt
index 7cddf0a..7d3a921 100644
--- a/smoosense-py/smoosense/statics/LiteTable.txt
+++ b/smoosense-py/smoosense/statics/LiteTable.txt
@@ -1,11 +1,11 @@
1:"$Sreact.fragment"
-2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
-3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
+2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
+3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
4:I[84468,[],""]
5:I[29906,[],""]
-6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
+6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
7:I[1377,[],"ClientPageRoot"]
-8:I[94034,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","496","static/chunks/496-aecfd3fab8435429.js","213","static/chunks/213-4d0b9f96539b2693.js","553","static/chunks/app/LiteTable/page-c0f3beb4630db188.js"],"default"]
+8:I[94034,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","496","static/chunks/496-167b672ad715b8e4.js","213","static/chunks/213-929cdf2f2ab1ba92.js","553","static/chunks/app/LiteTable/page-c0f3beb4630db188.js"],"default"]
b:I[94484,[],"OutletBoundary"]
d:I[78892,[],"AsyncMetadataOutlet"]
f:I[94484,[],"ViewportBoundary"]
@@ -14,8 +14,8 @@ f:I[94484,[],"ViewportBoundary"]
14:I[29872,[],""]
:HL["https://cdn.smoosense.ai/_next/static/media/4cf2300e9c8272f7-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["https://cdn.smoosense.ai/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
-:HL["https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","style"]
-0:{"P":null,"b":"EX7F0HqjKDNTM4zsVzu6J","p":"https://cdn.smoosense.ai","c":["","LiteTable"],"i":false,"f":[[["",{"children":["LiteTable",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["LiteTable",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
+:HL["https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","style"]
+0:{"P":null,"b":"Nw8PPXXNrI8fWtDB0HVFP","p":"https://cdn.smoosense.ai","c":["","LiteTable"],"i":false,"f":[[["",{"children":["LiteTable",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["LiteTable",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
9:{}
a:"$0:f:0:1:2:children:2:children:1:props:children:0:props:params"
10:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
diff --git a/smoosense-py/smoosense/statics/MiniTable.html b/smoosense-py/smoosense/statics/MiniTable.html
index 7b8f1c4..ec32e78 100644
--- a/smoosense-py/smoosense/statics/MiniTable.html
+++ b/smoosense-py/smoosense/statics/MiniTable.html
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/smoosense-py/smoosense/statics/MiniTable.txt b/smoosense-py/smoosense/statics/MiniTable.txt
index 5f607bc..456d444 100644
--- a/smoosense-py/smoosense/statics/MiniTable.txt
+++ b/smoosense-py/smoosense/statics/MiniTable.txt
@@ -1,11 +1,11 @@
1:"$Sreact.fragment"
-2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
-3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
+2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
+3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
4:I[84468,[],""]
5:I[29906,[],""]
-6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
+6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
7:I[1377,[],"ClientPageRoot"]
-8:I[75677,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","496","static/chunks/496-aecfd3fab8435429.js","213","static/chunks/213-4d0b9f96539b2693.js","124","static/chunks/app/MiniTable/page-d3f5aa95f232b0e1.js"],"default"]
+8:I[75677,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","496","static/chunks/496-167b672ad715b8e4.js","213","static/chunks/213-929cdf2f2ab1ba92.js","124","static/chunks/app/MiniTable/page-6acadfe28a21ac1e.js"],"default"]
b:I[94484,[],"OutletBoundary"]
d:I[78892,[],"AsyncMetadataOutlet"]
f:I[94484,[],"ViewportBoundary"]
@@ -14,8 +14,8 @@ f:I[94484,[],"ViewportBoundary"]
14:I[29872,[],""]
:HL["https://cdn.smoosense.ai/_next/static/media/4cf2300e9c8272f7-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["https://cdn.smoosense.ai/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
-:HL["https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","style"]
-0:{"P":null,"b":"EX7F0HqjKDNTM4zsVzu6J","p":"https://cdn.smoosense.ai","c":["","MiniTable"],"i":false,"f":[[["",{"children":["MiniTable",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["MiniTable",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
+:HL["https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","style"]
+0:{"P":null,"b":"Nw8PPXXNrI8fWtDB0HVFP","p":"https://cdn.smoosense.ai","c":["","MiniTable"],"i":false,"f":[[["",{"children":["MiniTable",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["MiniTable",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
9:{}
a:"$0:f:0:1:2:children:2:children:1:props:children:0:props:params"
10:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
diff --git a/smoosense-py/smoosense/statics/Table.html b/smoosense-py/smoosense/statics/Table.html
index 72b6f3f..cfa7770 100644
--- a/smoosense-py/smoosense/statics/Table.html
+++ b/smoosense-py/smoosense/statics/Table.html
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/smoosense-py/smoosense/statics/Table.txt b/smoosense-py/smoosense/statics/Table.txt
index e892f0f..4ca8d67 100644
--- a/smoosense-py/smoosense/statics/Table.txt
+++ b/smoosense-py/smoosense/statics/Table.txt
@@ -1,11 +1,11 @@
1:"$Sreact.fragment"
-2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
-3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
+2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
+3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
4:I[84468,[],""]
5:I[29906,[],""]
-6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
+6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
7:I[1377,[],"ClientPageRoot"]
-8:I[39076,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","765","static/chunks/765-f3f38ac19a0db617.js","769","static/chunks/769-38a429028a9a1959.js","796","static/chunks/796-c55c1ea3dcac0342.js","286","static/chunks/286-48b8d52998d22749.js","555","static/chunks/555-a40f653952bd2083.js","496","static/chunks/496-aecfd3fab8435429.js","213","static/chunks/213-4d0b9f96539b2693.js","947","static/chunks/947-3c453905c69bcd58.js","960","static/chunks/960-b7b352ba5538d2d3.js","637","static/chunks/637-1be29adcabd01448.js","785","static/chunks/785-2563770b687318b2.js","597","static/chunks/app/Table/page-5ddb0e4e6261c300.js"],"default"]
+8:I[39076,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","765","static/chunks/765-f3f38ac19a0db617.js","53","static/chunks/53-c1ea9e1118520cc0.js","769","static/chunks/769-38a429028a9a1959.js","465","static/chunks/465-af44619db04e4529.js","555","static/chunks/555-a40f653952bd2083.js","496","static/chunks/496-167b672ad715b8e4.js","213","static/chunks/213-929cdf2f2ab1ba92.js","947","static/chunks/947-e5789eb2fa9c004d.js","960","static/chunks/960-d460d3aba64ef202.js","637","static/chunks/637-a0148480fbb64275.js","785","static/chunks/785-6f7a59e8bb062840.js","597","static/chunks/app/Table/page-d718a570d2548291.js"],"default"]
b:I[94484,[],"OutletBoundary"]
d:I[78892,[],"AsyncMetadataOutlet"]
f:I[94484,[],"ViewportBoundary"]
@@ -14,8 +14,8 @@ f:I[94484,[],"ViewportBoundary"]
14:I[29872,[],""]
:HL["https://cdn.smoosense.ai/_next/static/media/4cf2300e9c8272f7-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["https://cdn.smoosense.ai/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
-:HL["https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","style"]
-0:{"P":null,"b":"EX7F0HqjKDNTM4zsVzu6J","p":"https://cdn.smoosense.ai","c":["","Table"],"i":false,"f":[[["",{"children":["Table",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["Table",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
+:HL["https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","style"]
+0:{"P":null,"b":"Nw8PPXXNrI8fWtDB0HVFP","p":"https://cdn.smoosense.ai","c":["","Table"],"i":false,"f":[[["",{"children":["Table",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["Table",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
9:{}
a:"$0:f:0:1:2:children:2:children:1:props:children:0:props:params"
10:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
diff --git a/smoosense-py/smoosense/statics/example/objectdetection.html b/smoosense-py/smoosense/statics/example/objectdetection.html
index 2b78be9..209c3e4 100644
--- a/smoosense-py/smoosense/statics/example/objectdetection.html
+++ b/smoosense-py/smoosense/statics/example/objectdetection.html
@@ -1,8 +1,8 @@
-
Object Detection Analysis
+
Object Detection Analysis
SmooSense helps evaluate object detection models by visually comparing their predicted bounding boxes against the ground truth.
This provides an intuitive way to audit the model's accuracy and identify specific errors on a given dataset.
This example shows COCO dataset object detection results comparing ground truth bounding boxes with model predictions.
\ No newline at end of file
diff --git a/smoosense-py/smoosense/statics/example/objectdetection.txt b/smoosense-py/smoosense/statics/example/objectdetection.txt
index 6eb031a..b6f5cfb 100644
--- a/smoosense-py/smoosense/statics/example/objectdetection.txt
+++ b/smoosense-py/smoosense/statics/example/objectdetection.txt
@@ -1,11 +1,11 @@
1:"$Sreact.fragment"
-2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
-3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
+2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
+3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
4:I[84468,[],""]
5:I[29906,[],""]
-6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
+6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
7:I[1377,[],"ClientPageRoot"]
-8:I[17347,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","765","static/chunks/765-f3f38ac19a0db617.js","496","static/chunks/496-aecfd3fab8435429.js","213","static/chunks/213-4d0b9f96539b2693.js","947","static/chunks/947-3c453905c69bcd58.js","550","static/chunks/app/example/objectdetection/page-84b15bb87333466b.js"],"default"]
+8:I[17347,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","765","static/chunks/765-f3f38ac19a0db617.js","496","static/chunks/496-167b672ad715b8e4.js","213","static/chunks/213-929cdf2f2ab1ba92.js","947","static/chunks/947-e5789eb2fa9c004d.js","550","static/chunks/app/example/objectdetection/page-84b15bb87333466b.js"],"default"]
b:I[94484,[],"OutletBoundary"]
d:I[78892,[],"AsyncMetadataOutlet"]
f:I[94484,[],"ViewportBoundary"]
@@ -14,8 +14,8 @@ f:I[94484,[],"ViewportBoundary"]
14:I[29872,[],""]
:HL["https://cdn.smoosense.ai/_next/static/media/4cf2300e9c8272f7-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["https://cdn.smoosense.ai/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
-:HL["https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","style"]
-0:{"P":null,"b":"EX7F0HqjKDNTM4zsVzu6J","p":"https://cdn.smoosense.ai","c":["","example","objectdetection"],"i":false,"f":[[["",{"children":["example",{"children":["objectdetection",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["example",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["objectdetection",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
+:HL["https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","style"]
+0:{"P":null,"b":"Nw8PPXXNrI8fWtDB0HVFP","p":"https://cdn.smoosense.ai","c":["","example","objectdetection"],"i":false,"f":[[["",{"children":["example",{"children":["objectdetection",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["example",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["objectdetection",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
9:{}
a:"$0:f:0:1:2:children:2:children:2:children:1:props:children:0:props:params"
10:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
diff --git a/smoosense-py/smoosense/statics/example/text2image.html b/smoosense-py/smoosense/statics/example/text2image.html
index 7a73edc..83082b7 100644
--- a/smoosense-py/smoosense/statics/example/text2image.html
+++ b/smoosense-py/smoosense/statics/example/text2image.html
@@ -1,4 +1,4 @@
-
Text-to-Image Alignment
+
Text-to-Image Alignment
SmooSense allows users to visualize and evaluate text-to-image AI performance.
Its intuitive UI utilizes word scores and visual masks to help pinpoint misalignments between the text and the image, simplifying model analysis.
This example shows human labelers' feedback to text-to-image generation.
@@ -23,4 +23,4 @@
Image mask
Name your column such that it contains image_mask.
Save mask data as a grayscale png file and store url in the column.
Ensure there is a column named image_url containing the corresponding image.
-
Data in this page
Loading...
\ No newline at end of file
+
Data in this page
Loading...
\ No newline at end of file
diff --git a/smoosense-py/smoosense/statics/example/text2image.txt b/smoosense-py/smoosense/statics/example/text2image.txt
index 57e047b..67b8487 100644
--- a/smoosense-py/smoosense/statics/example/text2image.txt
+++ b/smoosense-py/smoosense/statics/example/text2image.txt
@@ -1,11 +1,11 @@
1:"$Sreact.fragment"
-2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
-3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
+2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
+3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
4:I[84468,[],""]
5:I[29906,[],""]
-6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
+6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
7:I[1377,[],"ClientPageRoot"]
-8:I[82915,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","765","static/chunks/765-f3f38ac19a0db617.js","496","static/chunks/496-aecfd3fab8435429.js","213","static/chunks/213-4d0b9f96539b2693.js","947","static/chunks/947-3c453905c69bcd58.js","668","static/chunks/app/example/text2image/page-72e55b31e9338d58.js"],"default"]
+8:I[82915,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","765","static/chunks/765-f3f38ac19a0db617.js","496","static/chunks/496-167b672ad715b8e4.js","213","static/chunks/213-929cdf2f2ab1ba92.js","947","static/chunks/947-e5789eb2fa9c004d.js","668","static/chunks/app/example/text2image/page-72e55b31e9338d58.js"],"default"]
b:I[94484,[],"OutletBoundary"]
d:I[78892,[],"AsyncMetadataOutlet"]
f:I[94484,[],"ViewportBoundary"]
@@ -14,8 +14,8 @@ f:I[94484,[],"ViewportBoundary"]
14:I[29872,[],""]
:HL["https://cdn.smoosense.ai/_next/static/media/4cf2300e9c8272f7-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["https://cdn.smoosense.ai/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
-:HL["https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","style"]
-0:{"P":null,"b":"EX7F0HqjKDNTM4zsVzu6J","p":"https://cdn.smoosense.ai","c":["","example","text2image"],"i":false,"f":[[["",{"children":["example",{"children":["text2image",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["example",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["text2image",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
+:HL["https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","style"]
+0:{"P":null,"b":"Nw8PPXXNrI8fWtDB0HVFP","p":"https://cdn.smoosense.ai","c":["","example","text2image"],"i":false,"f":[[["",{"children":["example",{"children":["text2image",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["example",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["text2image",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
9:{}
a:"$0:f:0:1:2:children:2:children:2:children:1:props:children:0:props:params"
10:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
diff --git a/smoosense-py/smoosense/statics/example/text2video.html b/smoosense-py/smoosense/statics/example/text2video.html
index 680c853..c213950 100644
--- a/smoosense-py/smoosense/statics/example/text2video.html
+++ b/smoosense-py/smoosense/statics/example/text2video.html
@@ -1,4 +1,4 @@
-
Text-to-Video Comparison
+
Text-to-Video Comparison
SmooSense provides a platform to evaluate and compare different text-to-video AI models.
By presenting videos generated from the same prompt side-by-side, it allows for a direct visual assessment of each model's quality and performance.
This example shows how to analyze and compare video generation results from multiple models using the same prompts and reference images.
@@ -10,4 +10,4 @@
Use SmooSense to compare
Hover at a video to play.
Click the switch to turn on/off auto-play for all videos.
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/smoosense-py/smoosense/statics/example/text2video.txt b/smoosense-py/smoosense/statics/example/text2video.txt
index 95634cb..8a580bc 100644
--- a/smoosense-py/smoosense/statics/example/text2video.txt
+++ b/smoosense-py/smoosense/statics/example/text2video.txt
@@ -1,11 +1,11 @@
1:"$Sreact.fragment"
-2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
-3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
+2:I[33537,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ThemeProvider"]
+3:I[68145,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"default"]
4:I[84468,[],""]
5:I[29906,[],""]
-6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-aecfd3fab8435429.js","785","static/chunks/785-2563770b687318b2.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
+6:I[84526,["265","static/chunks/6dc81886-0d3828b166e5e246.js","933","static/chunks/933-3fa934a1373c1443.js","769","static/chunks/769-38a429028a9a1959.js","496","static/chunks/496-167b672ad715b8e4.js","785","static/chunks/785-6f7a59e8bb062840.js","177","static/chunks/app/layout-d6f40cc585abf459.js"],"ToasterProvider"]
7:I[1377,[],"ClientPageRoot"]
-8:I[83578,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","765","static/chunks/765-f3f38ac19a0db617.js","496","static/chunks/496-aecfd3fab8435429.js","213","static/chunks/213-4d0b9f96539b2693.js","947","static/chunks/947-3c453905c69bcd58.js","390","static/chunks/app/example/text2video/page-8e1c302b096dc33f.js"],"default"]
+8:I[83578,["265","static/chunks/6dc81886-0d3828b166e5e246.js","652","static/chunks/4ec49466-f80a37a142905954.js","346","static/chunks/191d07e0-76e9a3be7a7ac399.js","933","static/chunks/933-3fa934a1373c1443.js","916","static/chunks/916-01ab46af81553d20.js","765","static/chunks/765-f3f38ac19a0db617.js","496","static/chunks/496-167b672ad715b8e4.js","213","static/chunks/213-929cdf2f2ab1ba92.js","947","static/chunks/947-e5789eb2fa9c004d.js","390","static/chunks/app/example/text2video/page-8e1c302b096dc33f.js"],"default"]
b:I[94484,[],"OutletBoundary"]
d:I[78892,[],"AsyncMetadataOutlet"]
f:I[94484,[],"ViewportBoundary"]
@@ -14,8 +14,8 @@ f:I[94484,[],"ViewportBoundary"]
14:I[29872,[],""]
:HL["https://cdn.smoosense.ai/_next/static/media/4cf2300e9c8272f7-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["https://cdn.smoosense.ai/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
-:HL["https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","style"]
-0:{"P":null,"b":"EX7F0HqjKDNTM4zsVzu6J","p":"https://cdn.smoosense.ai","c":["","example","text2video"],"i":false,"f":[[["",{"children":["example",{"children":["text2video",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/b6835f0bae32f6b8.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["example",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["text2video",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
+:HL["https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","style"]
+0:{"P":null,"b":"Nw8PPXXNrI8fWtDB0HVFP","p":"https://cdn.smoosense.ai","c":["","example","text2video"],"i":false,"f":[[["",{"children":["example",{"children":["text2video",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"https://cdn.smoosense.ai/_next/static/css/4c99ec665fc98ab2.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_0d22f4 __variable_a7838a antialiased","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","$L3",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","$L6",null,{}]]}]}]]}]]}],{"children":["example",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["text2video",["$","$1","c",{"children":[null,["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L7",null,{"Component":"$8","searchParams":{},"params":{},"promises":["$@9","$@a"]}],null,["$","$Lb",null,{"children":["$Lc",["$","$Ld",null,{"promise":"$@e"}]]}]]}],{},null,false]},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lf",null,{"children":"$L10"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$L11",null,{"children":["$","div",null,{"hidden":true,"children":["$","$12",null,{"fallback":null,"children":"$L13"}]}]}]]}],false]],"m":"$undefined","G":["$14",[]],"s":false,"S":true}
9:{}
a:"$0:f:0:1:2:children:2:children:2:children:1:props:children:0:props:params"
10:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
diff --git a/smoosense-py/smoosense/statics/index.html b/smoosense-py/smoosense/statics/index.html
index c8d0961..a9aae18 100644
--- a/smoosense-py/smoosense/statics/index.html
+++ b/smoosense-py/smoosense/statics/index.html
@@ -1 +1 @@
-