Skip to content

Commit

Permalink
Merge pull request #100 from bespokelabsai/dev
Browse files Browse the repository at this point in the history
0.1.8
  • Loading branch information
vutrung96 authored Nov 14, 2024
2 parents a6536c9 + 4de0c76 commit c843106
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 113 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
__pycache__
.vscode

/src/bespokelabs/curator/viewer/static
# Static / Build
/src/bespokelabs/curator/viewer/static
/dist
12 changes: 12 additions & 0 deletions bespoke-dataset-viewer/app/api/responses/[runHash]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ export async function GET(
} else {
// Online streaming mode
const responsesPath = join(runDir, 'responses_0.jsonl')

// Check if the file exists before trying to read it
if (!existsSync(responsesPath)) {
return NextResponse.json({
data: [],
totalLines: 0,
isBatchMode: false,
processedFiles: null,
message: "No responses file found yet"
})
}

const content = await fs.readFile(responsesPath, 'utf-8')
const lines = content.split('\n').filter(line => line.trim() !== '')

Expand Down
109 changes: 56 additions & 53 deletions bespoke-dataset-viewer/components/dataset-viewer/DatasetViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { cn, getColumnValue } from "@/lib/utils"
import { DataItem } from "@/types/dataset"
import { Column } from "@/types/table"
import { AnimatePresence } from "framer-motion"
import { Loader2 } from "lucide-react"
import { useCallback, useEffect, useMemo, useState } from "react"
import { FileText, Loader2, RefreshCcw } from "lucide-react"
import { useCallback, useEffect, useState } from "react"
import { DetailsSidebar } from "./DetailsSidebar"
import { DistributionChart } from "./DistributionChart"

Expand All @@ -31,11 +31,34 @@ interface DatasetViewerProps {
batchMode: boolean
}

function NoDataView({ batchMode, isPolling }: { batchMode: boolean, isPolling: boolean }) {
return (
<div className="flex flex-col items-center justify-center h-[60vh] p-8">
<div className="bg-muted/30 rounded-full p-6 mb-6">
<FileText className="w-12 h-12 text-muted-foreground" />
</div>
<h3 className="text-xl font-semibold mb-2">No responses available yet</h3>
<p className="text-muted-foreground text-center max-w-md mb-4">
{batchMode
? "Waiting for the first batch to complete. Once finished, responses will appear here in batches."
: "Responses will appear here as they are generated. The table will update automatically. Check if the curator is still running."}
</p>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
{isPolling ? (
<>
<RefreshCcw className="w-4 h-4 animate-spin" />
<span>Polling for new responses...</span>
</>
) : (
<span>Polling is paused</span>
)}
</div>
</div>
)
}

export function DatasetViewer({ runHash, batchMode }: DatasetViewerProps) {
const [data, setData] = useState<DataItem[]>([])
const [sortColumn] = useState<string | null>(null)
const [sortDirection] = useState<"asc" | "desc">("asc")
const [filters] = useState<Record<string, string>>({})
const [theme, setTheme] = useState<"light" | "dark">("light")
const [mounted, setMounted] = useState(false)
const [selectedDistribution, setSelectedDistribution] = useState<string | null>("total_tokens")
Expand Down Expand Up @@ -65,30 +88,6 @@ export function DatasetViewer({ runHash, batchMode }: DatasetViewerProps) {
localStorage.setItem('theme', theme)
}, [theme, mounted])

const filteredData = useMemo(() => {
const dataArray = Array.isArray(data) ? data : []

return dataArray.filter((item) => {
return Object.entries(filters).every(([column, filterValue]) => {
if (!filterValue) return true
const cellValue = getColumnValue(item, column)
return cellValue.toLowerCase().includes(filterValue.toLowerCase())
})
})
}, [data, filters])

const sortedData = useMemo(() => {
if (!sortColumn) return filteredData

return [...filteredData].sort((a, b) => {
const aValue = getColumnValue(a, sortColumn)
const bValue = getColumnValue(b, sortColumn)

const comparison = aValue.localeCompare(bValue)
return sortDirection === "asc" ? comparison : -comparison
})
}, [filteredData, sortColumn, sortDirection])

const fetchNewResponses = useCallback(async () => {
if (!runHash) return

Expand Down Expand Up @@ -224,29 +223,31 @@ export function DatasetViewer({ runHash, batchMode }: DatasetViewerProps) {
<h2 className="text-2xl font-semibold text-foreground">Dataset Details</h2>
<p className="text-sm text-muted-foreground">View and analyze your dataset responses</p>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
{selectedDistribution
? selectedDistribution.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')
: 'Select Metric'}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setSelectedDistribution(null)}>
None
</DropdownMenuItem>
{["total_tokens", "prompt_tokens", "completion_tokens"].map((column) => (
<DropdownMenuItem key={column} onClick={() => setSelectedDistribution(column)}>
{column === "total_tokens" ? "Total Tokens" :
column === "prompt_tokens" ? "Prompt Tokens" :
"Completion Tokens"}
{data.length > 0 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
{selectedDistribution
? selectedDistribution.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')
: 'Select Metric'}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setSelectedDistribution(null)}>
None
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{["total_tokens", "prompt_tokens", "completion_tokens"].map((column) => (
<DropdownMenuItem key={column} onClick={() => setSelectedDistribution(column)}>
{column === "total_tokens" ? "Total Tokens" :
column === "prompt_tokens" ? "Prompt Tokens" :
"Completion Tokens"}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
</div>

{isInitialLoad ? (
Expand All @@ -256,13 +257,15 @@ export function DatasetViewer({ runHash, batchMode }: DatasetViewerProps) {
<p className="text-muted-foreground">Loading dataset...</p>
</div>
</div>
) : data.length === 0 ? (
<NoDataView batchMode={batchMode} isPolling={isPolling} />
) : (
<>
<div className="mb-8 space-y-4">
{selectedDistribution && (
<div className="rounded-lg border bg-card p-4">
<DistributionChart
data={sortedData}
data={data}
column={selectedDistribution}
/>
</div>
Expand All @@ -273,7 +276,7 @@ export function DatasetViewer({ runHash, batchMode }: DatasetViewerProps) {
<AnimatePresence>
<SortableTable
columns={COLUMNS}
data={sortedData}
data={data}
getRowKey={(item) => item.raw_response.id}
getCellContent={(item, columnKey) => getColumnValue(item, columnKey)}
onRowClick={(item) => setSelectedItem(item)}
Expand Down
23 changes: 17 additions & 6 deletions bespoke-dataset-viewer/components/ui/sortable-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { SortableTableProps, SortDirection } from "@/types/table"
import { Tooltip } from "@/components/ui/tooltip"
import { TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { motion } from "framer-motion"
import { cn } from "@/lib/utils"
import { cn, isNumeric } from "@/lib/utils"
import {
Pagination,
PaginationContent,
Expand Down Expand Up @@ -85,14 +85,25 @@ export function SortableTable({
}
})

// Apply sorting
// Apply sorting with numeric support
if (sortColumn) {
result.sort((a, b) => {
const aValue = String(getCellContent(a, sortColumn))
const bValue = String(getCellContent(b, sortColumn))
const aValue = getCellContent(a, sortColumn)
const bValue = getCellContent(b, sortColumn)

// Handle numeric sorting
if (isNumeric(aValue) && isNumeric(bValue)) {
const aNum = parseFloat(String(aValue))
const bNum = parseFloat(String(bValue))
return sortDirection === "asc"
? aNum - bNum
: bNum - aNum
}

// Fall back to string sorting
return sortDirection === "asc"
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue)
? String(aValue).localeCompare(String(bValue))
: String(bValue).localeCompare(String(aValue))
})
}

Expand Down
5 changes: 5 additions & 0 deletions bespoke-dataset-viewer/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
import { DataItem } from "../types/dataset"

export function isNumeric(value: unknown): boolean {
if (value === null || value === undefined) return false;
return Number.isFinite(Number(value));
}

export const getColumnValue = (item: DataItem, column: string): string => {
if (!item) return "N/A"

Expand Down
33 changes: 24 additions & 9 deletions build_pkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@ def npm_install():
run_command("npm install", cwd="bespoke-dataset-viewer")


def copy_with_excludes(source, target, excludes=None):
"""Copy files/directories while excluding specified paths"""
if excludes is None:
excludes = []

if source.is_file():
shutil.copy2(source, target)
print(f"Copied file {source} to {target}")
elif source.is_dir():
if target.exists():
shutil.rmtree(target)

def ignore_patterns(path, names):
return [n for n in names if str(Path(path) / n) in excludes]

shutil.copytree(source, target, ignore=ignore_patterns)
print(f"Copied directory {source} to {target}")


def nextjs_build():
print("Running Next.js build")
run_command("npm run build", cwd="bespoke-dataset-viewer")
Expand All @@ -28,7 +47,7 @@ def nextjs_build():
shutil.rmtree(target_base)
target_base.mkdir(parents=True, exist_ok=True)

# Copy only the necessary files, excluding node_modules
# Files and directories to copy
files_to_copy = [
".next",
"app",
Expand All @@ -46,19 +65,15 @@ def nextjs_build():
"components.json",
]

# Paths to exclude
exclude_paths = [str(source_base / ".next" / "cache")]

for item in files_to_copy:
source = source_base / item
target = target_base / item

if source.exists():
if source.is_file():
shutil.copy2(source, target)
print(f"Copied file {source} to {target}")
elif source.is_dir():
if target.exists():
shutil.rmtree(target)
shutil.copytree(source, target)
print(f"Copied directory {source} to {target}")
copy_with_excludes(source, target, exclude_paths)
else:
print(f"Warning: {source} not found")

Expand Down
Loading

0 comments on commit c843106

Please sign in to comment.