Spaces:
Running
Running
File size: 6,736 Bytes
7ac3a44 ba37872 d89f8be ba37872 5a4ba70 ba37872 d89f8be ba37872 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | import React from 'react';
import { useState } from "react";
// Color palette β each chunk gets a rotating color
const CHUNK_COLORS = [
{ bg: "bg-blue-100", border: "border-blue-400", text: "text-blue-800", ring: "ring-blue-300" },
{ bg: "bg-purple-100",border: "border-purple-400", text: "text-purple-800",ring: "ring-purple-300"},
{ bg: "bg-green-100", border: "border-green-400", text: "text-green-800", ring: "ring-green-300" },
{ bg: "bg-pink-100", border: "border-pink-400", text: "text-pink-800", ring: "ring-pink-300" },
{ bg: "bg-yellow-100",border: "border-yellow-400", text: "text-yellow-800",ring: "ring-yellow-300"},
];
export default function ChunkVisualizer({ chunks = [], loading, error }) {
const [selectedId, setSelectedId] = useState(null);
// handle different response shapes from backend
const chunkList = Array.isArray(chunks) ? chunks : chunks?.chunks || chunks?.data || [];
const selected = chunkList.find((c) => c.id === selectedId);
if (loading) {
return (
<div className="flex items-center justify-center h-64 text-gray-400">
<div className="text-center space-y-2">
<div className="h-8 w-8 animate-spin rounded-full border-2 border-gray-200 border-t-blue-500 mx-auto" />
<p className="text-sm">Generating chunksβ¦</p>
</div>
</div>
);
}
if (error) {
return (
<div className="flex items-center justify-center h-64 rounded-xl border-2 border-dashed border-red-200 bg-red-50 text-red-600">
<div className="text-center space-y-2">
<p className="text-sm font-medium">β οΈ Error loading chunks</p>
<p className="text-xs text-red-500">{error}</p>
</div>
</div>
);
}
if (!chunkList.length) {
return (
<div className="flex items-center justify-center h-64 rounded-xl border-2 border-dashed border-gray-200 text-gray-400">
<p className="text-sm">Upload a document and configure chunking to see the preview.</p>
</div>
);
}
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 h-full">
{/* ββ Left panel: chunk blocks ββββββββββββββββββββββββββββ */}
<div className="rounded-xl border border-gray-200 bg-white overflow-y-auto max-h-[600px] p-4 space-y-2">
<p className="text-xs text-gray-400 font-medium uppercase tracking-wide mb-3">
{chunks.length} chunks β click to inspect
</p>
{chunks.map((chunk, i) => {
const color = CHUNK_COLORS[i % CHUNK_COLORS.length];
const isSelected = chunk.id === selectedId;
const hasOverlapPrev = !!chunk.overlap_prev;
const hasOverlapNext = !!chunk.overlap_next;
return (
<div key={chunk.id} onClick={() => setSelectedId(isSelected ? null : chunk.id)}>
{/* Overlap indicator β top */}
{hasOverlapPrev && (
<div className="text-[10px] text-orange-500 font-medium pl-2 -mb-1">
β΅ {chunk.overlap_prev}ch overlap with previous
</div>
)}
{/* Chunk block */}
<div className={`rounded-lg border-l-4 px-3 py-2 cursor-pointer transition-all
${color.bg} ${color.border}
${isSelected ? `ring-2 ${color.ring} shadow-sm` : "hover:shadow-sm"}
`}>
<div className="flex justify-between items-start mb-1">
<span className={`text-[10px] font-mono font-bold ${color.text}`}>
#{chunk.sequence_num + 1} Β· {chunk.id.slice(0, 6)}
</span>
<span className="text-[10px] text-gray-400">
{chunk.start_char}β{chunk.end_char}
</span>
</div>
<p className="text-xs text-gray-700 leading-relaxed line-clamp-3">
{chunk.text}
</p>
</div>
{/* Overlap indicator β bottom */}
{hasOverlapNext && (
<div className="text-[10px] text-orange-500 font-medium pl-2 -mt-1">
{chunk.overlap_next}ch overlap with next βΆ
</div>
)}
</div>
);
})}
</div>
{/* ββ Right panel: selected chunk detail βββββββββββββββββ */}
<div className="rounded-xl border border-gray-200 bg-white p-4">
{selected ? (
<ChunkDetail chunk={selected} index={chunks.indexOf(selected)} color={CHUNK_COLORS[chunks.indexOf(selected) % CHUNK_COLORS.length]} />
) : (
<div className="flex items-center justify-center h-full text-gray-300">
<div className="text-center">
<div className="text-4xl mb-2">π</div>
<p className="text-sm">Select a chunk to see details</p>
</div>
</div>
)}
</div>
</div>
);
}
function ChunkDetail({ chunk, index, color }) {
const charCount = chunk.end_char - chunk.start_char;
const approxTokens = Math.round(charCount / 4); // rough approximation
return (
<div className="space-y-4">
<div className="flex items-center gap-2">
<div className={`w-3 h-3 rounded-full border-2 ${color.border} ${color.bg}`} />
<h3 className="font-semibold text-gray-800">Chunk #{index + 1}</h3>
</div>
{/* Metadata grid */}
<div className="grid grid-cols-2 gap-2">
{[
{ label: "Chunk ID", value: chunk.id },
{ label: "Position", value: `${chunk.start_char} β ${chunk.end_char}` },
{ label: "Characters", value: charCount },
{ label: "Approx Tokens", value: `~${approxTokens}` },
chunk.overlap_prev && { label: "Overlap Prev", value: `${chunk.overlap_prev}ch` },
chunk.overlap_next && { label: "Overlap Next", value: `${chunk.overlap_next}ch` },
].filter(Boolean).map(({ label, value }) => (
<div key={label} className="rounded-lg bg-gray-50 p-2">
<p className="text-[10px] text-gray-400 uppercase tracking-wide">{label}</p>
<p className="text-xs font-mono text-gray-700 mt-0.5 truncate">{value}</p>
</div>
))}
</div>
{/* Full text */}
<div>
<p className="text-xs text-gray-400 uppercase tracking-wide mb-1">Full Text</p>
<div className="rounded-lg bg-gray-50 border border-gray-200 p-3 max-h-52 overflow-y-auto">
<p className="text-sm text-gray-700 leading-relaxed whitespace-pre-wrap">{chunk.text}</p>
</div>
</div>
</div>
);
}
|