@cccarv82/freya 2.20.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.js CHANGED
@@ -21,7 +21,7 @@
21
21
  timelineTag: '',
22
22
  chatSessionId: null,
23
23
  chatLoaded: false,
24
- pastedImages: [] // { file: File, dataUrl: string, name: string }
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,2000})/gi;
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
- lines.push(num + '. \u{1F4DD} **Registrar no log:** ' + m[1]);
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
- // Upload pasted images if any (for visual context in log)
507
- var askAttachments = [];
508
- if (state.pastedImages.length > 0) {
509
- askAttachments = await uploadPastedImages();
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
- setPill('run', askAttachments.length ? 'enviando imagem + pesquisando…' : 'pesquisando…');
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
- const r = await api('/api/chat/ask', { dir: dirOrDefault(), sessionId, query, attachments: askAttachments });
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
- // Upload pasted images if any
2366
- var attachments = [];
2367
- var hasImages = state.pastedImages.length > 0;
2368
- if (hasImages) {
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
- // Typing indicator while processing
2389
- var saveTypingId = 'typing-save-' + Date.now();
2390
- chatAppend('assistant', '<div class="typing-indicator"><span></span><span></span><span></span></div>', { id: saveTypingId, html: true });
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
- await api('/api/inbox/add', { dir: dirOrDefault(), text, attachments });
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', attachments.length ? 'processando texto + imagens…' : 'processando…');
2396
- const r = await api('/api/agents/plan', { dir: dirOrDefault(), text, attachments });
2390
+ setPill('run', 'processando…');
2391
+ const r = await api('/api/agents/plan', { dir: dirOrDefault(), text });
2397
2392
 
2398
2393
  // Remove typing indicator
2399
- var typEl = $(saveTypingId);
2400
- if (typEl) typEl.remove();
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
- var applyTypingId = 'typing-apply-' + Date.now();
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 human-friendly summary instead of raw JSON
2479
+ // Build natural language summary instead of raw JSON
2489
2480
  var parts = [];
2490
- var tasks = Number(summary.tasks || 0);
2491
- var blockers = Number(summary.blockers || 0);
2492
- var skippedT = Number(summary.tasksSkipped || 0);
2493
- var skippedB = Number(summary.blockersSkipped || 0);
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
- if (tasks > 0) parts.push('βœ… **' + tasks + ' tarefa' + (tasks > 1 ? 's' : '') + '** criada' + (tasks > 1 ? 's' : ''));
2496
- if (blockers > 0) parts.push('🚧 **' + blockers + ' blocker' + (blockers > 1 ? 's' : '') + '** registrado' + (blockers > 1 ? 's' : ''));
2497
- if (skippedT > 0) parts.push('⏭️ ' + skippedT + ' tarefa' + (skippedT > 1 ? 's' : '') + ' jÑ existente' + (skippedT > 1 ? 's' : ''));
2498
- if (skippedB > 0) parts.push('⏭️ ' + skippedB + ' blocker' + (skippedB > 1 ? 's' : '') + ' jÑ existente' + (skippedB > 1 ? 's' : ''));
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 = parts.join('\n');
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\nπŸ“Š **RelatΓ³rios sugeridos:** ' + summary.reportsSuggested.join(', ');
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 = '&times;';
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 lightbox β†’ quick-add β†’ blur
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();