@cccarv82/freya 3.0.0 → 3.1.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/cli/web-ui.css +0 -132
- package/cli/web-ui.js +156 -211
- package/cli/web.js +52 -93
- package/package.json +1 -1
package/cli/web-ui.css
CHANGED
|
@@ -1279,134 +1279,6 @@ textarea:focus {
|
|
|
1279
1279
|
background: rgba(0, 0, 0, .18);
|
|
1280
1280
|
}
|
|
1281
1281
|
|
|
1282
|
-
/* ── Image Lightbox Modal ── */
|
|
1283
|
-
.img-lightbox {
|
|
1284
|
-
display: none;
|
|
1285
|
-
position: fixed;
|
|
1286
|
-
inset: 0;
|
|
1287
|
-
z-index: 10000;
|
|
1288
|
-
background: rgba(0, 0, 0, 0.85);
|
|
1289
|
-
backdrop-filter: blur(8px);
|
|
1290
|
-
-webkit-backdrop-filter: blur(8px);
|
|
1291
|
-
justify-content: center;
|
|
1292
|
-
align-items: center;
|
|
1293
|
-
cursor: zoom-out;
|
|
1294
|
-
animation: lbFadeIn 0.15s ease;
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
.img-lightbox.active {
|
|
1298
|
-
display: flex;
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
@keyframes lbFadeIn {
|
|
1302
|
-
from { opacity: 0; }
|
|
1303
|
-
to { opacity: 1; }
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
.img-lightbox img {
|
|
1307
|
-
max-width: 92vw;
|
|
1308
|
-
max-height: 88vh;
|
|
1309
|
-
border-radius: 10px;
|
|
1310
|
-
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
|
|
1311
|
-
object-fit: contain;
|
|
1312
|
-
cursor: default;
|
|
1313
|
-
animation: lbZoomIn 0.2s ease;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
@keyframes lbZoomIn {
|
|
1317
|
-
from { transform: scale(0.9); opacity: 0; }
|
|
1318
|
-
to { transform: scale(1); opacity: 1; }
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
.img-lightbox-close {
|
|
1322
|
-
position: absolute;
|
|
1323
|
-
top: 16px;
|
|
1324
|
-
right: 20px;
|
|
1325
|
-
width: 36px;
|
|
1326
|
-
height: 36px;
|
|
1327
|
-
border-radius: 50%;
|
|
1328
|
-
background: rgba(255, 255, 255, 0.12);
|
|
1329
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
1330
|
-
color: #fff;
|
|
1331
|
-
font-size: 20px;
|
|
1332
|
-
cursor: pointer;
|
|
1333
|
-
display: flex;
|
|
1334
|
-
align-items: center;
|
|
1335
|
-
justify-content: center;
|
|
1336
|
-
transition: background 0.15s;
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
.img-lightbox-close:hover {
|
|
1340
|
-
background: rgba(255, 255, 255, 0.25);
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
.img-lightbox-name {
|
|
1344
|
-
position: absolute;
|
|
1345
|
-
bottom: 16px;
|
|
1346
|
-
left: 50%;
|
|
1347
|
-
transform: translateX(-50%);
|
|
1348
|
-
color: rgba(255, 255, 255, 0.7);
|
|
1349
|
-
font-size: 12px;
|
|
1350
|
-
background: rgba(0, 0, 0, 0.4);
|
|
1351
|
-
padding: 4px 14px;
|
|
1352
|
-
border-radius: 20px;
|
|
1353
|
-
max-width: 80vw;
|
|
1354
|
-
overflow: hidden;
|
|
1355
|
-
text-overflow: ellipsis;
|
|
1356
|
-
white-space: nowrap;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
/* ── Paste Preview Strip ── */
|
|
1360
|
-
.paste-thumb {
|
|
1361
|
-
position: relative;
|
|
1362
|
-
display: inline-block;
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
.paste-thumb-remove {
|
|
1366
|
-
position: absolute;
|
|
1367
|
-
top: -6px;
|
|
1368
|
-
right: -6px;
|
|
1369
|
-
width: 18px;
|
|
1370
|
-
height: 18px;
|
|
1371
|
-
border-radius: 50%;
|
|
1372
|
-
background: var(--danger, #ef4444);
|
|
1373
|
-
color: #fff;
|
|
1374
|
-
border: none;
|
|
1375
|
-
font-size: 12px;
|
|
1376
|
-
line-height: 18px;
|
|
1377
|
-
text-align: center;
|
|
1378
|
-
cursor: pointer;
|
|
1379
|
-
padding: 0;
|
|
1380
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
.paste-thumb-remove:hover {
|
|
1384
|
-
background: #dc2626;
|
|
1385
|
-
transform: scale(1.1);
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
/* ── Bubble Attachments (images in chat) ── */
|
|
1389
|
-
.bubble-attachments {
|
|
1390
|
-
margin-top: 8px;
|
|
1391
|
-
display: flex;
|
|
1392
|
-
gap: 6px;
|
|
1393
|
-
flex-wrap: wrap;
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
.bubble-img {
|
|
1397
|
-
max-height: 120px;
|
|
1398
|
-
max-width: 200px;
|
|
1399
|
-
border-radius: 8px;
|
|
1400
|
-
border: 1px solid var(--border);
|
|
1401
|
-
cursor: pointer;
|
|
1402
|
-
transition: transform 0.15s ease;
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
.bubble-img:hover {
|
|
1406
|
-
transform: scale(1.05);
|
|
1407
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
1282
|
.chatComposer {
|
|
1411
1283
|
display: none;
|
|
1412
1284
|
}
|
|
@@ -1741,10 +1613,6 @@ textarea:focus {
|
|
|
1741
1613
|
font-size: 11px;
|
|
1742
1614
|
}
|
|
1743
1615
|
|
|
1744
|
-
* {
|
|
1745
|
-
border-radius: 0 !important;
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
1616
|
/* ── Kanban Board ── */
|
|
1749
1617
|
.kanban-board {
|
|
1750
1618
|
display: grid;
|
package/cli/web-ui.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
timelineTag: '',
|
|
22
22
|
chatSessionId: null,
|
|
23
23
|
chatLoaded: false,
|
|
24
|
-
|
|
24
|
+
pendingImage: null // { filename, url, mimeType } from clipboard paste
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
function applyDarkTheme() {
|
|
@@ -196,11 +196,12 @@
|
|
|
196
196
|
var num = 0;
|
|
197
197
|
|
|
198
198
|
// Match append_daily_log / appenddailylog actions
|
|
199
|
-
var logRe = /"type"\s*:\s*"append_?daily_?log"\s*,\s*"text"\s*:\s*"([^"]{1,
|
|
199
|
+
var logRe = /"type"\s*:\s*"append_?daily_?log"\s*,\s*"text"\s*:\s*"([^"]{1,300})/gi;
|
|
200
200
|
var m;
|
|
201
201
|
while ((m = logRe.exec(text)) !== null) {
|
|
202
202
|
num++;
|
|
203
|
-
|
|
203
|
+
var t = m[1].slice(0, 140);
|
|
204
|
+
lines.push(num + '. \u{1F4DD} **Registrar no log:** ' + t + (m[1].length > 140 ? '...' : ''));
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
// Match create_task actions
|
|
@@ -246,8 +247,8 @@
|
|
|
246
247
|
var num = i + 1;
|
|
247
248
|
|
|
248
249
|
if (type === 'appenddailylog') {
|
|
249
|
-
var t = String(a.text || '');
|
|
250
|
-
return num + '. ' + icon + ' **Registrar no log:** ' + t;
|
|
250
|
+
var t = String(a.text || '').slice(0, 140);
|
|
251
|
+
return num + '. ' + icon + ' **Registrar no log:** ' + t + (String(a.text || '').length > 140 ? '...' : '');
|
|
251
252
|
}
|
|
252
253
|
if (type === 'createtask') {
|
|
253
254
|
var desc = String(a.description || '').slice(0, 120);
|
|
@@ -503,32 +504,32 @@
|
|
|
503
504
|
const tag = $('chatModeTag');
|
|
504
505
|
if (tag) { tag.textContent = '🔍 oracle'; tag.style.display = ''; tag.style.color = 'var(--accent)'; tag.style.borderColor = 'var(--accent)'; }
|
|
505
506
|
|
|
506
|
-
//
|
|
507
|
-
var
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
if (askAttachments.length) {
|
|
512
|
-
var askHtml = escapeHtml(query).replace(/\n/g, '<br>');
|
|
513
|
-
askHtml += '<div class="bubble-attachments">';
|
|
514
|
-
for (var ai = 0; ai < askAttachments.length; ai++) {
|
|
515
|
-
askHtml += '<img src="/attachments/' + askAttachments[ai].path.replace('data/attachments/', '') + '" class="bubble-img" alt="' + escapeHtml(askAttachments[ai].name) + '" />';
|
|
516
|
-
}
|
|
517
|
-
askHtml += '</div>';
|
|
518
|
-
chatAppend('user', askHtml, { html: true });
|
|
507
|
+
// Include pasted image if present
|
|
508
|
+
var pendingImg = state.pendingImage;
|
|
509
|
+
if (pendingImg) {
|
|
510
|
+
chatAppend('user', escapeHtml(query) + '<br><img src="' + pendingImg.url + '" style="max-height:80px;border-radius:4px;margin-top:6px;cursor:pointer;" onclick="openLightbox(\'' + pendingImg.url + '\')" />', { html: true });
|
|
519
511
|
} else {
|
|
520
512
|
chatAppend('user', query);
|
|
521
513
|
}
|
|
522
|
-
clearPastedImages();
|
|
523
514
|
syncChatThreadVisibility();
|
|
524
|
-
|
|
515
|
+
|
|
516
|
+
// Clear image preview
|
|
517
|
+
var preview = document.getElementById('pastePreview');
|
|
518
|
+
if (preview) preview.remove();
|
|
519
|
+
|
|
520
|
+
setPill('run', 'pesquisando…');
|
|
525
521
|
|
|
526
522
|
const typingId = 'typing-' + Date.now();
|
|
527
523
|
chatAppend('assistant', '<div class="typing-indicator"><span></span><span></span><span></span></div>', { id: typingId, html: true });
|
|
528
524
|
|
|
529
525
|
try {
|
|
530
526
|
const sessionId = ensureChatSession();
|
|
531
|
-
|
|
527
|
+
var askPayload = { dir: dirOrDefault(), sessionId, query };
|
|
528
|
+
if (pendingImg) {
|
|
529
|
+
askPayload.imagePath = 'data/attachments/' + pendingImg.filename;
|
|
530
|
+
}
|
|
531
|
+
state.pendingImage = null;
|
|
532
|
+
const r = await api('/api/chat/ask', askPayload);
|
|
532
533
|
const answer = r && r.answer ? r.answer : 'Não encontrei registro';
|
|
533
534
|
|
|
534
535
|
const el = $(typingId);
|
|
@@ -2362,42 +2363,36 @@
|
|
|
2362
2363
|
const tag = $('chatModeTag');
|
|
2363
2364
|
if (tag) { tag.textContent = '📥 inbox'; tag.style.display = ''; tag.style.color = 'var(--primary)'; tag.style.borderColor = 'var(--primary)'; }
|
|
2364
2365
|
|
|
2365
|
-
//
|
|
2366
|
-
var
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
setPill('run', 'enviando imagens…');
|
|
2370
|
-
attachments = await uploadPastedImages();
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
|
-
// Show user message with image thumbnails
|
|
2374
|
-
var userHtml = escapeHtml(text).replace(/\n/g, '<br>');
|
|
2375
|
-
if (attachments.length) {
|
|
2376
|
-
userHtml += '<div class="bubble-attachments">';
|
|
2377
|
-
for (var ai = 0; ai < attachments.length; ai++) {
|
|
2378
|
-
userHtml += '<img src="/attachments/' + attachments[ai].path.replace('data/attachments/', '') + '" class="bubble-img" alt="' + escapeHtml(attachments[ai].name) + '" />';
|
|
2379
|
-
}
|
|
2380
|
-
userHtml += '</div>';
|
|
2381
|
-
chatAppend('user', userHtml, { html: true });
|
|
2366
|
+
// Include pasted image if present
|
|
2367
|
+
var pendingImg = state.pendingImage;
|
|
2368
|
+
if (pendingImg) {
|
|
2369
|
+
chatAppend('user', escapeHtml(text) + '<br><img src="' + pendingImg.url + '" style="max-height:80px;border-radius:4px;margin-top:6px;cursor:pointer;" onclick="openLightbox(\'' + pendingImg.url + '\')" />', { html: true });
|
|
2382
2370
|
} else {
|
|
2383
2371
|
chatAppend('user', text);
|
|
2384
2372
|
}
|
|
2385
|
-
clearPastedImages();
|
|
2386
2373
|
syncChatThreadVisibility();
|
|
2387
2374
|
|
|
2388
|
-
//
|
|
2389
|
-
var
|
|
2390
|
-
|
|
2375
|
+
// Clear image preview
|
|
2376
|
+
var preview = document.getElementById('pastePreview');
|
|
2377
|
+
if (preview) preview.remove();
|
|
2378
|
+
|
|
2379
|
+
// Show typing indicator while processing
|
|
2380
|
+
var typingId = 'typing-' + Date.now();
|
|
2381
|
+
var typingHtml = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
|
|
2382
|
+
chatAppend('assistant', typingHtml, { id: typingId, html: true });
|
|
2391
2383
|
|
|
2392
2384
|
setPill('run', 'salvando…');
|
|
2393
|
-
|
|
2385
|
+
var inboxPayload = { dir: dirOrDefault(), text };
|
|
2386
|
+
if (pendingImg) inboxPayload.imagePath = 'data/attachments/' + pendingImg.filename;
|
|
2387
|
+
state.pendingImage = null;
|
|
2388
|
+
await api('/api/inbox/add', inboxPayload);
|
|
2394
2389
|
|
|
2395
|
-
setPill('run',
|
|
2396
|
-
const r = await api('/api/agents/plan', { dir: dirOrDefault(), text
|
|
2390
|
+
setPill('run', 'processando…');
|
|
2391
|
+
const r = await api('/api/agents/plan', { dir: dirOrDefault(), text });
|
|
2397
2392
|
|
|
2398
2393
|
// Remove typing indicator
|
|
2399
|
-
var
|
|
2400
|
-
if (
|
|
2394
|
+
var typingEl = $(typingId);
|
|
2395
|
+
if (typingEl) typingEl.remove();
|
|
2401
2396
|
|
|
2402
2397
|
state.lastPlan = r.plan || '';
|
|
2403
2398
|
|
|
@@ -2416,12 +2411,8 @@
|
|
|
2416
2411
|
}
|
|
2417
2412
|
|
|
2418
2413
|
if (state.autoApply) {
|
|
2419
|
-
|
|
2420
|
-
chatAppend('assistant', '<div class="typing-indicator"><span></span><span></span><span></span></div>', { id: applyTypingId, html: true });
|
|
2421
|
-
setPill('run', 'aplicando…');
|
|
2414
|
+
setPill('run', 'applying…');
|
|
2422
2415
|
await applyPlan();
|
|
2423
|
-
var applyEl = $(applyTypingId);
|
|
2424
|
-
if (applyEl) applyEl.remove();
|
|
2425
2416
|
const a = state.lastApplied || {};
|
|
2426
2417
|
setPill('ok', `applied(${a.tasks || 0}t, ${a.blockers || 0}b)`);
|
|
2427
2418
|
if (state.autoRunReports) {
|
|
@@ -2485,23 +2476,31 @@
|
|
|
2485
2476
|
state.lastApplied = r.applied || null;
|
|
2486
2477
|
const summary = r.applied || {};
|
|
2487
2478
|
|
|
2488
|
-
// Build
|
|
2479
|
+
// Build natural language summary instead of raw JSON
|
|
2489
2480
|
var parts = [];
|
|
2490
|
-
var
|
|
2491
|
-
var
|
|
2492
|
-
|
|
2493
|
-
|
|
2481
|
+
var tc = (summary.tasks || 0), bc = (summary.blockers || 0);
|
|
2482
|
+
var ts = (summary.tasksSkipped || 0), bs = (summary.blockersSkipped || 0);
|
|
2483
|
+
if (tc > 0) parts.push('**' + tc + ' tarefa' + (tc > 1 ? 's' : '') + '** registrada' + (tc > 1 ? 's' : ''));
|
|
2484
|
+
if (bc > 0) parts.push('**' + bc + ' blocker' + (bc > 1 ? 's' : '') + '** registrado' + (bc > 1 ? 's' : ''));
|
|
2485
|
+
if (ts > 0) parts.push(ts + ' tarefa' + (ts > 1 ? 's' : '') + ' já existente' + (ts > 1 ? 's' : '') + ' (ignorada' + (ts > 1 ? 's' : '') + ')');
|
|
2486
|
+
if (bs > 0) parts.push(bs + ' blocker' + (bs > 1 ? 's' : '') + ' já existente' + (bs > 1 ? 's' : '') + ' (ignorado' + (bs > 1 ? 's' : '') + ')');
|
|
2487
|
+
if (summary.mode) parts.push('Modo: **' + summary.mode + '**');
|
|
2494
2488
|
|
|
2495
|
-
|
|
2496
|
-
if (
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
if (tasks === 0 && blockers === 0) parts.push('📝 Informação registrada no log diário');
|
|
2489
|
+
var oq = summary.oracleQueries;
|
|
2490
|
+
if (oq && Array.isArray(oq) && oq.length > 0) {
|
|
2491
|
+
parts.push(oq.length + ' consulta' + (oq.length > 1 ? 's' : '') + ' realizada' + (oq.length > 1 ? 's' : '') + ' no histórico');
|
|
2492
|
+
}
|
|
2500
2493
|
|
|
2501
|
-
let msg =
|
|
2494
|
+
let msg = '## Resultado\n\n';
|
|
2495
|
+
if (parts.length > 0) {
|
|
2496
|
+
msg += parts.join(' · ') + '\n';
|
|
2497
|
+
} else if (tc === 0 && bc === 0) {
|
|
2498
|
+
msg += 'Contexto registrado no log diário. Nenhuma tarefa ou blocker identificado.\n';
|
|
2499
|
+
}
|
|
2502
2500
|
|
|
2503
2501
|
if (summary && Array.isArray(summary.reportsSuggested) && summary.reportsSuggested.length) {
|
|
2504
|
-
msg += '\n
|
|
2502
|
+
msg += '\n**Relatórios sugeridos:** ' + summary.reportsSuggested.join(', ');
|
|
2503
|
+
msg += '\n\nUse: **Rodar relatórios sugeridos** (barra lateral)';
|
|
2505
2504
|
}
|
|
2506
2505
|
|
|
2507
2506
|
setOut(msg);
|
|
@@ -2527,6 +2526,98 @@
|
|
|
2527
2526
|
loadLocal();
|
|
2528
2527
|
wireRailNav();
|
|
2529
2528
|
|
|
2529
|
+
// ── Image paste (Ctrl+V) support ──
|
|
2530
|
+
(function setupImagePaste() {
|
|
2531
|
+
var ta = $('inboxText');
|
|
2532
|
+
if (!ta) return;
|
|
2533
|
+
|
|
2534
|
+
ta.addEventListener('paste', async function(e) {
|
|
2535
|
+
var items = e.clipboardData && e.clipboardData.items;
|
|
2536
|
+
if (!items) return;
|
|
2537
|
+
for (var i = 0; i < items.length; i++) {
|
|
2538
|
+
if (items[i].type.indexOf('image') !== -1) {
|
|
2539
|
+
e.preventDefault();
|
|
2540
|
+
var file = items[i].getAsFile();
|
|
2541
|
+
if (!file) return;
|
|
2542
|
+
var reader = new FileReader();
|
|
2543
|
+
reader.onload = async function(ev) {
|
|
2544
|
+
var base64 = ev.target.result.split(',')[1];
|
|
2545
|
+
var mimeType = file.type || 'image/png';
|
|
2546
|
+
try {
|
|
2547
|
+
var r = await api('/api/attachments/upload', { dir: dirOrDefault(), data: base64, mimeType: mimeType });
|
|
2548
|
+
if (r && r.ok) {
|
|
2549
|
+
state.pendingImage = { filename: r.filename, url: r.url, mimeType: mimeType };
|
|
2550
|
+
showImagePreview(r.url, r.filename);
|
|
2551
|
+
}
|
|
2552
|
+
} catch (err) {
|
|
2553
|
+
console.error('Image upload failed:', err);
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2556
|
+
reader.readAsDataURL(file);
|
|
2557
|
+
break;
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
});
|
|
2561
|
+
})();
|
|
2562
|
+
|
|
2563
|
+
function showImagePreview(url, filename) {
|
|
2564
|
+
var existing = document.getElementById('pastePreview');
|
|
2565
|
+
if (existing) existing.remove();
|
|
2566
|
+
var ta = $('inboxText');
|
|
2567
|
+
if (!ta) return;
|
|
2568
|
+
var container = document.createElement('div');
|
|
2569
|
+
container.id = 'pastePreview';
|
|
2570
|
+
container.style.cssText = 'display:flex; align-items:center; gap:8px; padding:6px 10px; background:var(--surface); border:1px solid var(--border); border-radius:6px; margin-top:6px;';
|
|
2571
|
+
var img = document.createElement('img');
|
|
2572
|
+
img.src = url;
|
|
2573
|
+
img.style.cssText = 'max-height:48px; max-width:80px; border-radius:4px; cursor:pointer;';
|
|
2574
|
+
img.onclick = function() { openLightbox(url); };
|
|
2575
|
+
var label = document.createElement('span');
|
|
2576
|
+
label.textContent = '📎 ' + filename;
|
|
2577
|
+
label.style.cssText = 'font-size:12px; color:var(--muted); flex:1;';
|
|
2578
|
+
var removeBtn = document.createElement('button');
|
|
2579
|
+
removeBtn.textContent = '✕';
|
|
2580
|
+
removeBtn.style.cssText = 'background:none; border:none; color:var(--muted); cursor:pointer; font-size:14px;';
|
|
2581
|
+
removeBtn.onclick = function() { container.remove(); state.pendingImage = null; };
|
|
2582
|
+
container.appendChild(img);
|
|
2583
|
+
container.appendChild(label);
|
|
2584
|
+
container.appendChild(removeBtn);
|
|
2585
|
+
ta.parentElement.insertBefore(container, ta.nextSibling);
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// ── Image lightbox modal ──
|
|
2589
|
+
function openLightbox(url) {
|
|
2590
|
+
var existing = document.getElementById('freyaLightbox');
|
|
2591
|
+
if (existing) existing.remove();
|
|
2592
|
+
var overlay = document.createElement('div');
|
|
2593
|
+
overlay.id = 'freyaLightbox';
|
|
2594
|
+
overlay.style.cssText = 'position:fixed; inset:0; background:rgba(0,0,0,0.85); display:flex; align-items:center; justify-content:center; z-index:99999; cursor:pointer;';
|
|
2595
|
+
overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };
|
|
2596
|
+
var img = document.createElement('img');
|
|
2597
|
+
img.src = url;
|
|
2598
|
+
img.style.cssText = 'max-width:90vw; max-height:90vh; border-radius:8px; box-shadow:0 8px 40px rgba(0,0,0,0.6);';
|
|
2599
|
+
var closeBtn = document.createElement('button');
|
|
2600
|
+
closeBtn.textContent = '✕';
|
|
2601
|
+
closeBtn.style.cssText = 'position:absolute; top:20px; right:20px; background:rgba(255,255,255,0.15); border:none; color:white; font-size:24px; width:40px; height:40px; border-radius:50%; cursor:pointer;';
|
|
2602
|
+
closeBtn.onclick = function() { overlay.remove(); };
|
|
2603
|
+
overlay.appendChild(img);
|
|
2604
|
+
overlay.appendChild(closeBtn);
|
|
2605
|
+
document.body.appendChild(overlay);
|
|
2606
|
+
// ESC to close
|
|
2607
|
+
function onEsc(e) { if (e.key === 'Escape') { overlay.remove(); document.removeEventListener('keydown', onEsc); } }
|
|
2608
|
+
document.addEventListener('keydown', onEsc);
|
|
2609
|
+
}
|
|
2610
|
+
window.openLightbox = openLightbox;
|
|
2611
|
+
|
|
2612
|
+
// Make all chat images clickable for lightbox (delegate)
|
|
2613
|
+
document.addEventListener('click', function(e) {
|
|
2614
|
+
var img = e.target;
|
|
2615
|
+
if (img.tagName === 'IMG' && img.closest && img.closest('#chatThread')) {
|
|
2616
|
+
e.preventDefault();
|
|
2617
|
+
openLightbox(img.src);
|
|
2618
|
+
}
|
|
2619
|
+
});
|
|
2620
|
+
|
|
2530
2621
|
// Developer drawer (persist open/close)
|
|
2531
2622
|
try {
|
|
2532
2623
|
const d = $('devDrawer');
|
|
@@ -2616,149 +2707,8 @@
|
|
|
2616
2707
|
refreshToday();
|
|
2617
2708
|
reloadSlugRules();
|
|
2618
2709
|
loadChatHistory();
|
|
2619
|
-
initPasteHandler();
|
|
2620
|
-
|
|
2621
|
-
// Click on chat images → open lightbox
|
|
2622
|
-
var thread = $('chatThread');
|
|
2623
|
-
if (thread) {
|
|
2624
|
-
thread.addEventListener('click', function(e) {
|
|
2625
|
-
if (e.target && e.target.classList && e.target.classList.contains('bubble-img')) {
|
|
2626
|
-
openLightbox(e.target.src, e.target.alt || 'Anexo');
|
|
2627
|
-
}
|
|
2628
|
-
});
|
|
2629
|
-
}
|
|
2630
2710
|
})();
|
|
2631
2711
|
|
|
2632
|
-
// ── Image Paste Handler ──────────────────────────────────────
|
|
2633
|
-
function initPasteHandler() {
|
|
2634
|
-
var ta = $('inboxText');
|
|
2635
|
-
if (!ta) return;
|
|
2636
|
-
|
|
2637
|
-
ta.addEventListener('paste', function(e) {
|
|
2638
|
-
var items = e.clipboardData && e.clipboardData.items;
|
|
2639
|
-
if (!items) return;
|
|
2640
|
-
|
|
2641
|
-
for (var i = 0; i < items.length; i++) {
|
|
2642
|
-
if (items[i].type.indexOf('image/') === 0) {
|
|
2643
|
-
e.preventDefault();
|
|
2644
|
-
var file = items[i].getAsFile();
|
|
2645
|
-
if (!file) continue;
|
|
2646
|
-
|
|
2647
|
-
var reader = new FileReader();
|
|
2648
|
-
reader.onload = function(ev) {
|
|
2649
|
-
var ext = file.type.split('/')[1] || 'png';
|
|
2650
|
-
if (ext === 'jpeg') ext = 'jpg';
|
|
2651
|
-
var ts = Date.now();
|
|
2652
|
-
var name = 'paste-' + ts + '.' + ext;
|
|
2653
|
-
state.pastedImages.push({ file: file, dataUrl: ev.target.result, name: name });
|
|
2654
|
-
renderPastePreview();
|
|
2655
|
-
};
|
|
2656
|
-
reader.readAsDataURL(file);
|
|
2657
|
-
break; // handle one image per paste
|
|
2658
|
-
}
|
|
2659
|
-
}
|
|
2660
|
-
});
|
|
2661
|
-
}
|
|
2662
|
-
|
|
2663
|
-
function renderPastePreview() {
|
|
2664
|
-
var strip = $('pastePreview');
|
|
2665
|
-
var inner = $('pastePreviewInner');
|
|
2666
|
-
if (!strip || !inner) return;
|
|
2667
|
-
|
|
2668
|
-
if (!state.pastedImages.length) {
|
|
2669
|
-
strip.style.display = 'none';
|
|
2670
|
-
inner.innerHTML = '';
|
|
2671
|
-
return;
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
strip.style.display = 'block';
|
|
2675
|
-
inner.innerHTML = '';
|
|
2676
|
-
|
|
2677
|
-
for (var i = 0; i < state.pastedImages.length; i++) {
|
|
2678
|
-
(function(idx) {
|
|
2679
|
-
var img = state.pastedImages[idx];
|
|
2680
|
-
var wrap = document.createElement('div');
|
|
2681
|
-
wrap.className = 'paste-thumb';
|
|
2682
|
-
|
|
2683
|
-
var thumb = document.createElement('img');
|
|
2684
|
-
thumb.src = img.dataUrl;
|
|
2685
|
-
thumb.alt = img.name;
|
|
2686
|
-
thumb.style.cssText = 'max-height:60px; max-width:100px; border-radius:6px; border:1px solid var(--border); cursor:pointer;';
|
|
2687
|
-
thumb.title = img.name;
|
|
2688
|
-
thumb.onclick = function() {
|
|
2689
|
-
openLightbox(img.dataUrl, img.name);
|
|
2690
|
-
};
|
|
2691
|
-
|
|
2692
|
-
var removeBtn = document.createElement('button');
|
|
2693
|
-
removeBtn.className = 'paste-thumb-remove';
|
|
2694
|
-
removeBtn.innerHTML = '×';
|
|
2695
|
-
removeBtn.title = 'Remover';
|
|
2696
|
-
removeBtn.onclick = function() {
|
|
2697
|
-
state.pastedImages.splice(idx, 1);
|
|
2698
|
-
renderPastePreview();
|
|
2699
|
-
};
|
|
2700
|
-
|
|
2701
|
-
wrap.appendChild(thumb);
|
|
2702
|
-
wrap.appendChild(removeBtn);
|
|
2703
|
-
inner.appendChild(wrap);
|
|
2704
|
-
})(i);
|
|
2705
|
-
}
|
|
2706
|
-
|
|
2707
|
-
// label
|
|
2708
|
-
var label = document.createElement('span');
|
|
2709
|
-
label.style.cssText = 'font-size:11px; color:var(--faint); margin-left:4px;';
|
|
2710
|
-
label.textContent = state.pastedImages.length + ' imagem(ns) anexada(s)';
|
|
2711
|
-
inner.appendChild(label);
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
// Upload pasted images and return array of { name, path }
|
|
2715
|
-
async function uploadPastedImages() {
|
|
2716
|
-
var results = [];
|
|
2717
|
-
for (var i = 0; i < state.pastedImages.length; i++) {
|
|
2718
|
-
var img = state.pastedImages[i];
|
|
2719
|
-
try {
|
|
2720
|
-
var r = await api('/api/attachments/upload', {
|
|
2721
|
-
dir: dirOrDefault(),
|
|
2722
|
-
data: img.dataUrl,
|
|
2723
|
-
filename: img.name
|
|
2724
|
-
});
|
|
2725
|
-
if (r && r.ok) {
|
|
2726
|
-
results.push({ name: img.name, path: r.path });
|
|
2727
|
-
}
|
|
2728
|
-
} catch(err) {
|
|
2729
|
-
// best-effort, skip failed uploads
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
return results;
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
function clearPastedImages() {
|
|
2736
|
-
state.pastedImages = [];
|
|
2737
|
-
renderPastePreview();
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
// ── Image Lightbox ──────────────────────────────────────────
|
|
2741
|
-
function openLightbox(src, name) {
|
|
2742
|
-
var lb = $('imgLightbox');
|
|
2743
|
-
var img = $('imgLightboxImg');
|
|
2744
|
-
var label = $('imgLightboxName');
|
|
2745
|
-
if (!lb || !img) return;
|
|
2746
|
-
img.src = src;
|
|
2747
|
-
if (label) label.textContent = name || '';
|
|
2748
|
-
lb.classList.add('active');
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
function closeLightbox(e) {
|
|
2752
|
-
if (e) e.stopPropagation();
|
|
2753
|
-
var lb = $('imgLightbox');
|
|
2754
|
-
if (lb) lb.classList.remove('active');
|
|
2755
|
-
var img = $('imgLightboxImg');
|
|
2756
|
-
if (img) img.src = '';
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
window.openLightbox = openLightbox;
|
|
2760
|
-
window.closeLightbox = closeLightbox;
|
|
2761
|
-
|
|
2762
2712
|
setPill('ok', 'pronto');
|
|
2763
2713
|
|
|
2764
2714
|
/* ── Global Keyboard Shortcuts ── */
|
|
@@ -2775,13 +2725,8 @@
|
|
|
2775
2725
|
openQuickAdd();
|
|
2776
2726
|
return;
|
|
2777
2727
|
}
|
|
2778
|
-
// Escape: Close
|
|
2728
|
+
// Escape: Close quick-add modal or blur active element
|
|
2779
2729
|
if (e.key === 'Escape') {
|
|
2780
|
-
const lb = $('imgLightbox');
|
|
2781
|
-
if (lb && lb.classList.contains('active')) {
|
|
2782
|
-
closeLightbox();
|
|
2783
|
-
return;
|
|
2784
|
-
}
|
|
2785
2730
|
const overlay = $('quickAddOverlay');
|
|
2786
2731
|
if (overlay && overlay.style.display !== 'none') {
|
|
2787
2732
|
closeQuickAdd();
|
package/cli/web.js
CHANGED
|
@@ -1192,14 +1192,9 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
1192
1192
|
</div>
|
|
1193
1193
|
|
|
1194
1194
|
<!-- Textarea -->
|
|
1195
|
-
<textarea id="inboxText" aria-label="Entrada de texto para processar ou perguntar" placeholder="Cole updates, decisões, blockers... ou faça uma pergunta à Freya. ▸ Salvar & Processar → extrai tarefas e blockers do texto ▸ Perguntar → consulta o histórico via busca semântica (RAG)
|
|
1195
|
+
<textarea id="inboxText" aria-label="Entrada de texto para processar ou perguntar" placeholder="Cole updates, decisões, blockers... ou faça uma pergunta à Freya. ▸ Salvar & Processar → extrai tarefas e blockers do texto ▸ Perguntar → consulta o histórico via busca semântica (RAG)" style="resize:none; min-height: 200px; flex: 1; border-radius: 0; border-left: none; border-right: none; border-top: none; border-bottom: 1px solid var(--border); padding: 14px 16px; font-size: 13px; line-height: 1.6;"
|
|
1196
1196
|
onkeydown="if((event.metaKey||event.ctrlKey)&&event.key==='Enter'){event.preventDefault();window.saveAndPlan();}"></textarea>
|
|
1197
1197
|
|
|
1198
|
-
<!-- Image paste preview strip -->
|
|
1199
|
-
<div id="pastePreview" style="display:none; padding: 8px 14px; border-bottom: 1px solid var(--border); background: rgba(0,0,0,0.05); flex-shrink:0;">
|
|
1200
|
-
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap;" id="pastePreviewInner"></div>
|
|
1201
|
-
</div>
|
|
1202
|
-
|
|
1203
1198
|
<!-- Actions bar -->
|
|
1204
1199
|
<div style="padding: 10px 14px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; flex-shrink:0;">
|
|
1205
1200
|
<div style="display:flex; gap:8px; flex-wrap:wrap;">
|
|
@@ -1351,13 +1346,6 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
1351
1346
|
</div>
|
|
1352
1347
|
</div>
|
|
1353
1348
|
|
|
1354
|
-
<!-- Image lightbox modal -->
|
|
1355
|
-
<div id="imgLightbox" class="img-lightbox" onclick="window.closeLightbox(event)">
|
|
1356
|
-
<button class="img-lightbox-close" onclick="window.closeLightbox(event)" title="Fechar (Esc)">×</button>
|
|
1357
|
-
<img id="imgLightboxImg" src="" alt="preview" onclick="event.stopPropagation()" />
|
|
1358
|
-
<div id="imgLightboxName" class="img-lightbox-name"></div>
|
|
1359
|
-
</div>
|
|
1360
|
-
|
|
1361
1349
|
<script>
|
|
1362
1350
|
window.__FREYA_DEFAULT_DIR = "${safeDefault}";
|
|
1363
1351
|
</script>
|
|
@@ -2408,26 +2396,25 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
2408
2396
|
return;
|
|
2409
2397
|
}
|
|
2410
2398
|
|
|
2411
|
-
// Serve attachment images
|
|
2399
|
+
// Serve attachment images from workspace data/attachments/
|
|
2412
2400
|
if (req.method === 'GET' && req.url.startsWith('/attachments/')) {
|
|
2413
|
-
const
|
|
2401
|
+
const fname = decodeURIComponent(req.url.slice('/attachments/'.length)).replace(/[\/\\]/g, '');
|
|
2414
2402
|
const requestedDir = dir || './freya';
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
if (!filePath
|
|
2419
|
-
res.writeHead(
|
|
2420
|
-
|
|
2421
|
-
if (exists(filePath)) {
|
|
2422
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
2423
|
-
const mimeMap = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp' };
|
|
2424
|
-
const mime = mimeMap[ext] || 'application/octet-stream';
|
|
2425
|
-
const data = fs.readFileSync(filePath);
|
|
2426
|
-
res.writeHead(200, { 'Content-Type': mime, 'Cache-Control': 'public, max-age=86400' });
|
|
2427
|
-
res.end(data);
|
|
2403
|
+
let wsDir;
|
|
2404
|
+
try { wsDir = normalizeWorkspaceDir(requestedDir); } catch { wsDir = path.resolve(process.cwd(), requestedDir); }
|
|
2405
|
+
const filePath = path.join(wsDir, 'data', 'attachments', fname);
|
|
2406
|
+
if (!exists(filePath)) {
|
|
2407
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
2408
|
+
res.end('Not found');
|
|
2428
2409
|
return;
|
|
2429
2410
|
}
|
|
2430
|
-
|
|
2411
|
+
const ext = path.extname(fname).toLowerCase();
|
|
2412
|
+
const mimeMap = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp', '.svg': 'image/svg+xml' };
|
|
2413
|
+
const mime = mimeMap[ext] || 'application/octet-stream';
|
|
2414
|
+
const data = fs.readFileSync(filePath);
|
|
2415
|
+
res.writeHead(200, { 'Content-Type': mime, 'Cache-Control': 'max-age=86400' });
|
|
2416
|
+
res.end(data);
|
|
2417
|
+
return;
|
|
2431
2418
|
}
|
|
2432
2419
|
|
|
2433
2420
|
if (req.url.startsWith('/api/')) {
|
|
@@ -3240,30 +3227,9 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3240
3227
|
}
|
|
3241
3228
|
}
|
|
3242
3229
|
|
|
3243
|
-
// ── Upload attachment (image paste) ──────────────────────────
|
|
3244
|
-
if (req.url === '/api/attachments/upload') {
|
|
3245
|
-
const data = String(payload.data || '').trim();
|
|
3246
|
-
const fname = String(payload.filename || '').trim();
|
|
3247
|
-
if (!data || !fname) return safeJson(res, 400, { error: 'Missing data or filename' });
|
|
3248
|
-
|
|
3249
|
-
const d = isoDate();
|
|
3250
|
-
const attDir = path.join(workspaceDir, 'data', 'attachments', d);
|
|
3251
|
-
ensureDir(attDir);
|
|
3252
|
-
|
|
3253
|
-
// data is base64 (may have data:image/... prefix)
|
|
3254
|
-
const base64 = data.replace(/^data:image\/[^;]+;base64,/, '');
|
|
3255
|
-
const buf = Buffer.from(base64, 'base64');
|
|
3256
|
-
const filePath = path.join(attDir, fname);
|
|
3257
|
-
fs.writeFileSync(filePath, buf);
|
|
3258
|
-
|
|
3259
|
-
const relPath = `data/attachments/${d}/${fname}`;
|
|
3260
|
-
return safeJson(res, 200, { ok: true, path: relPath });
|
|
3261
|
-
}
|
|
3262
|
-
|
|
3263
3230
|
if (req.url === '/api/inbox/add') {
|
|
3264
3231
|
const text = String(payload.text || '').trim();
|
|
3265
|
-
|
|
3266
|
-
if (!text && !attachments.length) return safeJson(res, 400, { error: 'Missing text' });
|
|
3232
|
+
if (!text) return safeJson(res, 400, { error: 'Missing text' });
|
|
3267
3233
|
|
|
3268
3234
|
const d = isoDate();
|
|
3269
3235
|
const file = path.join(workspaceDir, 'logs', 'daily', `${d}.md`);
|
|
@@ -3277,13 +3243,8 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3277
3243
|
const linksText = linkInfo && linkInfo.linksText ? linkInfo.linksText : '';
|
|
3278
3244
|
const slugs = linkInfo && Array.isArray(linkInfo.slugs) ? linkInfo.slugs : [];
|
|
3279
3245
|
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
if (attachments.length) {
|
|
3283
|
-
attachBlock = '\n\n**Anexos:**\n' + attachments.map(a => ``).join('\n');
|
|
3284
|
-
}
|
|
3285
|
-
|
|
3286
|
-
const block = `\n\n## [${hh}:${mm}] Raw Input\n${text}${linksText}${attachBlock}\n`;
|
|
3246
|
+
const imgRef = payload.imagePath ? `\n` : '';
|
|
3247
|
+
const block = `\n\n## [${hh}:${mm}] Raw Input\n${text}${imgRef}${linksText}\n`;
|
|
3287
3248
|
fs.appendFileSync(file, block, 'utf8');
|
|
3288
3249
|
|
|
3289
3250
|
if (slugs.length) {
|
|
@@ -3342,16 +3303,7 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3342
3303
|
]
|
|
3343
3304
|
};
|
|
3344
3305
|
|
|
3345
|
-
|
|
3346
|
-
const attachments = Array.isArray(payload.attachments) ? payload.attachments : [];
|
|
3347
|
-
let imageContext = '';
|
|
3348
|
-
if (attachments.length) {
|
|
3349
|
-
imageContext = '\n\nIMAGENS ANEXADAS (use @caminho para analisar):\n' +
|
|
3350
|
-
attachments.map(a => `- @${path.join(workspaceDir, a.path)} (${a.name})`).join('\n') + '\n' +
|
|
3351
|
-
'Analise as imagens anexadas e inclua as informações visuais no plano.\n';
|
|
3352
|
-
}
|
|
3353
|
-
|
|
3354
|
-
const prompt = `Você é o planner do sistema F.R.E.Y.A.\n\nContexto: vamos receber um input bruto do usuário e propor ações estruturadas.\nRegras: siga os arquivos de regras abaixo.\nSaída: retorne APENAS JSON válido no formato: ${JSON.stringify(schema)}\n\nRestrições:\n- NÃO use code fences (\`\`\`)\n- NÃO inclua texto extra antes/depois do JSON\n- NÃO use quebras de linha dentro de strings (transforme em uma frase única)\n\nREGRAS:${rulesText}${imageContext}\n\nINPUT DO USUÁRIO:\n${text}\n`;
|
|
3306
|
+
const prompt = `Você é o planner do sistema F.R.E.Y.A.\n\nContexto: vamos receber um input bruto do usuário e propor ações estruturadas.\nRegras: siga os arquivos de regras abaixo.\nSaída: retorne APENAS JSON válido no formato: ${JSON.stringify(schema)}\n\nRestrições:\n- NÃO use code fences (\`\`\`)\n- NÃO inclua texto extra antes/depois do JSON\n- NÃO use quebras de linha dentro de strings (transforme em uma frase única)\n\nREGRAS:${rulesText}\n\nINPUT DO USUÁRIO:\n${text}\n`;
|
|
3355
3307
|
|
|
3356
3308
|
// Prefer COPILOT_CMD if provided, otherwise try 'copilot'
|
|
3357
3309
|
const cmd = process.env.COPILOT_CMD || 'copilot';
|
|
@@ -3361,7 +3313,7 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3361
3313
|
// BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
|
|
3362
3314
|
const agentEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
|
|
3363
3315
|
try {
|
|
3364
|
-
const r = await run(cmd, ['-s', '--no-color', '--stream', 'off', '-p', prompt, '--allow-all-tools'
|
|
3316
|
+
const r = await run(cmd, ['-s', '--no-color', '--stream', 'off', '-p', prompt, '--allow-all-tools'], workspaceDir, agentEnv);
|
|
3365
3317
|
const out = (r.stdout + r.stderr).trim();
|
|
3366
3318
|
if (r.code !== 0) {
|
|
3367
3319
|
return safeJson(res, 200, {
|
|
@@ -3707,9 +3659,25 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3707
3659
|
return safeJson(res, r.code === 0 ? 200 : 400, r.code === 0 ? { ok: true, output: out } : { error: out || 'index rebuild failed', output: out });
|
|
3708
3660
|
}
|
|
3709
3661
|
|
|
3662
|
+
// Upload attachment (base64 image from clipboard paste)
|
|
3663
|
+
if (req.url === '/api/attachments/upload') {
|
|
3664
|
+
const base64 = String(payload.data || '').trim();
|
|
3665
|
+
const mimeType = String(payload.mimeType || 'image/png').trim();
|
|
3666
|
+
if (!base64) return safeJson(res, 400, { error: 'Missing data' });
|
|
3667
|
+
const ext = mimeType === 'image/jpeg' ? '.jpg' : mimeType === 'image/gif' ? '.gif' : mimeType === 'image/webp' ? '.webp' : '.png';
|
|
3668
|
+
const fname = 'paste-' + Date.now() + ext;
|
|
3669
|
+
const attachDir = path.join(workspaceDir, 'data', 'attachments');
|
|
3670
|
+
if (!exists(attachDir)) fs.mkdirSync(attachDir, { recursive: true });
|
|
3671
|
+
const filePath = path.join(attachDir, fname);
|
|
3672
|
+
const buf = Buffer.from(base64, 'base64');
|
|
3673
|
+
fs.writeFileSync(filePath, buf);
|
|
3674
|
+
return safeJson(res, 200, { ok: true, filename: fname, url: '/attachments/' + fname });
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3710
3677
|
if (req.url === '/api/chat/ask') {
|
|
3711
3678
|
const sessionId = String(payload.sessionId || '').trim();
|
|
3712
3679
|
const query = String(payload.query || '').trim();
|
|
3680
|
+
const imagePath = payload.imagePath ? String(payload.imagePath).trim() : null;
|
|
3713
3681
|
if (!query) return safeJson(res, 400, { error: 'Missing query' });
|
|
3714
3682
|
|
|
3715
3683
|
const workspaceRulesBase = path.join(workspaceDir, '.agent', 'rules', 'freya');
|
|
@@ -3742,27 +3710,23 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3742
3710
|
console.error('[oracle] RAG search failed (embedder/sharp unavailable), continuing without context:', ragErr.message);
|
|
3743
3711
|
}
|
|
3744
3712
|
|
|
3745
|
-
|
|
3746
|
-
const askAttachments = Array.isArray(payload.attachments) ? payload.attachments : [];
|
|
3747
|
-
let askImageContext = '';
|
|
3748
|
-
const askHasImages = askAttachments.length > 0;
|
|
3749
|
-
if (askHasImages) {
|
|
3750
|
-
askImageContext = '\n\nIMAGENS ANEXADAS (use @caminho para analisar):\n' +
|
|
3751
|
-
askAttachments.map(a => `- @${path.join(workspaceDir, a.path)} (${a.name})`).join('\n') + '\n' +
|
|
3752
|
-
'Analise as imagens anexadas e use-as como contexto para responder à consulta.\n';
|
|
3753
|
-
}
|
|
3754
|
-
|
|
3755
|
-
const prompt = `Você é o agente Oracle do sistema F.R.E.Y.A.\n\nSiga estritamente os arquivos de regras abaixo.\nResponda de forma analítica e consultiva.\n${ragContext}${askImageContext}\n\nREGRAS:${rulesText}\n\nCONSULTA DO USUÁRIO:\n${query}\n`;
|
|
3713
|
+
const prompt = `Você é o agente Oracle do sistema F.R.E.Y.A.\n\nSiga estritamente os arquivos de regras abaixo.\nResponda de forma analítica e consultiva.\n${ragContext}\n\nREGRAS:${rulesText}\n\nCONSULTA DO USUÁRIO:\n${query}\n`;
|
|
3756
3714
|
|
|
3757
3715
|
const cmd = process.env.COPILOT_CMD || 'copilot';
|
|
3758
3716
|
|
|
3759
3717
|
// BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
|
|
3760
3718
|
const oracleEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
|
|
3761
3719
|
try {
|
|
3762
|
-
//
|
|
3763
|
-
const
|
|
3764
|
-
if (
|
|
3765
|
-
|
|
3720
|
+
// Build copilot args; add image if user pasted a screenshot
|
|
3721
|
+
const copilotArgs = ['-s', '--no-color', '--stream', 'off'];
|
|
3722
|
+
if (imagePath) {
|
|
3723
|
+
const absImg = path.isAbsolute(imagePath) ? imagePath : path.join(workspaceDir, imagePath);
|
|
3724
|
+
if (exists(absImg)) {
|
|
3725
|
+
copilotArgs.push('--add-image', absImg);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
copilotArgs.push('-p', prompt);
|
|
3729
|
+
const r = await run(cmd, copilotArgs, workspaceDir, oracleEnv);
|
|
3766
3730
|
const out = (r.stdout + r.stderr).trim();
|
|
3767
3731
|
if (r.code !== 0) {
|
|
3768
3732
|
return safeJson(res, 200, { ok: false, answer: 'Falha na busca do agente Oracle:\n' + (out || 'Exit code != 0'), sessionId });
|
|
@@ -3893,7 +3857,8 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
3893
3857
|
|
|
3894
3858
|
const rawTasks = dl.db.prepare(query).all(...params);
|
|
3895
3859
|
const tasks = rawTasks.map(t => {
|
|
3896
|
-
|
|
3860
|
+
let meta = {};
|
|
3861
|
+
try { meta = t.metadata ? JSON.parse(t.metadata) : {}; } catch (_) {}
|
|
3897
3862
|
return {
|
|
3898
3863
|
id: t.id,
|
|
3899
3864
|
description: t.description,
|
|
@@ -4091,7 +4056,8 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4091
4056
|
|
|
4092
4057
|
const rawBlockers = dl.db.prepare(query).all(...params);
|
|
4093
4058
|
const blockers = rawBlockers.map(b => {
|
|
4094
|
-
|
|
4059
|
+
let meta = {};
|
|
4060
|
+
try { meta = b.metadata ? JSON.parse(b.metadata) : {}; } catch (_) {}
|
|
4095
4061
|
return {
|
|
4096
4062
|
id: b.id,
|
|
4097
4063
|
title: b.title,
|
|
@@ -4671,13 +4637,6 @@ function buildKanbanHtml(safeDefault, appVersion) {
|
|
|
4671
4637
|
</div>
|
|
4672
4638
|
</div>
|
|
4673
4639
|
|
|
4674
|
-
<!-- Image lightbox modal -->
|
|
4675
|
-
<div id="imgLightbox" class="img-lightbox" onclick="window.closeLightbox(event)">
|
|
4676
|
-
<button class="img-lightbox-close" onclick="window.closeLightbox(event)" title="Fechar (Esc)">×</button>
|
|
4677
|
-
<img id="imgLightboxImg" src="" alt="preview" onclick="event.stopPropagation()" />
|
|
4678
|
-
<div id="imgLightboxName" class="img-lightbox-name"></div>
|
|
4679
|
-
</div>
|
|
4680
|
-
|
|
4681
4640
|
<script>
|
|
4682
4641
|
window.__FREYA_DEFAULT_DIR = "${safeDefault}";
|
|
4683
4642
|
</script>
|
package/package.json
CHANGED