vector-inspector 0.2.3__py3-none-any.whl → 0.2.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import csv
5
5
  from typing import Dict, Any, List, Optional
6
6
  from pathlib import Path
7
7
  import pandas as pd
8
+ import numpy as np
8
9
 
9
10
 
10
11
  class ImportExportService:
@@ -36,9 +37,13 @@ class ImportExportService:
36
37
  "document": documents[i] if i < len(documents) else None,
37
38
  "metadata": metadatas[i] if i < len(metadatas) else {},
38
39
  }
39
- # Optionally include embeddings
40
- if embeddings and i < len(embeddings):
41
- item["embedding"] = embeddings[i]
40
+ # Optionally include embeddings (convert numpy arrays to lists)
41
+ if len(embeddings) > 0 and i < len(embeddings):
42
+ embedding = embeddings[i]
43
+ # Convert numpy array to list if needed
44
+ if isinstance(embedding, np.ndarray):
45
+ embedding = embedding.tolist()
46
+ item["embedding"] = embedding
42
47
  export_data.append(item)
43
48
 
44
49
  # Write to file
@@ -82,9 +87,13 @@ class ImportExportService:
82
87
  for key, value in metadatas[i].items():
83
88
  row[f"metadata_{key}"] = value
84
89
 
85
- # Optionally add embeddings
86
- if include_embeddings and embeddings and i < len(embeddings):
87
- row["embedding"] = json.dumps(embeddings[i])
90
+ # Optionally add embeddings (convert numpy arrays to lists)
91
+ if include_embeddings and len(embeddings) > 0 and i < len(embeddings):
92
+ embedding = embeddings[i]
93
+ # Convert numpy array to list if needed
94
+ if isinstance(embedding, np.ndarray):
95
+ embedding = embedding.tolist()
96
+ row["embedding"] = json.dumps(embedding)
88
97
 
89
98
  rows.append(row)
90
99
 
@@ -118,17 +127,24 @@ class ImportExportService:
118
127
  # Prepare data for DataFrame
119
128
  df_data = {
120
129
  "id": ids,
121
- "document": documents if documents else [None] * len(ids),
130
+ "document": documents if len(documents) > 0 else [None] * len(ids),
122
131
  }
123
132
 
124
133
  # Add metadata fields as separate columns
125
- if metadatas and metadatas[0]:
134
+ if len(metadatas) > 0 and metadatas[0]:
126
135
  for key in metadatas[0].keys():
127
136
  df_data[f"metadata_{key}"] = [m.get(key) if m else None for m in metadatas]
128
137
 
129
- # Add embeddings as a column
130
- if embeddings:
131
- df_data["embedding"] = embeddings
138
+ # Add embeddings as a column (convert numpy arrays to lists for compatibility)
139
+ if len(embeddings) > 0:
140
+ # Convert numpy arrays to lists if needed
141
+ embedding_list = []
142
+ for emb in embeddings:
143
+ if isinstance(emb, np.ndarray):
144
+ embedding_list.append(emb.tolist())
145
+ else:
146
+ embedding_list.append(emb)
147
+ df_data["embedding"] = embedding_list
132
148
 
133
149
  # Create DataFrame and save
134
150
  df = pd.DataFrame(df_data)
@@ -173,7 +189,7 @@ class ImportExportService:
173
189
  "metadatas": metadatas,
174
190
  }
175
191
 
176
- if embeddings:
192
+ if len(embeddings) > 0:
177
193
  result["embeddings"] = embeddings
178
194
 
179
195
  return result
@@ -227,7 +243,7 @@ class ImportExportService:
227
243
  "metadatas": metadatas,
228
244
  }
229
245
 
230
- if embeddings:
246
+ if len(embeddings) > 0:
231
247
  result["embeddings"] = embeddings
232
248
 
233
249
  return result
@@ -277,7 +293,7 @@ class ImportExportService:
277
293
  "metadatas": metadatas,
278
294
  }
279
295
 
280
- if embeddings:
296
+ if len(embeddings) > 0:
281
297
  result["embeddings"] = embeddings
282
298
 
283
299
  return result
@@ -15,6 +15,7 @@ from vector_inspector.ui.components.loading_dialog import LoadingDialog
15
15
  from vector_inspector.ui.components.filter_builder import FilterBuilder
16
16
  from vector_inspector.services.import_export_service import ImportExportService
17
17
  from vector_inspector.services.filter_service import apply_client_side_filters
18
+ from vector_inspector.services.settings_service import SettingsService
18
19
  from PySide6.QtWidgets import QApplication
19
20
 
20
21
 
@@ -29,6 +30,7 @@ class MetadataView(QWidget):
29
30
  self.page_size = 50
30
31
  self.current_page = 0
31
32
  self.loading_dialog = LoadingDialog("Loading data...", self)
33
+ self.settings_service = SettingsService()
32
34
 
33
35
  # Debounce timer for filter changes
34
36
  self.filter_reload_timer = QTimer()
@@ -426,33 +428,41 @@ class MetadataView(QWidget):
426
428
  QMessageBox.warning(self, "Error", "Failed to update item.")
427
429
 
428
430
  def _export_data(self, format_type: str):
429
- """Export collection data to file."""
431
+ """Export current table data to file (visible rows or selected rows)."""
430
432
  if not self.current_collection:
431
433
  QMessageBox.warning(self, "No Collection", "Please select a collection first.")
432
434
  return
433
-
434
- # Get all data (not just current page)
435
- self.loading_dialog.show_loading("Exporting data...")
436
- QApplication.processEvents()
437
435
 
438
- try:
439
- # Get filter if active
440
- where_filter = None
441
- if self.filter_group.isChecked() and self.filter_builder.has_filters():
442
- where_filter = self.filter_builder.get_filter()
443
-
444
- # Fetch all data
445
- all_data = self.connection.get_all_items(
446
- self.current_collection,
447
- where=where_filter
448
- )
449
- finally:
450
- self.loading_dialog.hide_loading()
451
-
452
- if not all_data or not all_data.get("ids"):
436
+ if not self.current_data or not self.current_data.get("ids"):
453
437
  QMessageBox.warning(self, "No Data", "No data to export.")
454
438
  return
455
439
 
440
+ # Check if there are selected rows
441
+ selected_rows = self.table.selectionModel().selectedRows()
442
+
443
+ if selected_rows:
444
+ # Export only selected rows
445
+ export_data = {
446
+ "ids": [],
447
+ "documents": [],
448
+ "metadatas": [],
449
+ "embeddings": []
450
+ }
451
+
452
+ for index in selected_rows:
453
+ row = index.row()
454
+ if row < len(self.current_data["ids"]):
455
+ export_data["ids"].append(self.current_data["ids"][row])
456
+ if "documents" in self.current_data and row < len(self.current_data["documents"]):
457
+ export_data["documents"].append(self.current_data["documents"][row])
458
+ if "metadatas" in self.current_data and row < len(self.current_data["metadatas"]):
459
+ export_data["metadatas"].append(self.current_data["metadatas"][row])
460
+ if "embeddings" in self.current_data and row < len(self.current_data["embeddings"]):
461
+ export_data["embeddings"].append(self.current_data["embeddings"][row])
462
+ else:
463
+ # Export all visible data from current table
464
+ export_data = self.current_data
465
+
456
466
  # Select file path
457
467
  file_filters = {
458
468
  "json": "JSON Files (*.json)",
@@ -460,10 +470,14 @@ class MetadataView(QWidget):
460
470
  "parquet": "Parquet Files (*.parquet)"
461
471
  }
462
472
 
473
+ # Get last used directory from settings
474
+ last_dir = self.settings_service.get("last_import_export_dir", "")
475
+ default_path = f"{last_dir}/{self.current_collection}.{format_type}" if last_dir else f"{self.current_collection}.{format_type}"
476
+
463
477
  file_path, _ = QFileDialog.getSaveFileName(
464
478
  self,
465
479
  f"Export to {format_type.upper()}",
466
- f"{self.current_collection}.{format_type}",
480
+ default_path,
467
481
  file_filters[format_type]
468
482
  )
469
483
 
@@ -475,17 +489,21 @@ class MetadataView(QWidget):
475
489
  success = False
476
490
 
477
491
  if format_type == "json":
478
- success = service.export_to_json(all_data, file_path)
492
+ success = service.export_to_json(export_data, file_path)
479
493
  elif format_type == "csv":
480
- success = service.export_to_csv(all_data, file_path)
494
+ success = service.export_to_csv(export_data, file_path)
481
495
  elif format_type == "parquet":
482
- success = service.export_to_parquet(all_data, file_path)
496
+ success = service.export_to_parquet(export_data, file_path)
483
497
 
484
498
  if success:
499
+ # Save the directory for next time
500
+ from pathlib import Path
501
+ self.settings_service.set("last_import_export_dir", str(Path(file_path).parent))
502
+
485
503
  QMessageBox.information(
486
504
  self,
487
505
  "Export Successful",
488
- f"Exported {len(all_data['ids'])} items to {file_path}"
506
+ f"Exported {len(export_data['ids'])} items to {file_path}"
489
507
  )
490
508
  else:
491
509
  QMessageBox.warning(self, "Export Failed", "Failed to export data.")
@@ -503,10 +521,13 @@ class MetadataView(QWidget):
503
521
  "parquet": "Parquet Files (*.parquet)"
504
522
  }
505
523
 
524
+ # Get last used directory from settings
525
+ last_dir = self.settings_service.get("last_import_export_dir", "")
526
+
506
527
  file_path, _ = QFileDialog.getOpenFileName(
507
528
  self,
508
529
  f"Import from {format_type.upper()}",
509
- "",
530
+ last_dir,
510
531
  file_filters[format_type]
511
532
  )
512
533
 
@@ -531,6 +552,54 @@ class MetadataView(QWidget):
531
552
  if not imported_data:
532
553
  QMessageBox.warning(self, "Import Failed", "Failed to parse import file.")
533
554
  return
555
+
556
+ # Handle Qdrant-specific requirements (similar to backup/restore)
557
+ from vector_inspector.core.connections.qdrant_connection import QdrantConnection
558
+ if isinstance(self.connection, QdrantConnection):
559
+ # Check if embeddings are missing and need to be generated
560
+ if not imported_data.get("embeddings"):
561
+ self.loading_dialog.setLabelText("Generating embeddings for Qdrant...")
562
+ QApplication.processEvents()
563
+ try:
564
+ from sentence_transformers import SentenceTransformer
565
+ model = SentenceTransformer("all-MiniLM-L6-v2")
566
+ documents = imported_data.get("documents", [])
567
+ imported_data["embeddings"] = model.encode(documents, show_progress_bar=False).tolist()
568
+ except Exception as e:
569
+ QMessageBox.warning(self, "Import Failed",
570
+ f"Qdrant requires embeddings. Failed to generate: {e}")
571
+ return
572
+
573
+ # Convert IDs to Qdrant-compatible format (integers or UUIDs)
574
+ # Store original IDs in metadata
575
+ original_ids = imported_data.get("ids", [])
576
+ qdrant_ids = []
577
+ metadatas = imported_data.get("metadatas", [])
578
+
579
+ for i, orig_id in enumerate(original_ids):
580
+ # Try to convert to integer, otherwise use index
581
+ try:
582
+ # If it's like "doc_123", extract the number
583
+ if isinstance(orig_id, str) and "_" in orig_id:
584
+ qdrant_id = int(orig_id.split("_")[-1])
585
+ else:
586
+ qdrant_id = int(orig_id)
587
+ except (ValueError, AttributeError):
588
+ # Use index as ID if can't convert
589
+ qdrant_id = i
590
+
591
+ qdrant_ids.append(qdrant_id)
592
+
593
+ # Store original ID in metadata
594
+ if i < len(metadatas):
595
+ if metadatas[i] is None:
596
+ metadatas[i] = {}
597
+ metadatas[i]["original_id"] = orig_id
598
+ else:
599
+ metadatas.append({"original_id": orig_id})
600
+
601
+ imported_data["ids"] = qdrant_ids
602
+ imported_data["metadatas"] = metadatas
534
603
 
535
604
  # Add items to collection
536
605
  success = self.connection.add_items(
@@ -544,6 +613,10 @@ class MetadataView(QWidget):
544
613
  self.loading_dialog.hide_loading()
545
614
 
546
615
  if success:
616
+ # Save the directory for next time
617
+ from pathlib import Path
618
+ self.settings_service.set("last_import_export_dir", str(Path(file_path).parent))
619
+
547
620
  QMessageBox.information(
548
621
  self,
549
622
  "Import Successful",
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vector-inspector
3
- Version: 0.2.3
3
+ Version: 0.2.4.3
4
4
  Summary: A comprehensive desktop application for visualizing, querying, and managing vector database data
5
5
  Author-Email: Anthony Dawson <anthonypdawson+github@gmail.com>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://vector-inspector.divinedevops.com
8
- Project-URL: Source, https://github.com/anthony-dawson/vector-inspector
9
- Project-URL: Issues, https://github.com/anthony-dawson/vector-inspector/issues
10
- Project-URL: Documentation, https://github.com/anthony-dawson/vector-inspector#readme
8
+ Project-URL: Source, https://github.com/anthonypdawson/vector-inspector
9
+ Project-URL: Issues, https://github.com/anthonypdawson/vector-inspector/issues
10
+ Project-URL: Documentation, https://github.com/anthonypdawson/vector-inspector#readme
11
11
  Requires-Python: ==3.12.*
12
12
  Requires-Dist: chromadb>=0.4.22
13
13
  Requires-Dist: qdrant-client>=1.7.0
@@ -1,6 +1,6 @@
1
- vector_inspector-0.2.3.dist-info/METADATA,sha256=l_D2Z-QyHHaxIhHUWO-iIocIlkLWbB8IiYytYU86l_k,9384
2
- vector_inspector-0.2.3.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
- vector_inspector-0.2.3.dist-info/entry_points.txt,sha256=u96envMI2NFImZUJDFutiiWl7ZoHrrev9joAgtyvTxo,80
1
+ vector_inspector-0.2.4.3.dist-info/METADATA,sha256=dA5b-TGlPGSnIEqKCXkz2UbmzL1g9U0v-DUEBeycl9I,9386
2
+ vector_inspector-0.2.4.3.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
+ vector_inspector-0.2.4.3.dist-info/entry_points.txt,sha256=u96envMI2NFImZUJDFutiiWl7ZoHrrev9joAgtyvTxo,80
4
4
  vector_inspector/__init__.py,sha256=Q8XbXn98o0eliQWPePhy-aGUz2KNnVg7bQq-sBPl7zQ,119
5
5
  vector_inspector/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
6
6
  vector_inspector/core/__init__.py,sha256=hjOqiJwF1P0rXjiOKhK4qDTvBY7G3m4kq8taH-gKrFM,57
@@ -13,7 +13,7 @@ vector_inspector/main.py,sha256=puu1Fur298j6H8fG3_wF85RMhi4tjLZ0Are16kloMqM,479
13
13
  vector_inspector/services/__init__.py,sha256=QLgH7oybjHuEYDFNiBgmJxvSpgAzHEuBEPXa3SKJb_I,67
14
14
  vector_inspector/services/backup_restore_service.py,sha256=2bbmLamGg7gaYUl3zy_-8wL8xbYvO00kZ2tbImZkzi0,11782
15
15
  vector_inspector/services/filter_service.py,sha256=r1y_lPi4Mo4ZqVq_hfbTzRxcCbdH7RlsXg7ytkBTKi8,2334
16
- vector_inspector/services/import_export_service.py,sha256=76n7Vm02shfa-Fn7AWHWsrmaJVICMMs0JnWI-wXeZ_o,9751
16
+ vector_inspector/services/import_export_service.py,sha256=OPCrBXBewCznu5o8wFGvduU0jGZAcBvp_Fpv15kdoJ4,10712
17
17
  vector_inspector/services/settings_service.py,sha256=Y-2eJGyxUPPZid-3S0rCZ13xTRSLWYIOQ3ckSGX7gvE,2078
18
18
  vector_inspector/services/visualization_service.py,sha256=Bo9jSihT4axVKOAmefqXHemgm8uw59su2pz-UkXdONc,3901
19
19
  vector_inspector/ui/__init__.py,sha256=262ZiXO6Luk8vZnhCIoYxOtGiny0bXK-BTKjxUNBx-w,43
@@ -26,7 +26,7 @@ vector_inspector/ui/main_window.py,sha256=zHL7V_0pxyL_Bw2421GOJ-2X1jNphQdhwTX9s8
26
26
  vector_inspector/ui/views/__init__.py,sha256=FeMtVzSbVFBMjdwLQSQqD0FRW4ieJ4ZKXtTBci2e_bw,30
27
27
  vector_inspector/ui/views/collection_browser.py,sha256=oG9_YGPoVuMs-f_zSd4EcITmEU9caxvwuubsFUrNf-c,3991
28
28
  vector_inspector/ui/views/connection_view.py,sha256=5TL28JMfb0W42eUDDNbj6bIaj6m7WpalpdUEcd37qmM,16903
29
- vector_inspector/ui/views/metadata_view.py,sha256=1prg0ZFkPbA2PxUzr5Z-WlW8nTXa-_WSNlLIU-RTLME,21286
29
+ vector_inspector/ui/views/metadata_view.py,sha256=aBMHJU61ljttRljOuAkemZkLEowjSaTnMl-lSCZRb88,25453
30
30
  vector_inspector/ui/views/search_view.py,sha256=p6vt2heSpEbiRge46VSedTZjg7i3-AWdS0f96JXzlEU,10527
31
31
  vector_inspector/ui/views/visualization_view.py,sha256=389lKOOqz4_7I-pUIIu86ZbA2ttq8Tuwflbb5W-W4g0,9038
32
- vector_inspector-0.2.3.dist-info/RECORD,,
32
+ vector_inspector-0.2.4.3.dist-info/RECORD,,