Prevent example duplicates and improve responsive UI in
Browse files
src/components/pipelines/FeatureExtraction.tsx
CHANGED
|
@@ -142,9 +142,21 @@ function FeatureExtraction() {
|
|
| 142 |
const handleAddExample = useCallback(() => {
|
| 143 |
if (!newExampleText.trim()) return
|
| 144 |
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
setNewExampleText('')
|
| 147 |
-
}, [newExampleText, addExample])
|
| 148 |
|
| 149 |
const handleExtractAll = useCallback(() => {
|
| 150 |
const textsToExtract = examples
|
|
@@ -167,8 +179,13 @@ function FeatureExtraction() {
|
|
| 167 |
)
|
| 168 |
|
| 169 |
const handleLoadSampleData = useCallback(() => {
|
| 170 |
-
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
useEffect(() => {
|
| 174 |
if (!activeWorker) return
|
|
@@ -224,14 +241,16 @@ function FeatureExtraction() {
|
|
| 224 |
const busy = status !== 'ready' || isExtracting
|
| 225 |
|
| 226 |
return (
|
| 227 |
-
<div className="flex flex-col h-full max-h-[
|
| 228 |
-
<div className="flex items-center justify-between mb-4">
|
| 229 |
-
<h1 className="text-2xl font-bold">
|
| 230 |
-
|
|
|
|
|
|
|
| 231 |
<button
|
| 232 |
onClick={handleLoadSampleData}
|
| 233 |
disabled={!hasBeenLoaded || isExtracting}
|
| 234 |
-
className="px-3 py-2 bg-purple-100 hover:bg-purple-200 disabled:bg-gray-100 disabled:cursor-not-allowed rounded-lg transition-colors text-sm"
|
| 235 |
title="Load Sample Data"
|
| 236 |
>
|
| 237 |
Load Samples
|
|
@@ -259,28 +278,28 @@ function FeatureExtraction() {
|
|
| 259 |
</div>
|
| 260 |
</div>
|
| 261 |
|
| 262 |
-
<div className="flex flex-col lg:flex-row gap-4 flex-1">
|
| 263 |
{/* Left Panel - Examples */}
|
| 264 |
-
<div className="lg:w-1/2 flex flex-col">
|
| 265 |
{/* Add Example */}
|
| 266 |
<div className="mb-4">
|
| 267 |
<label className="block text-sm font-medium text-gray-700 mb-2">
|
| 268 |
Add Text Examples:
|
| 269 |
</label>
|
| 270 |
-
<div className="flex gap-2">
|
| 271 |
<textarea
|
| 272 |
value={newExampleText}
|
| 273 |
onChange={(e) => setNewExampleText(e.target.value)}
|
| 274 |
onKeyPress={handleKeyPress}
|
| 275 |
placeholder="Enter text to get embeddings... (Press Enter to add)"
|
| 276 |
-
className="flex-1 p-3 border border-gray-300 rounded-lg resize-none focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
| 277 |
rows={2}
|
| 278 |
disabled={!hasBeenLoaded || isExtracting}
|
| 279 |
/>
|
| 280 |
<button
|
| 281 |
onClick={handleAddExample}
|
| 282 |
disabled={!newExampleText.trim() || !hasBeenLoaded}
|
| 283 |
-
className="px-4 py-2 bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded-lg transition-colors"
|
| 284 |
>
|
| 285 |
<Plus className="w-4 h-4" />
|
| 286 |
</button>
|
|
@@ -309,9 +328,9 @@ function FeatureExtraction() {
|
|
| 309 |
)}
|
| 310 |
|
| 311 |
{/* Examples List */}
|
| 312 |
-
<div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg bg-white">
|
| 313 |
-
<div className="p-4">
|
| 314 |
-
<h3 className="text-sm font-medium text-gray-700 mb-3">
|
| 315 |
Examples ({examples.length})
|
| 316 |
</h3>
|
| 317 |
{examples.length === 0 ? (
|
|
@@ -319,11 +338,11 @@ function FeatureExtraction() {
|
|
| 319 |
No examples added yet. Add some text above to get started.
|
| 320 |
</div>
|
| 321 |
) : (
|
| 322 |
-
<div className="space-y-2">
|
| 323 |
{examples.map((example) => (
|
| 324 |
<div
|
| 325 |
key={example.id}
|
| 326 |
-
className={`p-3 border rounded-lg cursor-pointer transition-colors ${
|
| 327 |
selectedExample?.id === example.id
|
| 328 |
? 'border-blue-500 bg-blue-50'
|
| 329 |
: 'border-gray-200 hover:border-gray-300'
|
|
@@ -377,19 +396,19 @@ function FeatureExtraction() {
|
|
| 377 |
</div>
|
| 378 |
|
| 379 |
{/* Right Panel - Visualization and Similarities */}
|
| 380 |
-
<div className="lg:w-1/2 flex flex-col">
|
| 381 |
{showVisualization && (
|
| 382 |
<div className="mb-4">
|
| 383 |
<h3 className="text-sm font-medium text-gray-700 mb-2">
|
| 384 |
2D Visualization
|
| 385 |
</h3>
|
| 386 |
-
<div className="border border-gray-300 rounded-lg bg-white p-4">
|
| 387 |
<svg
|
| 388 |
ref={chartRef}
|
| 389 |
width="100%"
|
| 390 |
-
height="
|
| 391 |
viewBox="0 0 400 300"
|
| 392 |
-
className="border border-gray-100"
|
| 393 |
>
|
| 394 |
{points2D.map((point) => {
|
| 395 |
const isSelected = selectedExample?.id === point.id
|
|
@@ -463,30 +482,30 @@ function FeatureExtraction() {
|
|
| 463 |
</div>
|
| 464 |
)}
|
| 465 |
{points2D.length > 0 && (
|
| 466 |
-
<div className="mt-3 p-3 bg-gray-50 rounded-lg">
|
| 467 |
<h4 className="text-xs font-medium text-gray-700 mb-2">
|
| 468 |
Legend:
|
| 469 |
</h4>
|
| 470 |
-
<div className="flex flex-wrap gap-3 text-xs">
|
| 471 |
<div className="flex items-center gap-1">
|
| 472 |
-
<div className="w-3 h-3 rounded-full bg-blue-500"></div>
|
| 473 |
-
<span>Selected</span>
|
| 474 |
</div>
|
| 475 |
<div className="flex items-center gap-1">
|
| 476 |
-
<div className="w-3 h-3 rounded-full bg-green-500"></div>
|
| 477 |
-
<span>High
|
| 478 |
</div>
|
| 479 |
<div className="flex items-center gap-1">
|
| 480 |
-
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
|
| 481 |
-
<span>
|
| 482 |
</div>
|
| 483 |
<div className="flex items-center gap-1">
|
| 484 |
-
<div className="w-3 h-3 rounded-full bg-red-500"></div>
|
| 485 |
-
<span>Low
|
| 486 |
</div>
|
| 487 |
<div className="flex items-center gap-1">
|
| 488 |
-
<div className="w-3 h-3 rounded-full bg-gray-500"></div>
|
| 489 |
-
<span>Not compared</span>
|
| 490 |
</div>
|
| 491 |
</div>
|
| 492 |
</div>
|
|
@@ -496,9 +515,9 @@ function FeatureExtraction() {
|
|
| 496 |
)}
|
| 497 |
|
| 498 |
{/* Similarity Results */}
|
| 499 |
-
<div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg bg-white">
|
| 500 |
-
<div className="p-4">
|
| 501 |
-
<h3 className="text-sm font-medium text-gray-700 mb-3">
|
| 502 |
Cosine Similarities
|
| 503 |
{selectedExample &&
|
| 504 |
` (vs "${selectedExample.text.substring(0, 30)}...")`}
|
|
@@ -512,7 +531,7 @@ function FeatureExtraction() {
|
|
| 512 |
No other examples with embeddings to compare
|
| 513 |
</div>
|
| 514 |
) : (
|
| 515 |
-
<div className="space-y-2">
|
| 516 |
{similarities.map((sim) => {
|
| 517 |
const example = examples.find(
|
| 518 |
(ex) => ex.id === sim.exampleId
|
|
@@ -530,7 +549,7 @@ function FeatureExtraction() {
|
|
| 530 |
return (
|
| 531 |
<div
|
| 532 |
key={sim.exampleId}
|
| 533 |
-
className="p-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
|
| 534 |
>
|
| 535 |
<div className="flex justify-between items-start">
|
| 536 |
<div className="flex-1 min-w-0">
|
|
|
|
| 142 |
const handleAddExample = useCallback(() => {
|
| 143 |
if (!newExampleText.trim()) return
|
| 144 |
|
| 145 |
+
// Check for duplicates
|
| 146 |
+
const trimmedText = newExampleText.trim()
|
| 147 |
+
const isDuplicate = examples.some(
|
| 148 |
+
(example) => example.text.toLowerCase() === trimmedText.toLowerCase()
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
if (isDuplicate) {
|
| 152 |
+
// Optionally show a toast or alert here
|
| 153 |
+
setNewExampleText('')
|
| 154 |
+
return
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
addExample(trimmedText)
|
| 158 |
setNewExampleText('')
|
| 159 |
+
}, [newExampleText, addExample, examples])
|
| 160 |
|
| 161 |
const handleExtractAll = useCallback(() => {
|
| 162 |
const textsToExtract = examples
|
|
|
|
| 179 |
)
|
| 180 |
|
| 181 |
const handleLoadSampleData = useCallback(() => {
|
| 182 |
+
const existingTexts = new Set(examples.map((ex) => ex.text.toLowerCase()))
|
| 183 |
+
SAMPLE_TEXTS.forEach((text) => {
|
| 184 |
+
if (!existingTexts.has(text.toLowerCase())) {
|
| 185 |
+
addExample(text)
|
| 186 |
+
}
|
| 187 |
+
})
|
| 188 |
+
}, [addExample, examples])
|
| 189 |
|
| 190 |
useEffect(() => {
|
| 191 |
if (!activeWorker) return
|
|
|
|
| 241 |
const busy = status !== 'ready' || isExtracting
|
| 242 |
|
| 243 |
return (
|
| 244 |
+
<div className="flex flex-col h-full max-h-[88vh] w-full p-4">
|
| 245 |
+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4 gap-2">
|
| 246 |
+
<h1 className="text-xl sm:text-2xl font-bold">
|
| 247 |
+
Feature Extraction (Embeddings)
|
| 248 |
+
</h1>
|
| 249 |
+
<div className="flex flex-nowrap gap-2">
|
| 250 |
<button
|
| 251 |
onClick={handleLoadSampleData}
|
| 252 |
disabled={!hasBeenLoaded || isExtracting}
|
| 253 |
+
className="px-3 py-2 bg-purple-100 hover:bg-purple-200 disabled:bg-gray-100 disabled:cursor-not-allowed rounded-lg transition-colors text-xs sm:text-sm"
|
| 254 |
title="Load Sample Data"
|
| 255 |
>
|
| 256 |
Load Samples
|
|
|
|
| 278 |
</div>
|
| 279 |
</div>
|
| 280 |
|
| 281 |
+
<div className="flex flex-col lg:flex-row gap-4 flex-1 min-h-0">
|
| 282 |
{/* Left Panel - Examples */}
|
| 283 |
+
<div className="lg:w-1/2 flex flex-col min-h-0">
|
| 284 |
{/* Add Example */}
|
| 285 |
<div className="mb-4">
|
| 286 |
<label className="block text-sm font-medium text-gray-700 mb-2">
|
| 287 |
Add Text Examples:
|
| 288 |
</label>
|
| 289 |
+
<div className="flex flex-col sm:flex-row gap-2">
|
| 290 |
<textarea
|
| 291 |
value={newExampleText}
|
| 292 |
onChange={(e) => setNewExampleText(e.target.value)}
|
| 293 |
onKeyPress={handleKeyPress}
|
| 294 |
placeholder="Enter text to get embeddings... (Press Enter to add)"
|
| 295 |
+
className="flex-1 p-3 border border-gray-300 rounded-lg resize-none focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed text-sm"
|
| 296 |
rows={2}
|
| 297 |
disabled={!hasBeenLoaded || isExtracting}
|
| 298 |
/>
|
| 299 |
<button
|
| 300 |
onClick={handleAddExample}
|
| 301 |
disabled={!newExampleText.trim() || !hasBeenLoaded}
|
| 302 |
+
className="px-4 py-2 bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded-lg transition-colors self-start sm:self-stretch"
|
| 303 |
>
|
| 304 |
<Plus className="w-4 h-4" />
|
| 305 |
</button>
|
|
|
|
| 328 |
)}
|
| 329 |
|
| 330 |
{/* Examples List */}
|
| 331 |
+
<div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg bg-white min-h-0 max-h-[35vh] sm:max-h-[40vh] lg:max-h-none">
|
| 332 |
+
<div className="p-4 h-full">
|
| 333 |
+
<h3 className="text-sm font-medium text-gray-700 mb-3 sticky top-0 bg-white z-10">
|
| 334 |
Examples ({examples.length})
|
| 335 |
</h3>
|
| 336 |
{examples.length === 0 ? (
|
|
|
|
| 338 |
No examples added yet. Add some text above to get started.
|
| 339 |
</div>
|
| 340 |
) : (
|
| 341 |
+
<div className="space-y-2 overflow-y-auto max-h-[calc(100%-3rem)]">
|
| 342 |
{examples.map((example) => (
|
| 343 |
<div
|
| 344 |
key={example.id}
|
| 345 |
+
className={`p-2 sm:p-3 border rounded-lg cursor-pointer transition-colors ${
|
| 346 |
selectedExample?.id === example.id
|
| 347 |
? 'border-blue-500 bg-blue-50'
|
| 348 |
: 'border-gray-200 hover:border-gray-300'
|
|
|
|
| 396 |
</div>
|
| 397 |
|
| 398 |
{/* Right Panel - Visualization and Similarities */}
|
| 399 |
+
<div className="lg:w-1/2 flex flex-col min-h-0">
|
| 400 |
{showVisualization && (
|
| 401 |
<div className="mb-4">
|
| 402 |
<h3 className="text-sm font-medium text-gray-700 mb-2">
|
| 403 |
2D Visualization
|
| 404 |
</h3>
|
| 405 |
+
<div className="border border-gray-300 rounded-lg bg-white p-2 sm:p-4">
|
| 406 |
<svg
|
| 407 |
ref={chartRef}
|
| 408 |
width="100%"
|
| 409 |
+
height="250"
|
| 410 |
viewBox="0 0 400 300"
|
| 411 |
+
className="border border-gray-100 sm:h-[300px]"
|
| 412 |
>
|
| 413 |
{points2D.map((point) => {
|
| 414 |
const isSelected = selectedExample?.id === point.id
|
|
|
|
| 482 |
</div>
|
| 483 |
)}
|
| 484 |
{points2D.length > 0 && (
|
| 485 |
+
<div className="mt-3 p-2 sm:p-3 bg-gray-50 rounded-lg">
|
| 486 |
<h4 className="text-xs font-medium text-gray-700 mb-2">
|
| 487 |
Legend:
|
| 488 |
</h4>
|
| 489 |
+
<div className="flex flex-wrap gap-2 sm:gap-3 text-xs">
|
| 490 |
<div className="flex items-center gap-1">
|
| 491 |
+
<div className="w-2 h-2 sm:w-3 sm:h-3 rounded-full bg-blue-500"></div>
|
| 492 |
+
<span className="text-xs">Selected</span>
|
| 493 |
</div>
|
| 494 |
<div className="flex items-center gap-1">
|
| 495 |
+
<div className="w-2 h-2 sm:w-3 sm:h-3 rounded-full bg-green-500"></div>
|
| 496 |
+
<span className="text-xs">High (>80%)</span>
|
| 497 |
</div>
|
| 498 |
<div className="flex items-center gap-1">
|
| 499 |
+
<div className="w-2 h-2 sm:w-3 sm:h-3 rounded-full bg-yellow-500"></div>
|
| 500 |
+
<span className="text-xs">Med (50-80%)</span>
|
| 501 |
</div>
|
| 502 |
<div className="flex items-center gap-1">
|
| 503 |
+
<div className="w-2 h-2 sm:w-3 sm:h-3 rounded-full bg-red-500"></div>
|
| 504 |
+
<span className="text-xs">Low (<50%)</span>
|
| 505 |
</div>
|
| 506 |
<div className="flex items-center gap-1">
|
| 507 |
+
<div className="w-2 h-2 sm:w-3 sm:h-3 rounded-full bg-gray-500"></div>
|
| 508 |
+
<span className="text-xs">Not compared</span>
|
| 509 |
</div>
|
| 510 |
</div>
|
| 511 |
</div>
|
|
|
|
| 515 |
)}
|
| 516 |
|
| 517 |
{/* Similarity Results */}
|
| 518 |
+
<div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg bg-white min-h-0 max-h-[35vh] sm:max-h-[40vh] lg:max-h-none">
|
| 519 |
+
<div className="p-4 h-full">
|
| 520 |
+
<h3 className="text-sm font-medium text-gray-700 mb-3 sticky top-0 bg-white z-10">
|
| 521 |
Cosine Similarities
|
| 522 |
{selectedExample &&
|
| 523 |
` (vs "${selectedExample.text.substring(0, 30)}...")`}
|
|
|
|
| 531 |
No other examples with embeddings to compare
|
| 532 |
</div>
|
| 533 |
) : (
|
| 534 |
+
<div className="space-y-2 overflow-y-auto max-h-[calc(100%-3rem)]">
|
| 535 |
{similarities.map((sim) => {
|
| 536 |
const example = examples.find(
|
| 537 |
(ex) => ex.id === sim.exampleId
|
|
|
|
| 549 |
return (
|
| 550 |
<div
|
| 551 |
key={sim.exampleId}
|
| 552 |
+
className="p-2 sm:p-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
|
| 553 |
>
|
| 554 |
<div className="flex justify-between items-start">
|
| 555 |
<div className="flex-1 min-w-0">
|