@runcontext/ui 0.5.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.
- package/LICENSE +21 -0
- package/dist/index.cjs +447 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.mjs +447 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +59 -0
- package/static/setup.css +677 -0
- package/static/setup.js +469 -0
package/static/setup.js
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var state = {
|
|
5
|
+
step: 1,
|
|
6
|
+
brief: {
|
|
7
|
+
product_name: '',
|
|
8
|
+
description: '',
|
|
9
|
+
owner: { name: '', team: '', email: '' },
|
|
10
|
+
sensitivity: 'internal',
|
|
11
|
+
docs: [],
|
|
12
|
+
},
|
|
13
|
+
sources: [],
|
|
14
|
+
pipelineId: null,
|
|
15
|
+
pollTimer: null,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// ---- Helpers ----
|
|
19
|
+
|
|
20
|
+
function $(sel) { return document.querySelector(sel); }
|
|
21
|
+
function $$(sel) { return document.querySelectorAll(sel); }
|
|
22
|
+
|
|
23
|
+
function esc(s) {
|
|
24
|
+
var d = document.createElement('div');
|
|
25
|
+
d.textContent = s;
|
|
26
|
+
return d.innerHTML;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function showError(fieldId, msg) {
|
|
30
|
+
var field = document.getElementById(fieldId);
|
|
31
|
+
if (!field) return;
|
|
32
|
+
field.classList.add('error');
|
|
33
|
+
var existing = field.parentElement.querySelector('.field-error');
|
|
34
|
+
if (existing) existing.remove();
|
|
35
|
+
if (msg) {
|
|
36
|
+
var el = document.createElement('p');
|
|
37
|
+
el.className = 'field-error';
|
|
38
|
+
el.textContent = msg;
|
|
39
|
+
field.parentElement.appendChild(el);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function clearErrors() {
|
|
44
|
+
$$('.error').forEach(function (el) { el.classList.remove('error'); });
|
|
45
|
+
$$('.field-error').forEach(function (el) { el.remove(); });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function api(method, path, body) {
|
|
49
|
+
var opts = { method: method, headers: {} };
|
|
50
|
+
if (body && !(body instanceof FormData)) {
|
|
51
|
+
opts.headers['Content-Type'] = 'application/json';
|
|
52
|
+
opts.body = JSON.stringify(body);
|
|
53
|
+
} else if (body) {
|
|
54
|
+
opts.body = body;
|
|
55
|
+
}
|
|
56
|
+
var res = await fetch(path, opts);
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
var text = await res.text();
|
|
59
|
+
throw new Error(text || res.statusText);
|
|
60
|
+
}
|
|
61
|
+
var ct = res.headers.get('content-type') || '';
|
|
62
|
+
return ct.includes('json') ? res.json() : res.text();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---- DOM builder helpers (avoid innerHTML for security) ----
|
|
66
|
+
|
|
67
|
+
function createElement(tag, attrs, children) {
|
|
68
|
+
var el = document.createElement(tag);
|
|
69
|
+
if (attrs) {
|
|
70
|
+
Object.keys(attrs).forEach(function (key) {
|
|
71
|
+
if (key === 'className') el.className = attrs[key];
|
|
72
|
+
else if (key === 'textContent') el.textContent = attrs[key];
|
|
73
|
+
else el.setAttribute(key, attrs[key]);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (children) {
|
|
77
|
+
children.forEach(function (child) {
|
|
78
|
+
if (typeof child === 'string') {
|
|
79
|
+
el.appendChild(document.createTextNode(child));
|
|
80
|
+
} else if (child) {
|
|
81
|
+
el.appendChild(child);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return el;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---- Navigation ----
|
|
89
|
+
|
|
90
|
+
function goToStep(n) {
|
|
91
|
+
if (n < 1 || n > 5) return;
|
|
92
|
+
for (var i = 1; i < n; i++) {
|
|
93
|
+
var ps = $('.progress-step[data-step="' + i + '"]');
|
|
94
|
+
if (ps) { ps.classList.remove('active'); ps.classList.add('completed'); }
|
|
95
|
+
}
|
|
96
|
+
var active = $('.progress-step[data-step="' + n + '"]');
|
|
97
|
+
if (active) { active.classList.remove('completed'); active.classList.add('active'); }
|
|
98
|
+
for (var j = n + 1; j <= 5; j++) {
|
|
99
|
+
var fut = $('.progress-step[data-step="' + j + '"]');
|
|
100
|
+
if (fut) { fut.classList.remove('active', 'completed'); }
|
|
101
|
+
}
|
|
102
|
+
$$('.step').forEach(function (el) { el.classList.remove('active'); });
|
|
103
|
+
var panel = $('#step-' + n);
|
|
104
|
+
if (panel) panel.classList.add('active');
|
|
105
|
+
|
|
106
|
+
state.step = n;
|
|
107
|
+
|
|
108
|
+
if (n === 3) loadSources();
|
|
109
|
+
if (n === 4) renderReview();
|
|
110
|
+
if (n === 5) startBuild();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function validateStep(n) {
|
|
114
|
+
clearErrors();
|
|
115
|
+
if (n === 1) {
|
|
116
|
+
var name = $('#product-name').value.trim();
|
|
117
|
+
if (!name) { showError('product-name', 'Product name is required'); return false; }
|
|
118
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) { showError('product-name', 'Only letters, numbers, dashes, underscores'); return false; }
|
|
119
|
+
state.brief.product_name = name;
|
|
120
|
+
state.brief.description = $('#description').value.trim();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
if (n === 2) {
|
|
124
|
+
state.brief.owner.name = $('#owner-name').value.trim();
|
|
125
|
+
state.brief.owner.team = $('#owner-team').value.trim();
|
|
126
|
+
state.brief.owner.email = $('#owner-email').value.trim();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---- Step 3: Sources & Upload ----
|
|
133
|
+
|
|
134
|
+
async function loadSources() {
|
|
135
|
+
var container = $('#sources-list');
|
|
136
|
+
container.textContent = '';
|
|
137
|
+
container.appendChild(createElement('p', { className: 'muted', textContent: 'Detecting data sources...' }));
|
|
138
|
+
try {
|
|
139
|
+
var data = await api('GET', '/api/sources');
|
|
140
|
+
state.sources = data.sources || data || [];
|
|
141
|
+
container.textContent = '';
|
|
142
|
+
if (state.sources.length === 0) {
|
|
143
|
+
container.appendChild(createElement('p', { className: 'muted', textContent: 'No data sources detected in this directory.' }));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
state.sources.forEach(function (src) {
|
|
147
|
+
var card = createElement('div', { className: 'source-card' }, [
|
|
148
|
+
createElement('span', { className: 'source-name', textContent: src.name || src.type }),
|
|
149
|
+
createElement('span', { className: 'source-type', textContent: src.type || '' }),
|
|
150
|
+
createElement('span', { className: 'source-status detected', textContent: 'Detected' }),
|
|
151
|
+
]);
|
|
152
|
+
card.addEventListener('click', function () {
|
|
153
|
+
$$('#sources-list .source-card').forEach(function (c) { c.classList.remove('selected'); });
|
|
154
|
+
card.classList.add('selected');
|
|
155
|
+
state.brief.data_source = src.type + ':' + (src.name || src.type);
|
|
156
|
+
});
|
|
157
|
+
container.appendChild(card);
|
|
158
|
+
});
|
|
159
|
+
// Auto-select if only one source detected
|
|
160
|
+
if (state.sources.length === 1) {
|
|
161
|
+
var only = container.querySelector('.source-card');
|
|
162
|
+
if (only) only.click();
|
|
163
|
+
}
|
|
164
|
+
} catch (e) {
|
|
165
|
+
container.textContent = '';
|
|
166
|
+
container.appendChild(createElement('p', { className: 'muted', textContent: 'Could not detect sources.' }));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function setupUpload() {
|
|
171
|
+
var area = $('#upload-area');
|
|
172
|
+
var input = $('#file-input');
|
|
173
|
+
if (!area || !input) return;
|
|
174
|
+
|
|
175
|
+
area.addEventListener('click', function () { input.click(); });
|
|
176
|
+
|
|
177
|
+
area.addEventListener('dragover', function (e) {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
area.classList.add('dragover');
|
|
180
|
+
});
|
|
181
|
+
area.addEventListener('dragleave', function () {
|
|
182
|
+
area.classList.remove('dragover');
|
|
183
|
+
});
|
|
184
|
+
area.addEventListener('drop', function (e) {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
area.classList.remove('dragover');
|
|
187
|
+
if (e.dataTransfer.files.length) uploadFiles(e.dataTransfer.files);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
input.addEventListener('change', function () {
|
|
191
|
+
if (input.files.length) uploadFiles(input.files);
|
|
192
|
+
input.value = '';
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function uploadFiles(files) {
|
|
197
|
+
var productName = state.brief.product_name || 'unnamed';
|
|
198
|
+
for (var i = 0; i < files.length; i++) {
|
|
199
|
+
var file = files[i];
|
|
200
|
+
var fd = new FormData();
|
|
201
|
+
fd.append('file', file);
|
|
202
|
+
addFileRow(file.name, 'uploading...');
|
|
203
|
+
try {
|
|
204
|
+
await api('POST', '/api/upload/' + encodeURIComponent(productName), fd);
|
|
205
|
+
updateFileRow(file.name, 'uploaded');
|
|
206
|
+
state.brief.docs.push(file.name);
|
|
207
|
+
} catch (e) {
|
|
208
|
+
updateFileRow(file.name, 'error');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function addFileRow(name, status) {
|
|
214
|
+
var container = $('#uploaded-files');
|
|
215
|
+
var row = createElement('div', { className: 'uploaded-file', 'data-file': name }, [
|
|
216
|
+
createElement('span', { className: 'file-name', textContent: name }),
|
|
217
|
+
createElement('span', { className: 'file-status', textContent: status }),
|
|
218
|
+
]);
|
|
219
|
+
container.appendChild(row);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function updateFileRow(name, status) {
|
|
223
|
+
var row = $('[data-file="' + CSS.escape(name) + '"]');
|
|
224
|
+
if (row) {
|
|
225
|
+
var s = row.querySelector('.file-status');
|
|
226
|
+
if (s) s.textContent = status;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ---- Sensitivity Cards ----
|
|
231
|
+
|
|
232
|
+
function setupSensitivity() {
|
|
233
|
+
$$('.sensitivity-cards .card').forEach(function (card) {
|
|
234
|
+
card.addEventListener('click', function () {
|
|
235
|
+
$$('.sensitivity-cards .card').forEach(function (c) { c.classList.remove('selected'); });
|
|
236
|
+
card.classList.add('selected');
|
|
237
|
+
state.brief.sensitivity = card.getAttribute('data-sensitivity');
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---- Voice Input ----
|
|
243
|
+
|
|
244
|
+
function setupVoice() {
|
|
245
|
+
var btn = $('#voice-btn');
|
|
246
|
+
if (!btn) return;
|
|
247
|
+
var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
248
|
+
if (!SpeechRecognition) {
|
|
249
|
+
btn.style.display = 'none';
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
var recognition = new SpeechRecognition();
|
|
253
|
+
recognition.continuous = false;
|
|
254
|
+
recognition.interimResults = false;
|
|
255
|
+
recognition.lang = 'en-US';
|
|
256
|
+
var recording = false;
|
|
257
|
+
|
|
258
|
+
btn.addEventListener('click', function () {
|
|
259
|
+
if (recording) {
|
|
260
|
+
recognition.stop();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
recording = true;
|
|
264
|
+
btn.classList.add('recording');
|
|
265
|
+
recognition.start();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
recognition.addEventListener('result', function (e) {
|
|
269
|
+
var transcript = e.results[0][0].transcript;
|
|
270
|
+
var desc = $('#description');
|
|
271
|
+
if (desc) desc.value = (desc.value ? desc.value + ' ' : '') + transcript;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
recognition.addEventListener('end', function () {
|
|
275
|
+
recording = false;
|
|
276
|
+
btn.classList.remove('recording');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
recognition.addEventListener('error', function () {
|
|
280
|
+
recording = false;
|
|
281
|
+
btn.classList.remove('recording');
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ---- Step 4: Review ----
|
|
286
|
+
|
|
287
|
+
function renderReview() {
|
|
288
|
+
var c = $('#review-content');
|
|
289
|
+
if (!c) return;
|
|
290
|
+
c.textContent = '';
|
|
291
|
+
var rows = [
|
|
292
|
+
['Product Name', state.brief.product_name],
|
|
293
|
+
['Description', state.brief.description || '(none)'],
|
|
294
|
+
['Owner', state.brief.owner.name || '(not set)'],
|
|
295
|
+
['Team', state.brief.owner.team || '(not set)'],
|
|
296
|
+
['Email', state.brief.owner.email || '(not set)'],
|
|
297
|
+
['Sensitivity', state.brief.sensitivity],
|
|
298
|
+
['Data Source', state.brief.data_source || '(none selected) — ' + state.sources.length + ' detected'],
|
|
299
|
+
['Uploaded Docs', state.brief.docs.length > 0 ? state.brief.docs.join(', ') : '(none)'],
|
|
300
|
+
];
|
|
301
|
+
rows.forEach(function (r) {
|
|
302
|
+
var row = createElement('div', { className: 'review-row' }, [
|
|
303
|
+
createElement('span', { className: 'review-label', textContent: r[0] }),
|
|
304
|
+
createElement('span', { className: 'review-value', textContent: r[1] }),
|
|
305
|
+
]);
|
|
306
|
+
c.appendChild(row);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ---- Step 5: Build ----
|
|
311
|
+
|
|
312
|
+
var STAGES = [
|
|
313
|
+
'Saving context brief',
|
|
314
|
+
'Scanning data sources',
|
|
315
|
+
'Extracting schema metadata',
|
|
316
|
+
'Generating semantic descriptions',
|
|
317
|
+
'Writing OSI-ready context',
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
function buildStageElement(item) {
|
|
321
|
+
var cls = 'pipeline-stage';
|
|
322
|
+
var dotText = '';
|
|
323
|
+
if (item.status === 'done' || item.status === 'completed' || item.status === 'complete') {
|
|
324
|
+
cls += ' done';
|
|
325
|
+
dotText = '\u2713';
|
|
326
|
+
} else if (item.status === 'running' || item.status === 'in_progress') {
|
|
327
|
+
cls += ' running';
|
|
328
|
+
dotText = '\u2026';
|
|
329
|
+
} else if (item.status === 'error') {
|
|
330
|
+
cls += ' error';
|
|
331
|
+
dotText = '!';
|
|
332
|
+
}
|
|
333
|
+
var children = [
|
|
334
|
+
createElement('div', { className: 'stage-dot', textContent: dotText }),
|
|
335
|
+
createElement('div', { className: 'stage-info' }, [
|
|
336
|
+
createElement('div', { className: 'stage-name', textContent: item.stage }),
|
|
337
|
+
item.detail ? createElement('div', { className: 'stage-status', textContent: item.detail }) : null,
|
|
338
|
+
]),
|
|
339
|
+
];
|
|
340
|
+
return createElement('div', { className: cls }, children);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function renderTimeline(items) {
|
|
344
|
+
var el = $('#pipeline-timeline');
|
|
345
|
+
if (!el) return;
|
|
346
|
+
if (items.length === 0) {
|
|
347
|
+
items = STAGES.map(function (name) {
|
|
348
|
+
return { stage: name, status: 'pending', detail: '' };
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
el.textContent = '';
|
|
352
|
+
items.forEach(function (item) {
|
|
353
|
+
el.appendChild(buildStageElement(item));
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function renderTimelineFromStatus(data) {
|
|
358
|
+
var stages = data.stages || data.steps || [];
|
|
359
|
+
var items = stages.map(function (s) {
|
|
360
|
+
return {
|
|
361
|
+
stage: s.name || s.stage || s.label || '',
|
|
362
|
+
status: s.status || 'pending',
|
|
363
|
+
detail: s.detail || s.message || '',
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
if (items.length === 0 && typeof data.currentStep === 'number') {
|
|
367
|
+
items = STAGES.map(function (name, i) {
|
|
368
|
+
var status = 'pending';
|
|
369
|
+
if (i < data.currentStep) status = 'done';
|
|
370
|
+
else if (i === data.currentStep) status = 'running';
|
|
371
|
+
return { stage: name, status: status, detail: '' };
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
renderTimeline(items);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function startBuild() {
|
|
378
|
+
renderTimeline([]);
|
|
379
|
+
try {
|
|
380
|
+
await api('POST', '/api/brief', state.brief);
|
|
381
|
+
var result = await api('POST', '/api/pipeline/start', {
|
|
382
|
+
productName: state.brief.product_name,
|
|
383
|
+
targetTier: 'gold',
|
|
384
|
+
});
|
|
385
|
+
state.pipelineId = result.id || result.pipelineId;
|
|
386
|
+
pollPipeline();
|
|
387
|
+
} catch (e) {
|
|
388
|
+
renderTimeline([{ stage: 'Error', status: 'error', detail: e.message }]);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function pollPipeline() {
|
|
393
|
+
if (!state.pipelineId) return;
|
|
394
|
+
state.pollTimer = setInterval(async function () {
|
|
395
|
+
try {
|
|
396
|
+
var data = await api('GET', '/api/pipeline/status/' + encodeURIComponent(state.pipelineId));
|
|
397
|
+
renderTimelineFromStatus(data);
|
|
398
|
+
if (data.status === 'done' || data.status === 'complete' || data.status === 'completed' || data.status === 'error') {
|
|
399
|
+
clearInterval(state.pollTimer);
|
|
400
|
+
if (data.status !== 'error') {
|
|
401
|
+
var doneEl = $('#pipeline-done');
|
|
402
|
+
if (doneEl) doneEl.style.display = '';
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} catch (e) {
|
|
406
|
+
clearInterval(state.pollTimer);
|
|
407
|
+
renderTimeline([{ stage: 'Error', status: 'error', detail: e.message }]);
|
|
408
|
+
}
|
|
409
|
+
}, 1000);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ---- Existing Products Banner ----
|
|
413
|
+
|
|
414
|
+
async function checkExistingProducts() {
|
|
415
|
+
try {
|
|
416
|
+
var res = await fetch('/api/products');
|
|
417
|
+
var products = await res.json();
|
|
418
|
+
if (products.length > 0) {
|
|
419
|
+
var banner = document.createElement('div');
|
|
420
|
+
banner.className = 'existing-products-banner';
|
|
421
|
+
|
|
422
|
+
var title = document.createElement('p');
|
|
423
|
+
title.className = 'banner-title';
|
|
424
|
+
title.textContent = 'Your semantic plane has ' + products.length + ' data product' + (products.length === 1 ? '' : 's') + '. Adding another.';
|
|
425
|
+
banner.appendChild(title);
|
|
426
|
+
|
|
427
|
+
var list = document.createElement('div');
|
|
428
|
+
list.className = 'product-chips';
|
|
429
|
+
for (var i = 0; i < products.length; i++) {
|
|
430
|
+
var chip = document.createElement('span');
|
|
431
|
+
chip.className = 'product-chip';
|
|
432
|
+
chip.textContent = products[i].name;
|
|
433
|
+
list.appendChild(chip);
|
|
434
|
+
}
|
|
435
|
+
banner.appendChild(list);
|
|
436
|
+
|
|
437
|
+
var step1 = document.getElementById('step-1');
|
|
438
|
+
if (step1) {
|
|
439
|
+
step1.parentNode.insertBefore(banner, step1);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
} catch (e) {
|
|
443
|
+
// ignore - not critical
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ---- Init ----
|
|
448
|
+
|
|
449
|
+
function init() {
|
|
450
|
+
$$('[data-next]').forEach(function (btn) {
|
|
451
|
+
btn.addEventListener('click', function () {
|
|
452
|
+
if (validateStep(state.step)) goToStep(state.step + 1);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
$$('[data-prev]').forEach(function (btn) {
|
|
456
|
+
btn.addEventListener('click', function () {
|
|
457
|
+
goToStep(state.step - 1);
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
setupSensitivity();
|
|
462
|
+
setupUpload();
|
|
463
|
+
setupVoice();
|
|
464
|
+
checkExistingProducts();
|
|
465
|
+
goToStep(1);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
469
|
+
})();
|