vectra-js 0.9.0

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.
@@ -0,0 +1,281 @@
1
+ :root {
2
+ --primary: #4f46e5;
3
+ --primary-hover: #4338ca;
4
+ --bg-app: #f3f4f6;
5
+ --bg-sidebar: #ffffff;
6
+ --bg-content: #ffffff;
7
+ --text-main: #111827;
8
+ --text-muted: #6b7280;
9
+ --border: #e5e7eb;
10
+ --input-bg: #f9fafb;
11
+ --danger: #ef4444;
12
+ --success: #10b981;
13
+ }
14
+
15
+ * {
16
+ box-sizing: border-box;
17
+ margin: 0;
18
+ padding: 0;
19
+ }
20
+
21
+ body {
22
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
23
+ background-color: var(--bg-app);
24
+ color: var(--text-main);
25
+ height: 100vh;
26
+ overflow: hidden;
27
+ }
28
+
29
+ .app-container {
30
+ display: flex;
31
+ height: 100%;
32
+ }
33
+
34
+ /* Sidebar */
35
+ .sidebar {
36
+ width: 260px;
37
+ background-color: var(--bg-sidebar);
38
+ border-right: 1px solid var(--border);
39
+ display: flex;
40
+ flex-direction: column;
41
+ padding: 1.5rem 1rem;
42
+ flex-shrink: 0;
43
+ }
44
+
45
+ .brand {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 0.75rem;
49
+ font-size: 1.25rem;
50
+ font-weight: 600;
51
+ color: var(--primary);
52
+ margin-bottom: 2rem;
53
+ padding: 0 0.5rem;
54
+ }
55
+
56
+ .nav-links {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 0.5rem;
60
+ flex: 1;
61
+ }
62
+
63
+ .nav-item {
64
+ background: none;
65
+ border: none;
66
+ text-align: left;
67
+ padding: 0.75rem 1rem;
68
+ border-radius: 0.5rem;
69
+ color: var(--text-muted);
70
+ font-weight: 500;
71
+ font-size: 0.95rem;
72
+ cursor: pointer;
73
+ transition: all 0.2s;
74
+ }
75
+
76
+ .nav-item:hover {
77
+ background-color: var(--bg-app);
78
+ color: var(--text-main);
79
+ }
80
+
81
+ .nav-item.active {
82
+ background-color: #eef2ff;
83
+ color: var(--primary);
84
+ }
85
+
86
+ .sidebar-footer {
87
+ margin-top: auto;
88
+ padding-top: 1rem;
89
+ border-top: 1px solid var(--border);
90
+ }
91
+
92
+ /* Content Area */
93
+ .content-area {
94
+ flex: 1;
95
+ overflow-y: auto;
96
+ padding: 2rem 3rem;
97
+ background-color: var(--bg-content);
98
+ max-width: 1000px; /* Constrain width for readability */
99
+ margin: 0 auto;
100
+ width: 100%;
101
+ border-left: 1px solid var(--border);
102
+ border-right: 1px solid var(--border);
103
+ }
104
+
105
+ .header-bar {
106
+ margin-bottom: 2.5rem;
107
+ }
108
+
109
+ .header-bar h1 {
110
+ font-size: 1.875rem;
111
+ font-weight: 600;
112
+ margin-bottom: 0.5rem;
113
+ }
114
+
115
+ .subtitle {
116
+ color: var(--text-muted);
117
+ }
118
+
119
+ /* Tabs */
120
+ .tab-content {
121
+ display: none;
122
+ animation: fadeIn 0.3s ease;
123
+ }
124
+
125
+ .tab-content.active {
126
+ display: block;
127
+ }
128
+
129
+ @keyframes fadeIn {
130
+ from { opacity: 0; transform: translateY(5px); }
131
+ to { opacity: 1; transform: translateY(0); }
132
+ }
133
+
134
+ section h2 {
135
+ font-size: 1.25rem;
136
+ font-weight: 600;
137
+ margin-bottom: 0.5rem;
138
+ }
139
+
140
+ .section-desc {
141
+ color: var(--text-muted);
142
+ margin-bottom: 1.5rem;
143
+ font-size: 0.95rem;
144
+ }
145
+
146
+ /* Forms */
147
+ .form-group {
148
+ margin-bottom: 1.5rem;
149
+ }
150
+
151
+ .grid-2 {
152
+ display: grid;
153
+ grid-template-columns: 1fr 1fr;
154
+ gap: 1.5rem;
155
+ }
156
+
157
+ label {
158
+ display: block;
159
+ font-weight: 500;
160
+ margin-bottom: 0.5rem;
161
+ font-size: 0.9rem;
162
+ color: #374151;
163
+ }
164
+
165
+ .form-control {
166
+ width: 100%;
167
+ padding: 0.625rem 0.875rem;
168
+ border: 1px solid var(--border);
169
+ border-radius: 0.5rem;
170
+ font-size: 0.95rem;
171
+ background-color: var(--input-bg);
172
+ transition: border-color 0.2s, box-shadow 0.2s;
173
+ font-family: inherit;
174
+ }
175
+
176
+ .form-control:focus {
177
+ outline: none;
178
+ border-color: var(--primary);
179
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
180
+ background-color: #fff;
181
+ }
182
+
183
+ textarea.form-control {
184
+ resize: vertical;
185
+ }
186
+
187
+ .code-font {
188
+ font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
189
+ font-size: 0.85rem;
190
+ }
191
+
192
+ .help-text {
193
+ font-size: 0.8rem;
194
+ color: var(--text-muted);
195
+ margin-top: 0.4rem;
196
+ }
197
+
198
+ .checkbox-group {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 0.5rem;
202
+ }
203
+
204
+ .checkbox-group input {
205
+ width: 1.1rem;
206
+ height: 1.1rem;
207
+ }
208
+
209
+ .checkbox-group label {
210
+ margin-bottom: 0;
211
+ }
212
+
213
+ /* Buttons */
214
+ .primary-btn {
215
+ width: 100%;
216
+ padding: 0.75rem;
217
+ background-color: var(--primary);
218
+ color: white;
219
+ border: none;
220
+ border-radius: 0.5rem;
221
+ font-weight: 500;
222
+ cursor: pointer;
223
+ transition: background-color 0.2s;
224
+ font-size: 0.95rem;
225
+ display: flex;
226
+ justify-content: center;
227
+ align-items: center;
228
+ }
229
+
230
+ .primary-btn:hover {
231
+ background-color: var(--primary-hover);
232
+ }
233
+
234
+ .primary-btn:disabled {
235
+ opacity: 0.7;
236
+ cursor: not-allowed;
237
+ }
238
+
239
+ #status-msg {
240
+ margin-top: 0.75rem;
241
+ font-size: 0.85rem;
242
+ text-align: center;
243
+ min-height: 1.25rem;
244
+ }
245
+
246
+ .success-msg { color: var(--success); }
247
+ .error-msg { color: var(--danger); }
248
+
249
+ /* Responsive */
250
+ @media (max-width: 768px) {
251
+ .app-container {
252
+ flex-direction: column;
253
+ overflow-y: auto;
254
+ }
255
+ .sidebar {
256
+ width: 100%;
257
+ height: auto;
258
+ padding: 1rem;
259
+ border-right: none;
260
+ border-bottom: 1px solid var(--border);
261
+ }
262
+ .nav-links {
263
+ flex-direction: row;
264
+ overflow-x: auto;
265
+ padding-bottom: 0.5rem;
266
+ }
267
+ .nav-item {
268
+ white-space: nowrap;
269
+ }
270
+ .content-area {
271
+ padding: 1.5rem;
272
+ border: none;
273
+ }
274
+ body {
275
+ overflow: auto;
276
+ }
277
+ .grid-2 {
278
+ grid-template-columns: 1fr;
279
+ gap: 0;
280
+ }
281
+ }
@@ -0,0 +1,175 @@
1
+ const http = require('http');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { RAGConfigSchema, ProviderType, ChunkingStrategy, RetrievalStrategy } = require('./config');
5
+
6
+ // Simple browser opener using child_process since 'open' package might not be installed
7
+ const { exec } = require('child_process');
8
+ const openBrowser = (url) => {
9
+ const start = (process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open');
10
+ exec(start + ' ' + url);
11
+ };
12
+
13
+ const DEFAULT_CONFIG = {
14
+ embedding: {
15
+ provider: ProviderType.OPENAI,
16
+ apiKey: "",
17
+ modelName: "text-embedding-3-small",
18
+ dimensions: null
19
+ },
20
+ llm: {
21
+ provider: ProviderType.GEMINI,
22
+ apiKey: "",
23
+ modelName: "gemini-1.5-pro-latest",
24
+ temperature: 0,
25
+ maxTokens: 1024
26
+ },
27
+ database: {
28
+ type: "prisma",
29
+ tableName: "Document",
30
+ columnMap: { content: "content", vector: "vector", metadata: "metadata" }
31
+ },
32
+ chunking: {
33
+ strategy: ChunkingStrategy.RECURSIVE,
34
+ chunkSize: 1000,
35
+ chunkOverlap: 200,
36
+ separators: ["\n\n", "\n", " ", ""]
37
+ },
38
+ retrieval: {
39
+ strategy: RetrievalStrategy.NAIVE,
40
+ hybridAlpha: 0.5
41
+ },
42
+ reranking: {
43
+ enabled: false,
44
+ provider: "llm",
45
+ topN: 5,
46
+ windowSize: 20
47
+ }
48
+ };
49
+
50
+ function serveStatic(res, filePath, contentType) {
51
+ if (fs.existsSync(filePath)) {
52
+ res.writeHead(200, { 'Content-Type': contentType });
53
+ fs.createReadStream(filePath).pipe(res);
54
+ } else {
55
+ res.writeHead(404);
56
+ res.end();
57
+ }
58
+ }
59
+
60
+ function start(configPath, port = 8766, openInBrowser = true) {
61
+ const absConfigPath = path.resolve(configPath);
62
+
63
+ const createServer = (currentPort) => {
64
+ const server = http.createServer((req, res) => {
65
+ const sendJson = (status, obj) => {
66
+ res.writeHead(status, { 'Content-Type': 'application/json' });
67
+ res.end(JSON.stringify(obj));
68
+ };
69
+
70
+ // Serve index.html
71
+ if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
72
+ const filePath = path.join(__dirname, 'ui', 'index.html');
73
+ serveStatic(res, filePath, 'text/html; charset=utf-8');
74
+ return;
75
+ }
76
+
77
+ // Serve static assets
78
+ if (req.method === 'GET' && !req.url.startsWith('/config')) {
79
+ const possiblePath = path.join(__dirname, 'ui', req.url.substring(1));
80
+ if (fs.existsSync(possiblePath) && fs.lstatSync(possiblePath).isFile()) {
81
+ const ext = path.extname(possiblePath).toLowerCase();
82
+ let contentType = 'text/plain';
83
+ if (ext === '.css') contentType = 'text/css';
84
+ if (ext === '.js') contentType = 'application/javascript';
85
+ if (ext === '.html') contentType = 'text/html';
86
+
87
+ serveStatic(res, possiblePath, contentType);
88
+ return;
89
+ }
90
+ }
91
+
92
+ if (req.method === 'GET' && req.url === '/config') {
93
+ if (fs.existsSync(absConfigPath)) {
94
+ try {
95
+ const raw = fs.readFileSync(absConfigPath, 'utf-8');
96
+ const json = JSON.parse(raw);
97
+ // Optional: validate with RAGConfigSchema if strictly needed, but for UI we might just load it
98
+ res.writeHead(200, { 'Content-Type': 'application/json' });
99
+ res.end(JSON.stringify(json));
100
+ } catch (e) {
101
+ sendJson(400, { error: e.message });
102
+ }
103
+ } else {
104
+ sendJson(200, DEFAULT_CONFIG);
105
+ }
106
+ return;
107
+ }
108
+
109
+ if (req.method === 'POST' && req.url === '/config') {
110
+ let body = '';
111
+ req.on('data', chunk => body += chunk);
112
+ req.on('end', () => {
113
+ try {
114
+ const data = JSON.parse(body);
115
+ // Clean undefined for JSON payload if present
116
+ const cleanJson = data.config ? JSON.parse(JSON.stringify(data.config)) : null;
117
+
118
+ // Ensure directory exists
119
+ const dir = path.dirname(absConfigPath);
120
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
121
+
122
+ // Write JSON config (for tooling compatibility)
123
+ if (cleanJson) {
124
+ fs.writeFileSync(absConfigPath, JSON.stringify(cleanJson, null, 2), 'utf-8');
125
+ }
126
+
127
+ // Write code file if provided
128
+ if (data.code && data.backend) {
129
+ const targetFile = data.backend === 'python'
130
+ ? path.join(dir, 'vectra_config.py')
131
+ : path.join(dir, 'vectra-config.js');
132
+ fs.writeFileSync(targetFile, String(data.code), 'utf-8');
133
+ sendJson(200, { message: 'Configuration saved successfully!', file: targetFile });
134
+ return;
135
+ }
136
+
137
+ sendJson(200, { message: 'Configuration saved successfully!' });
138
+ } catch (e) {
139
+ sendJson(400, { error: e.message });
140
+ }
141
+ });
142
+ return;
143
+ }
144
+
145
+ res.writeHead(404);
146
+ res.end();
147
+ });
148
+
149
+ server.on('error', (e) => {
150
+ if (e.code === 'EADDRINUSE') {
151
+ console.log(`Port ${currentPort} is in use, trying ${currentPort + 1}...`);
152
+ if (currentPort + 1 > 65535) {
153
+ console.error("No available ports found.");
154
+ return;
155
+ }
156
+ // Try next port with a new server instance
157
+ createServer(currentPort + 1);
158
+ } else {
159
+ throw e;
160
+ }
161
+ });
162
+
163
+ server.listen(currentPort, () => {
164
+ const url = `http://localhost:${currentPort}/`;
165
+ console.log(`Vectra WebConfig running at ${url}`);
166
+ if (openInBrowser) {
167
+ openBrowser(url);
168
+ }
169
+ });
170
+ };
171
+
172
+ createServer(port);
173
+ }
174
+
175
+ module.exports = { start };