anywidget-vector 0.1.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.
- anywidget_vector/__init__.py +1 -1
- anywidget_vector/backends/__init__.py +103 -0
- anywidget_vector/backends/chroma/__init__.py +27 -0
- anywidget_vector/backends/chroma/client.py +60 -0
- anywidget_vector/backends/chroma/converter.py +86 -0
- anywidget_vector/backends/grafeo/__init__.py +20 -0
- anywidget_vector/backends/grafeo/client.py +33 -0
- anywidget_vector/backends/grafeo/converter.py +46 -0
- anywidget_vector/backends/lancedb/__init__.py +22 -0
- anywidget_vector/backends/lancedb/client.py +56 -0
- anywidget_vector/backends/lancedb/converter.py +71 -0
- anywidget_vector/backends/pinecone/__init__.py +21 -0
- anywidget_vector/backends/pinecone/client.js +45 -0
- anywidget_vector/backends/pinecone/converter.py +62 -0
- anywidget_vector/backends/qdrant/__init__.py +26 -0
- anywidget_vector/backends/qdrant/client.js +61 -0
- anywidget_vector/backends/qdrant/converter.py +83 -0
- anywidget_vector/backends/weaviate/__init__.py +33 -0
- anywidget_vector/backends/weaviate/client.js +50 -0
- anywidget_vector/backends/weaviate/converter.py +81 -0
- anywidget_vector/static/icons.js +14 -0
- anywidget_vector/traitlets.py +84 -0
- anywidget_vector/ui/__init__.py +206 -0
- anywidget_vector/ui/canvas.js +521 -0
- anywidget_vector/ui/constants.js +64 -0
- anywidget_vector/ui/properties.js +158 -0
- anywidget_vector/ui/settings.js +265 -0
- anywidget_vector/ui/styles.css +348 -0
- anywidget_vector/ui/toolbar.js +117 -0
- anywidget_vector/widget.py +187 -850
- {anywidget_vector-0.1.0.dist-info → anywidget_vector-0.2.1.dist-info}/METADATA +70 -3
- anywidget_vector-0.2.1.dist-info/RECORD +34 -0
- anywidget_vector-0.1.0.dist-info/RECORD +0 -6
- {anywidget_vector-0.1.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
|
+
}
|