anywidget-vector 0.2.0__py3-none-any.whl → 0.2.1__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.
Files changed (34) hide show
  1. anywidget_vector/__init__.py +1 -1
  2. anywidget_vector/backends/__init__.py +103 -0
  3. anywidget_vector/backends/chroma/__init__.py +27 -0
  4. anywidget_vector/backends/chroma/client.py +60 -0
  5. anywidget_vector/backends/chroma/converter.py +86 -0
  6. anywidget_vector/backends/grafeo/__init__.py +20 -0
  7. anywidget_vector/backends/grafeo/client.py +33 -0
  8. anywidget_vector/backends/grafeo/converter.py +46 -0
  9. anywidget_vector/backends/lancedb/__init__.py +22 -0
  10. anywidget_vector/backends/lancedb/client.py +56 -0
  11. anywidget_vector/backends/lancedb/converter.py +71 -0
  12. anywidget_vector/backends/pinecone/__init__.py +21 -0
  13. anywidget_vector/backends/pinecone/client.js +45 -0
  14. anywidget_vector/backends/pinecone/converter.py +62 -0
  15. anywidget_vector/backends/qdrant/__init__.py +26 -0
  16. anywidget_vector/backends/qdrant/client.js +61 -0
  17. anywidget_vector/backends/qdrant/converter.py +83 -0
  18. anywidget_vector/backends/weaviate/__init__.py +33 -0
  19. anywidget_vector/backends/weaviate/client.js +50 -0
  20. anywidget_vector/backends/weaviate/converter.py +81 -0
  21. anywidget_vector/static/icons.js +14 -0
  22. anywidget_vector/traitlets.py +84 -0
  23. anywidget_vector/ui/__init__.py +206 -0
  24. anywidget_vector/ui/canvas.js +521 -0
  25. anywidget_vector/ui/constants.js +64 -0
  26. anywidget_vector/ui/properties.js +158 -0
  27. anywidget_vector/ui/settings.js +265 -0
  28. anywidget_vector/ui/styles.css +348 -0
  29. anywidget_vector/ui/toolbar.js +117 -0
  30. anywidget_vector/widget.py +174 -1120
  31. {anywidget_vector-0.2.0.dist-info → anywidget_vector-0.2.1.dist-info}/METADATA +3 -3
  32. anywidget_vector-0.2.1.dist-info/RECORD +34 -0
  33. anywidget_vector-0.2.0.dist-info/RECORD +0 -6
  34. {anywidget_vector-0.2.0.dist-info → anywidget_vector-0.2.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,158 @@
1
+ // Properties panel component for VectorSpace widget
2
+ import { ICONS } from "../static/icons.js";
3
+
4
+ export function createPropertiesPanel(model, callbacks) {
5
+ const panel = document.createElement("div");
6
+ panel.className = "avs-panel avs-panel-right";
7
+
8
+ const inner = document.createElement("div");
9
+ inner.className = "avs-panel-inner";
10
+ panel.appendChild(inner);
11
+
12
+ // Header
13
+ const header = document.createElement("div");
14
+ header.className = "avs-panel-header";
15
+ header.innerHTML = `<span>Properties</span>`;
16
+ const closeBtn = document.createElement("button");
17
+ closeBtn.className = "avs-btn avs-btn-icon";
18
+ closeBtn.innerHTML = ICONS.close;
19
+ closeBtn.addEventListener("click", () => callbacks.onClose?.());
20
+ header.appendChild(closeBtn);
21
+ inner.appendChild(header);
22
+
23
+ // Content container
24
+ const content = document.createElement("div");
25
+ content.className = "avs-properties-content";
26
+ inner.appendChild(content);
27
+
28
+ // Initial state
29
+ showEmpty(content);
30
+
31
+ // Update on selection change
32
+ model.on("change:selected_points", () => {
33
+ const selectedIds = model.get("selected_points") || [];
34
+ const points = model.get("points") || [];
35
+ if (selectedIds.length === 0) {
36
+ showEmpty(content);
37
+ } else if (selectedIds.length === 1) {
38
+ const point = points.find(p => p.id === selectedIds[0]);
39
+ if (point) {
40
+ showPointProperties(content, point);
41
+ } else {
42
+ showEmpty(content);
43
+ }
44
+ } else {
45
+ showMultipleSelection(content, selectedIds.length);
46
+ }
47
+ });
48
+
49
+ // Update on hover
50
+ model.on("change:hovered_point", () => {
51
+ const hovered = model.get("hovered_point");
52
+ const selectedIds = model.get("selected_points") || [];
53
+ if (hovered && selectedIds.length === 0) {
54
+ showPointProperties(content, hovered, true);
55
+ } else if (selectedIds.length === 0) {
56
+ showEmpty(content);
57
+ }
58
+ });
59
+
60
+ return {
61
+ element: panel,
62
+ open: () => panel.classList.add("avs-open"),
63
+ close: () => panel.classList.remove("avs-open"),
64
+ toggle: () => panel.classList.toggle("avs-open"),
65
+ isOpen: () => panel.classList.contains("avs-open"),
66
+ };
67
+ }
68
+
69
+ function showEmpty(container) {
70
+ container.innerHTML = `
71
+ <div class="avs-properties-empty">
72
+ <p>Select a point to view its properties</p>
73
+ </div>
74
+ `;
75
+ }
76
+
77
+ function showMultipleSelection(container, count) {
78
+ container.innerHTML = `
79
+ <div class="avs-properties-empty">
80
+ <p>${count} points selected</p>
81
+ </div>
82
+ `;
83
+ }
84
+
85
+ function showPointProperties(container, point, isHover = false) {
86
+ container.innerHTML = "";
87
+
88
+ // Header with ID
89
+ const header = document.createElement("div");
90
+ header.className = "avs-section-header";
91
+ header.textContent = isHover ? "Hovered" : "Selected";
92
+ container.appendChild(header);
93
+
94
+ // ID row
95
+ addPropertyRow(container, "id", point.id);
96
+
97
+ // Label if present
98
+ if (point.label) {
99
+ addPropertyRow(container, "label", point.label);
100
+ }
101
+
102
+ // Position section
103
+ const posHeader = document.createElement("div");
104
+ posHeader.className = "avs-section-header";
105
+ posHeader.textContent = "Position";
106
+ container.appendChild(posHeader);
107
+
108
+ addPropertyRow(container, "x", formatNumber(point.x));
109
+ addPropertyRow(container, "y", formatNumber(point.y));
110
+ addPropertyRow(container, "z", formatNumber(point.z));
111
+
112
+ // Score if present (from search results)
113
+ if (point.score !== undefined) {
114
+ const scoreHeader = document.createElement("div");
115
+ scoreHeader.className = "avs-section-header";
116
+ scoreHeader.textContent = "Similarity";
117
+ container.appendChild(scoreHeader);
118
+ addPropertyRow(container, "score", formatNumber(point.score));
119
+ }
120
+
121
+ // Other metadata
122
+ const skipKeys = new Set(["id", "label", "x", "y", "z", "score", "vector", "color", "size", "shape"]);
123
+ const metaKeys = Object.keys(point).filter(k => !skipKeys.has(k) && !k.startsWith("_"));
124
+
125
+ if (metaKeys.length > 0) {
126
+ const metaHeader = document.createElement("div");
127
+ metaHeader.className = "avs-section-header";
128
+ metaHeader.textContent = "Metadata";
129
+ container.appendChild(metaHeader);
130
+
131
+ metaKeys.forEach(key => {
132
+ addPropertyRow(container, key, formatValue(point[key]));
133
+ });
134
+ }
135
+ }
136
+
137
+ function addPropertyRow(container, key, value) {
138
+ const row = document.createElement("div");
139
+ row.className = "avs-property-row";
140
+ row.innerHTML = `
141
+ <span class="avs-property-key">${key}</span>
142
+ <span class="avs-property-value" title="${String(value)}">${value}</span>
143
+ `;
144
+ container.appendChild(row);
145
+ }
146
+
147
+ function formatNumber(val) {
148
+ if (val === undefined || val === null) return "—";
149
+ if (typeof val === "number") return val.toFixed(4);
150
+ return String(val);
151
+ }
152
+
153
+ function formatValue(val) {
154
+ if (val === undefined || val === null) return "—";
155
+ if (typeof val === "number") return val.toFixed(4);
156
+ if (typeof val === "object") return JSON.stringify(val).slice(0, 50);
157
+ return String(val).slice(0, 50);
158
+ }
@@ -0,0 +1,265 @@
1
+ // Settings panel component for VectorSpace widget
2
+ import { ICONS } from "../static/icons.js";
3
+ import { BACKENDS } from "./constants.js";
4
+
5
+ export function createSettingsPanel(model, callbacks) {
6
+ const panel = document.createElement("div");
7
+ panel.className = "avs-panel avs-panel-right";
8
+
9
+ const inner = document.createElement("div");
10
+ inner.className = "avs-panel-inner";
11
+ panel.appendChild(inner);
12
+
13
+ // Header
14
+ const header = document.createElement("div");
15
+ header.className = "avs-panel-header";
16
+ header.innerHTML = `<span>Settings</span>`;
17
+ const closeBtn = document.createElement("button");
18
+ closeBtn.className = "avs-btn avs-btn-icon";
19
+ closeBtn.innerHTML = ICONS.close;
20
+ closeBtn.addEventListener("click", () => callbacks.onClose?.());
21
+ header.appendChild(closeBtn);
22
+ inner.appendChild(header);
23
+
24
+ // Backend selector
25
+ const backendGroup = createFormGroup("Backend");
26
+ const backendSelect = document.createElement("select");
27
+ backendSelect.className = "avs-select";
28
+ Object.entries(BACKENDS).forEach(([key, config]) => {
29
+ const opt = document.createElement("option");
30
+ opt.value = key;
31
+ opt.textContent = `${config.name} (${config.side})`;
32
+ backendSelect.appendChild(opt);
33
+ });
34
+ backendSelect.value = model.get("backend") || "qdrant";
35
+ backendSelect.addEventListener("change", () => {
36
+ model.set("backend", backendSelect.value);
37
+ model.save_changes();
38
+ updateConnectionFields(connFields, model);
39
+ });
40
+ backendGroup.appendChild(backendSelect);
41
+ inner.appendChild(backendGroup);
42
+
43
+ // Connection fields container
44
+ const connFields = document.createElement("div");
45
+ connFields.className = "avs-connection-fields";
46
+ inner.appendChild(connFields);
47
+
48
+ // Embedding config section
49
+ const embHeader = document.createElement("div");
50
+ embHeader.className = "avs-section-header";
51
+ embHeader.textContent = "Embedding (for text search)";
52
+ inner.appendChild(embHeader);
53
+
54
+ const embProviderGroup = createFormGroup("Provider");
55
+ const embProviderSelect = document.createElement("select");
56
+ embProviderSelect.className = "avs-select";
57
+ [{ v: "openai", l: "OpenAI" }, { v: "cohere", l: "Cohere" }].forEach(p => {
58
+ const opt = document.createElement("option");
59
+ opt.value = p.v;
60
+ opt.textContent = p.l;
61
+ embProviderSelect.appendChild(opt);
62
+ });
63
+ const embConfig = model.get("embedding_config") || {};
64
+ embProviderSelect.value = embConfig.provider || "openai";
65
+ embProviderGroup.appendChild(embProviderSelect);
66
+ inner.appendChild(embProviderGroup);
67
+
68
+ const embKeyGroup = createFormGroup("API Key");
69
+ const embKeyInput = document.createElement("input");
70
+ embKeyInput.type = "password";
71
+ embKeyInput.className = "avs-input";
72
+ embKeyInput.placeholder = "sk-...";
73
+ embKeyInput.value = embConfig.apiKey || "";
74
+ embKeyGroup.appendChild(embKeyInput);
75
+ inner.appendChild(embKeyGroup);
76
+
77
+ const embModelGroup = createFormGroup("Model");
78
+ const embModelInput = document.createElement("input");
79
+ embModelInput.type = "text";
80
+ embModelInput.className = "avs-input";
81
+ embModelInput.placeholder = "text-embedding-3-small";
82
+ embModelInput.value = embConfig.model || "";
83
+ embModelGroup.appendChild(embModelInput);
84
+ inner.appendChild(embModelGroup);
85
+
86
+ // Update embedding config on change
87
+ const updateEmbedding = () => {
88
+ model.set("embedding_config", {
89
+ provider: embProviderSelect.value,
90
+ apiKey: embKeyInput.value,
91
+ model: embModelInput.value,
92
+ });
93
+ model.save_changes();
94
+ };
95
+ embProviderSelect.addEventListener("change", updateEmbedding);
96
+ embKeyInput.addEventListener("input", updateEmbedding);
97
+ embModelInput.addEventListener("input", updateEmbedding);
98
+
99
+ // Query limit
100
+ const limitGroup = createFormGroup("Result Limit");
101
+ const limitInput = document.createElement("input");
102
+ limitInput.type = "number";
103
+ limitInput.className = "avs-input";
104
+ limitInput.min = "1";
105
+ limitInput.max = "10000";
106
+ limitInput.value = model.get("query_limit") || 100;
107
+ limitInput.addEventListener("input", () => {
108
+ model.set("query_limit", parseInt(limitInput.value, 10) || 100);
109
+ model.save_changes();
110
+ });
111
+ limitGroup.appendChild(limitInput);
112
+ inner.appendChild(limitGroup);
113
+
114
+ // Error display
115
+ const errorDiv = document.createElement("div");
116
+ errorDiv.className = "avs-error";
117
+ errorDiv.style.display = "none";
118
+ inner.appendChild(errorDiv);
119
+
120
+ model.on("change:query_error", () => {
121
+ const err = model.get("query_error");
122
+ if (err) {
123
+ errorDiv.textContent = err;
124
+ errorDiv.style.display = "block";
125
+ } else {
126
+ errorDiv.style.display = "none";
127
+ }
128
+ });
129
+
130
+ // Initialize connection fields
131
+ updateConnectionFields(connFields, model);
132
+
133
+ return {
134
+ element: panel,
135
+ open: () => panel.classList.add("avs-open"),
136
+ close: () => panel.classList.remove("avs-open"),
137
+ toggle: () => panel.classList.toggle("avs-open"),
138
+ isOpen: () => panel.classList.contains("avs-open"),
139
+ };
140
+ }
141
+
142
+ function createFormGroup(label) {
143
+ const group = document.createElement("div");
144
+ group.className = "avs-form-group";
145
+ const lbl = document.createElement("label");
146
+ lbl.className = "avs-label";
147
+ lbl.textContent = label;
148
+ group.appendChild(lbl);
149
+ return group;
150
+ }
151
+
152
+ function updateConnectionFields(container, model) {
153
+ container.innerHTML = "";
154
+
155
+ const backend = model.get("backend") || "qdrant";
156
+ const config = model.get("backend_config") || {};
157
+ const backendInfo = BACKENDS[backend];
158
+
159
+ // URL field (for browser-side backends)
160
+ if (backendInfo.side === "browser") {
161
+ const urlGroup = createFormGroup("URL");
162
+ const urlInput = document.createElement("input");
163
+ urlInput.type = "text";
164
+ urlInput.className = "avs-input";
165
+ urlInput.placeholder = backend === "qdrant" ? "http://localhost:6333" :
166
+ backend === "pinecone" ? "https://xxx.pinecone.io" :
167
+ "http://localhost:8080";
168
+ urlInput.value = config.url || "";
169
+ urlInput.addEventListener("input", () => updateBackendConfig(model, container));
170
+ urlGroup.appendChild(urlInput);
171
+ container.appendChild(urlGroup);
172
+ }
173
+
174
+ // API Key (for backends with auth)
175
+ if (backendInfo.hasAuth) {
176
+ const keyGroup = createFormGroup("API Key");
177
+ const keyInput = document.createElement("input");
178
+ keyInput.type = "password";
179
+ keyInput.className = "avs-input";
180
+ keyInput.placeholder = "API Key (optional for local)";
181
+ keyInput.value = config.apiKey || "";
182
+ keyInput.addEventListener("input", () => updateBackendConfig(model, container));
183
+ keyGroup.appendChild(keyInput);
184
+ container.appendChild(keyGroup);
185
+ }
186
+
187
+ // Collection/Index/Class/Table name
188
+ let nameLabel, namePlaceholder;
189
+ if (backendInfo.hasCollection) {
190
+ nameLabel = "Collection";
191
+ namePlaceholder = "my_collection";
192
+ } else if (backendInfo.hasIndex) {
193
+ nameLabel = "Index";
194
+ namePlaceholder = "my-index";
195
+ } else if (backendInfo.hasClass) {
196
+ nameLabel = "Class";
197
+ namePlaceholder = "MyClass";
198
+ } else if (backendInfo.hasTable) {
199
+ nameLabel = "Table";
200
+ namePlaceholder = "my_table";
201
+ }
202
+
203
+ if (nameLabel) {
204
+ const nameGroup = createFormGroup(nameLabel);
205
+ const nameInput = document.createElement("input");
206
+ nameInput.type = "text";
207
+ nameInput.className = "avs-input";
208
+ nameInput.placeholder = namePlaceholder;
209
+ nameInput.value = config.collection || config.index || config.className || config.table || "";
210
+ nameInput.dataset.field = nameLabel.toLowerCase();
211
+ nameInput.addEventListener("input", () => updateBackendConfig(model, container));
212
+ nameGroup.appendChild(nameInput);
213
+ container.appendChild(nameGroup);
214
+ }
215
+
216
+ // Namespace (Pinecone specific)
217
+ if (backend === "pinecone") {
218
+ const nsGroup = createFormGroup("Namespace");
219
+ const nsInput = document.createElement("input");
220
+ nsInput.type = "text";
221
+ nsInput.className = "avs-input";
222
+ nsInput.placeholder = "(optional)";
223
+ nsInput.value = config.namespace || "";
224
+ nsInput.addEventListener("input", () => updateBackendConfig(model, container));
225
+ nsGroup.appendChild(nsInput);
226
+ container.appendChild(nsGroup);
227
+ }
228
+
229
+ // Python-side backends note
230
+ if (backendInfo.side === "python") {
231
+ const note = document.createElement("div");
232
+ note.className = "avs-note";
233
+ note.textContent = "Configure this backend in Python using set_backend()";
234
+ container.appendChild(note);
235
+ }
236
+ }
237
+
238
+ function updateBackendConfig(model, container) {
239
+ const backend = model.get("backend") || "qdrant";
240
+ const inputs = container.querySelectorAll(".avs-input");
241
+ const config = {};
242
+
243
+ inputs.forEach((input, idx) => {
244
+ if (!input.value) return;
245
+ const placeholder = input.placeholder.toLowerCase();
246
+ if (placeholder.includes("http") || placeholder.includes("localhost")) {
247
+ config.url = input.value;
248
+ } else if (placeholder.includes("api key")) {
249
+ config.apiKey = input.value;
250
+ } else if (placeholder.includes("collection")) {
251
+ config.collection = input.value;
252
+ } else if (placeholder.includes("index")) {
253
+ config.index = input.value;
254
+ } else if (placeholder.includes("class")) {
255
+ config.className = input.value;
256
+ } else if (placeholder.includes("table")) {
257
+ config.table = input.value;
258
+ } else if (placeholder.includes("optional")) {
259
+ config.namespace = input.value;
260
+ }
261
+ });
262
+
263
+ model.set("backend_config", config);
264
+ model.save_changes();
265
+ }