@cccarv82/freya 2.9.0 → 2.11.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.
Files changed (3) hide show
  1. package/cli/web-ui.js +182 -81
  2. package/cli/web.js +91 -78
  3. package/package.json +1 -1
package/cli/web-ui.js CHANGED
@@ -330,6 +330,47 @@
330
330
  }
331
331
  }
332
332
 
333
+ // Unified input → Oracle (RAG): reads from the main inboxText textarea
334
+ async function askFreyaFromInput() {
335
+ const ta = $('inboxText');
336
+ const query = ta ? (ta.value || '').trim() : '';
337
+ if (!query) {
338
+ setPill('err', 'vazio');
339
+ setTimeout(() => setPill('ok', 'pronto'), 800);
340
+ return;
341
+ }
342
+
343
+ if (ta) ta.value = '';
344
+
345
+ // Update mode tag
346
+ const tag = $('chatModeTag');
347
+ if (tag) { tag.textContent = 'oracle'; tag.style.color = 'var(--accent)'; tag.style.borderColor = 'var(--accent)'; }
348
+
349
+ chatAppend('user', query);
350
+ setPill('run', 'pesquisando…');
351
+
352
+ const typingId = 'typing-' + Date.now();
353
+ chatAppend('assistant', '<div class="typing-indicator"><span></span><span></span><span></span></div>', { id: typingId, html: true });
354
+
355
+ try {
356
+ const sessionId = ensureChatSession();
357
+ const r = await api('/api/chat/ask', { dir: dirOrDefault(), sessionId, query });
358
+ const answer = r && r.answer ? r.answer : 'Não encontrei registro';
359
+
360
+ const el = $(typingId);
361
+ if (el) el.remove();
362
+
363
+ chatAppend('assistant', answer, { markdown: true });
364
+ setPill('ok', 'pronto');
365
+ if (tag) setTimeout(() => { tag.textContent = 'oracle'; tag.style.color = ''; tag.style.borderColor = ''; }, 2000);
366
+ } catch (e) {
367
+ const el = $(typingId);
368
+ if (el) el.remove();
369
+ setPill('err', 'falhou');
370
+ chatAppend('assistant', '❌ ' + String(e && e.message ? e.message : e));
371
+ }
372
+ }
373
+
333
374
  function setOut(text) {
334
375
  state.lastText = text || '';
335
376
  const el = $('reportPreview');
@@ -1317,11 +1358,65 @@
1317
1358
  }
1318
1359
  }
1319
1360
 
1361
+ // Priority → color + icon
1362
+ function priColor(pri) {
1363
+ const p = String(pri || '').toUpperCase();
1364
+ if (p === 'CRITICAL') return { dot: '#ef4444', label: 'CRÍTICA' };
1365
+ if (p === 'HIGH') return { dot: '#f97316', label: 'ALTA' };
1366
+ if (p === 'MEDIUM') return { dot: '#eab308', label: 'MÉDIA' };
1367
+ if (p === 'LOW') return { dot: '#22c55e', label: 'BAIXA' };
1368
+ return { dot: 'var(--muted)', label: p || '—' };
1369
+ }
1370
+
1371
+ // Severity → color
1372
+ function sevColor(sev) {
1373
+ const s = String(sev || '').toUpperCase();
1374
+ if (s === 'CRITICAL') return '#ef4444';
1375
+ if (s === 'HIGH') return '#f97316';
1376
+ if (s === 'MEDIUM') return '#eab308';
1377
+ return '#94a3b8';
1378
+ }
1379
+
1320
1380
  function renderSwimlanes(tasks, blockers) {
1321
1381
  const el = $('swimlaneContainer');
1322
1382
  if (!el) return;
1323
1383
  el.innerHTML = '';
1324
1384
 
1385
+ // Update top-level summary chips
1386
+ const chips = $('focusSummaryChips');
1387
+ if (chips) {
1388
+ chips.innerHTML = '';
1389
+ if (blockers.length > 0) {
1390
+ const bc = document.createElement('span');
1391
+ bc.style.cssText = 'font-size:11px; font-weight:700; padding:2px 8px; border-radius:10px; background:rgba(239,68,68,0.12); color:#f87171; border:1px solid rgba(239,68,68,0.3);';
1392
+ bc.textContent = blockers.length + ' blocker' + (blockers.length > 1 ? 's' : '');
1393
+ chips.appendChild(bc);
1394
+ }
1395
+ if (tasks.length > 0) {
1396
+ const tc = document.createElement('span');
1397
+ tc.style.cssText = 'font-size:11px; font-weight:700; padding:2px 8px; border-radius:10px; background:rgba(34,197,94,0.10); color:#4ade80; border:1px solid rgba(34,197,94,0.25);';
1398
+ tc.textContent = tasks.length + ' task' + (tasks.length > 1 ? 's' : '');
1399
+ chips.appendChild(tc);
1400
+ }
1401
+ }
1402
+
1403
+ // Update progress bar (tasks completed today vs total — we show pending count)
1404
+ const progWrap = $('focusProgressWrap');
1405
+ const progBar = $('focusProgressBar');
1406
+ const progLabel = $('focusProgressLabel');
1407
+ if (progWrap) {
1408
+ if (tasks.length > 0 || blockers.length > 0) {
1409
+ progWrap.style.display = 'flex';
1410
+ // We only have pending tasks here; show blockers impact
1411
+ const blocker_pct = blockers.length === 0 ? 100 : Math.max(10, Math.round((1 - blockers.length / Math.max(tasks.length + blockers.length, 1)) * 100));
1412
+ if (progBar) progBar.style.width = blocker_pct + '%';
1413
+ if (progBar) progBar.style.background = blockers.length > 0 ? '#f97316' : 'var(--accent)';
1414
+ if (progLabel) progLabel.textContent = blockers.length > 0 ? blockers.length + ' blocking' : 'tudo livre';
1415
+ } else {
1416
+ progWrap.style.display = 'none';
1417
+ }
1418
+ }
1419
+
1325
1420
  const groups = {};
1326
1421
  const addGroup = (slug) => {
1327
1422
  const key = slug || 'Global / Sem Projeto';
@@ -1337,41 +1432,55 @@
1337
1432
  if (b === 'Global / Sem Projeto') return -1;
1338
1433
  return a.localeCompare(b);
1339
1434
  });
1340
-
1341
1435
  sortedKeys.sort((a, b) => groups[b].blockers.length - groups[a].blockers.length);
1342
1436
 
1343
1437
  if (sortedKeys.length === 0) {
1344
1438
  const empty = document.createElement('div');
1345
- empty.className = 'help';
1346
- empty.textContent = 'Nenhuma tarefa ou bloqueio pendente hoje.';
1439
+ empty.style.cssText = 'display:flex; flex-direction:column; align-items:center; justify-content:center; padding:48px 24px; gap:8px; flex:1;';
1440
+ empty.innerHTML = '<div style="font-size:28px; line-height:1;">✅</div>'
1441
+ + '<div style="font-size:14px; font-weight:600; color:var(--text);">Dia limpo!</div>'
1442
+ + '<div style="font-size:12px; color:var(--muted); text-align:center;">Nenhuma tarefa pendente (DO_NOW) nem bloqueios em aberto. Use o campo acima para registrar o que estiver fazendo.</div>';
1347
1443
  el.appendChild(empty);
1348
1444
  return;
1349
1445
  }
1350
1446
 
1351
1447
  const createTaskRow = (t) => {
1352
1448
  const row = document.createElement('div');
1353
- row.className = 'rep';
1354
- const pri = (t.priority || '').toUpperCase();
1355
- row.innerHTML = '<div style="display:flex; justify-content:space-between; gap:10px; align-items:center">'
1356
- + '<div style="min-width:0"><div style="font-weight:600">' + escapeHtml(t.description || '') + '</div>'
1357
- + '<div style="opacity:.7; font-size:11px; margin-top:4px; font-weight:500;">' + escapeHtml(String(t.category || ''))
1358
- + (pri ? (' · ' + escapeHtml(pri)) : '') + '</div></div>'
1359
- + '<div style="display:flex; gap:8px">'
1360
- + '<button class="btn small complete-btn" type="button">Concluir</button>'
1361
- + '<button class="btn small edit-btn" type="button">Editar</button>'
1449
+ const pc = priColor(t.priority);
1450
+ row.style.cssText = 'display:flex; justify-content:space-between; gap:10px; align-items:center; padding:10px 16px; background:var(--bg); border-bottom:1px solid var(--border); transition:background 0.15s;';
1451
+ row.onmouseover = () => row.style.background = 'var(--paper)';
1452
+ row.onmouseout = () => row.style.background = 'var(--bg)';
1453
+ row.innerHTML =
1454
+ '<div style="display:flex; align-items:center; gap:10px; min-width:0; flex:1;">'
1455
+ + '<div style="width:8px; height:8px; border-radius:50%; background:' + pc.dot + '; flex-shrink:0;" title="Prioridade: ' + escapeHtml(pc.label) + '"></div>'
1456
+ + '<div style="min-width:0;">'
1457
+ + '<div style="font-weight:600; font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">' + escapeHtml(t.description || '') + '</div>'
1458
+ + '<div style="font-size:11px; color:var(--muted); margin-top:2px;">'
1459
+ + escapeHtml(pc.label)
1460
+ + (t.category ? ' · <span style="font-family:var(--mono);">' + escapeHtml(t.category) + '</span>' : '')
1362
1461
  + '</div>'
1462
+ + '</div></div>'
1463
+ + '<div style="display:flex; gap:6px; flex-shrink:0;">'
1464
+ + '<button class="btn small complete-btn" type="button" style="padding:3px 10px; font-size:11px;" title="Marcar como concluída">✓ Concluir</button>'
1465
+ + '<button class="btn small edit-btn" type="button" style="padding:3px 8px; font-size:11px;">✎</button>'
1363
1466
  + '</div>';
1364
1467
  row.querySelector('.edit-btn').onclick = () => editTask(t);
1365
1468
  row.querySelector('.complete-btn').onclick = async () => {
1469
+ const btn = row.querySelector('.complete-btn');
1470
+ btn.disabled = true;
1471
+ btn.textContent = '…';
1366
1472
  try {
1367
- setPill('run', 'completing…');
1473
+ setPill('run', 'concluindo…');
1368
1474
  await api('/api/tasks/complete', { dir: dirOrDefault(), id: t.id });
1475
+ row.style.opacity = '0.4';
1476
+ row.style.pointerEvents = 'none';
1369
1477
  await refreshToday();
1370
- setPill('ok', 'completed');
1478
+ setPill('ok', 'concluída');
1371
1479
  setTimeout(() => setPill('ok', 'pronto'), 800);
1372
1480
  } catch (e) {
1373
- setPill('err', 'complete failed');
1374
- setOut(String(e && e.message ? e.message : e));
1481
+ btn.disabled = false;
1482
+ btn.textContent = '✓ Concluir';
1483
+ setPill('err', 'falhou');
1375
1484
  }
1376
1485
  };
1377
1486
  return row;
@@ -1379,81 +1488,57 @@
1379
1488
 
1380
1489
  const createBlockerRow = (b) => {
1381
1490
  const row = document.createElement('div');
1382
- row.className = 'rep';
1383
- row.style.borderLeft = '4px solid var(--warn)';
1384
- row.style.background = 'var(--warn-bg)';
1385
1491
  const sev = String(b.severity || '').toUpperCase();
1386
- row.innerHTML = '<div style="display:flex; justify-content:space-between; gap:10px; align-items:center">'
1387
- + '<div style="min-width:0"><div style="font-weight:800; color:var(--warn)">' + escapeHtml(sev) + '</div>'
1388
- + '<div style="margin-top:4px; font-weight:600;">' + escapeHtml(b.title || '') + '</div>'
1492
+ const color = sevColor(sev);
1493
+ const when = fmtWhen(new Date(b.createdAt || Date.now()).getTime());
1494
+ row.style.cssText = 'display:flex; justify-content:space-between; gap:10px; align-items:center; padding:10px 16px; border-bottom:1px solid rgba(239,68,68,0.15); border-left:3px solid ' + color + '; background:rgba(239,68,68,0.04); transition:background 0.15s;';
1495
+ row.onmouseover = () => row.style.background = 'rgba(239,68,68,0.08)';
1496
+ row.onmouseout = () => row.style.background = 'rgba(239,68,68,0.04)';
1497
+ row.innerHTML =
1498
+ '<div style="display:flex; align-items:flex-start; gap:10px; min-width:0; flex:1;">'
1499
+ + '<div style="flex-shrink:0; margin-top:1px;">'
1500
+ + '<span style="font-size:10px; font-weight:800; letter-spacing:0.5px; padding:2px 6px; border-radius:4px; background:' + color + '22; color:' + color + '; border:1px solid ' + color + '44;">' + escapeHtml(sev || 'BLOCKER') + '</span>'
1389
1501
  + '</div>'
1390
- + '<div style="display:flex; gap:8px; align-items:center">'
1391
- + '<div style="opacity:.7; font-size:11px; white-space:nowrap; font-weight:500;">' + escapeHtml(fmtWhen(new Date(b.createdAt || Date.now()).getTime())) + '</div>'
1392
- + '<button class="btn small edit-btn" type="button" style="border-color:var(--warn); color:var(--warn);">Editar</button>'
1393
- + '</div>'
1394
- + '</div>';
1502
+ + '<div style="min-width:0; flex:1;">'
1503
+ + '<div style="font-weight:600; font-size:13px; color:var(--text);">' + escapeHtml(b.title || '') + '</div>'
1504
+ + '<div style="font-size:11px; color:var(--muted); margin-top:3px;">🕐 ' + escapeHtml(when) + '</div>'
1505
+ + '</div></div>'
1506
+ + '<button class="btn small edit-btn" type="button" style="padding:3px 8px; font-size:11px; flex-shrink:0; border-color:' + color + '66; color:' + color + ';">✎</button>';
1395
1507
  row.querySelector('.edit-btn').onclick = () => editBlocker(b);
1396
1508
  return row;
1397
1509
  };
1398
1510
 
1399
1511
  for (const key of sortedKeys) {
1400
1512
  const g = groups[key];
1513
+ const bCount = g.blockers.length;
1514
+ const tCount = g.tasks.length;
1515
+ const hasBlockers = bCount > 0;
1516
+
1401
1517
  const swimlane = document.createElement('div');
1402
- swimlane.className = 'panel';
1403
- swimlane.style.border = '1px solid var(--border)';
1404
- swimlane.style.borderRadius = '8px';
1405
- swimlane.style.boxShadow = 'none';
1406
- swimlane.style.overflow = 'hidden';
1518
+ swimlane.style.cssText = 'border-bottom:1px solid var(--border); overflow:hidden;';
1407
1519
 
1408
1520
  const head = document.createElement('div');
1409
- head.style.display = 'flex';
1410
- head.style.justifyContent = 'space-between';
1411
- head.style.alignItems = 'center';
1412
- head.style.cursor = 'pointer';
1413
- head.style.padding = '10px 16px';
1414
- head.style.background = 'var(--bg2)';
1415
- head.style.borderBottom = '1px solid var(--border)';
1416
- head.style.transition = 'background 0.2s';
1521
+ head.style.cssText = 'display:flex; justify-content:space-between; align-items:center; cursor:pointer; padding:9px 16px; background:var(--bg2); transition:background 0.15s;'
1522
+ + (hasBlockers ? 'border-left:3px solid #f97316;' : 'border-left:3px solid transparent;');
1417
1523
  head.onmouseover = () => head.style.background = 'var(--paper2)';
1418
1524
  head.onmouseout = () => head.style.background = 'var(--bg2)';
1419
1525
 
1420
- const bCount = g.blockers.length;
1421
- const tCount = g.tasks.length;
1422
-
1423
1526
  head.innerHTML = `
1424
- <div style="font-weight:700; font-size:13px; display:flex; gap:8px; align-items:center;">
1425
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.6; transition: transform 0.2s;"><polyline points="6 9 12 15 18 9"></polyline></svg>
1426
- <span style="font-family: var(--mono); color: var(--accent);">${escapeHtml(key)}</span>
1527
+ <div style="display:flex; align-items:center; gap:8px; min-width:0;">
1528
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0; opacity:0.5; transition:transform 0.2s;"><polyline points="6 9 12 15 18 9"></polyline></svg>
1529
+ <span style="font-family:var(--mono); font-size:12px; font-weight:700; color:var(--accent); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">${escapeHtml(key)}</span>
1427
1530
  </div>
1428
- <div style="display:flex; gap:6px; font-size:11px;">
1429
- ${bCount > 0 ? `<span style="color:var(--warn); background:var(--warn-bg); padding:2px 8px; border-radius:12px; font-weight:700;">🔴 ${bCount} Blockers</span>` : ''}
1430
- ${tCount > 0 ? `<span style="color:var(--info); background:var(--bg2); border: 1px solid var(--border); padding:2px 8px; border-radius:12px; font-weight:700;">🟢 ${tCount} Tasks</span>` : ''}
1531
+ <div style="display:flex; gap:5px; align-items:center; flex-shrink:0;">
1532
+ ${bCount > 0 ? `<span style="font-size:11px; font-weight:700; padding:2px 7px; border-radius:10px; background:rgba(239,68,68,0.12); color:#f87171; border:1px solid rgba(239,68,68,0.3);">⛔ ${bCount}</span>` : ''}
1533
+ ${tCount > 0 ? `<span style="font-size:11px; font-weight:700; padding:2px 7px; border-radius:10px; background:rgba(34,197,94,0.10); color:#4ade80; border:1px solid rgba(34,197,94,0.25);">✓ ${tCount}</span>` : ''}
1431
1534
  </div>
1432
1535
  `;
1433
1536
 
1434
1537
  const body = document.createElement('div');
1435
- body.style.display = 'flex';
1436
- body.style.flexDirection = 'column';
1437
- body.style.background = 'var(--bg)';
1438
-
1439
- for (const b of g.blockers) {
1440
- const r = createBlockerRow(b);
1441
- r.style.margin = '0';
1442
- r.style.borderRadius = '0';
1443
- r.style.borderBottom = '1px solid var(--border)';
1444
- r.style.borderLeft = '4px solid var(--warn)';
1445
- r.style.boxShadow = 'none';
1446
- body.appendChild(r);
1447
- }
1448
- for (const t of g.tasks) {
1449
- const r = createTaskRow(t);
1450
- r.style.margin = '0';
1451
- r.style.borderRadius = '0';
1452
- r.style.borderBottom = '1px solid var(--border)';
1453
- r.style.borderLeft = '4px solid transparent';
1454
- r.style.boxShadow = 'none';
1455
- body.appendChild(r);
1456
- }
1538
+ body.style.cssText = 'display:flex; flex-direction:column; background:var(--bg);';
1539
+
1540
+ for (const b of g.blockers) body.appendChild(createBlockerRow(b));
1541
+ for (const t of g.tasks) body.appendChild(createTaskRow(t));
1457
1542
 
1458
1543
  let isOpen = true;
1459
1544
  head.onclick = () => {
@@ -1484,22 +1569,30 @@
1484
1569
 
1485
1570
  function renderBlockersInsights(payload) {
1486
1571
  const el = $('blockersInsights');
1572
+ const wrap = $('blockersInsightsWrap');
1487
1573
  if (!el) return;
1574
+
1488
1575
  if (!payload || !payload.summary) {
1489
- el.textContent = 'Sem insights no momento.';
1576
+ // Hide the strip if no content
1577
+ if (wrap) wrap.style.display = 'none';
1490
1578
  return;
1491
1579
  }
1580
+
1492
1581
  const lines = [];
1493
- lines.push('<div class="help" style="margin-bottom:6px"><b>Resumo:</b> ' + escapeHtml(payload.summary) + '</div>');
1582
+ lines.push('<div style="margin-bottom:6px; color:var(--text);">' + escapeHtml(payload.summary) + '</div>');
1494
1583
  if (payload.suggestions && payload.suggestions.length) {
1495
- lines.push('<div class="help"><b>Proximos passos:</b></div>');
1496
- lines.push('<ul style="margin:6px 0 0 18px; padding:0;">' + payload.suggestions.map((s) => '<li class="help">' + escapeHtml(s) + '</li>').join('') + '</ul>');
1584
+ lines.push('<div style="font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:0.5px; color:var(--muted); margin:8px 0 4px;">Próximos passos</div>');
1585
+ lines.push('<ul style="margin:0 0 0 14px; padding:0;">' + payload.suggestions.map((s) => '<li style="margin-bottom:3px;">' + escapeHtml(s) + '</li>').join('') + '</ul>');
1497
1586
  }
1498
1587
  if (payload.top && payload.top.length) {
1499
- lines.push('<div class="help" style="margin-top:8px"><b>Top blockers:</b></div>');
1500
- lines.push('<ul style="margin:6px 0 0 18px; padding:0;">' + payload.top.map((b) => '<li class="help">' + escapeHtml(String(b.severity || '')) + ' - ' + escapeHtml(String(b.title || '')) + '</li>').join('') + '</ul>');
1588
+ lines.push('<div style="font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:0.5px; color:var(--muted); margin:8px 0 4px;">Top blockers</div>');
1589
+ lines.push('<ul style="margin:0 0 0 14px; padding:0;">' + payload.top.map((b) => {
1590
+ const c = sevColor(String(b.severity || '').toUpperCase());
1591
+ return '<li style="margin-bottom:3px;"><span style="color:' + c + '; font-weight:700;">' + escapeHtml(String(b.severity || '')) + '</span> — ' + escapeHtml(String(b.title || '')) + '</li>';
1592
+ }).join('') + '</ul>');
1501
1593
  }
1502
1594
  el.innerHTML = lines.join('');
1595
+ if (wrap) wrap.style.display = 'block';
1503
1596
  }
1504
1597
 
1505
1598
  async function refreshBlockersInsights() {
@@ -2125,16 +2218,21 @@
2125
2218
  if (!ta) return;
2126
2219
  const text = (ta.value || '').trim();
2127
2220
  if (!text) {
2128
- setPill('err', 'empty');
2221
+ setPill('err', 'vazio');
2222
+ setTimeout(() => setPill('ok', 'pronto'), 800);
2129
2223
  return;
2130
2224
  }
2131
2225
 
2226
+ // Update mode tag
2227
+ const tag = $('chatModeTag');
2228
+ if (tag) { tag.textContent = 'inbox'; tag.style.color = 'var(--primary)'; tag.style.borderColor = 'var(--primary)'; }
2229
+
2132
2230
  chatAppend('user', text);
2133
2231
 
2134
- setPill('run', 'saving…');
2232
+ setPill('run', 'salvando…');
2135
2233
  await api('/api/inbox/add', { dir: dirOrDefault(), text });
2136
2234
 
2137
- setPill('run', 'planning…');
2235
+ setPill('run', 'processando…');
2138
2236
  const r = await api('/api/agents/plan', { dir: dirOrDefault(), text });
2139
2237
 
2140
2238
  state.lastPlan = r.plan || '';
@@ -2164,9 +2262,11 @@
2164
2262
  setPill('ok', 'planned');
2165
2263
  }
2166
2264
 
2265
+ const tag2 = $('chatModeTag');
2266
+ if (tag2) setTimeout(() => { tag2.textContent = 'oracle'; tag2.style.color = ''; tag2.style.borderColor = ''; }, 1500);
2167
2267
  setTimeout(() => setPill('ok', 'pronto'), 1200);
2168
2268
  } catch (e) {
2169
- setPill('err', 'plan failed');
2269
+ setPill('err', 'falhou');
2170
2270
  }
2171
2271
  }
2172
2272
 
@@ -2385,4 +2485,5 @@
2385
2485
  window.exportChatObsidian = exportChatObsidian;
2386
2486
  window.askFreya = askFreya;
2387
2487
  window.askFreyaInline = askFreyaInline;
2488
+ window.askFreyaFromInput = askFreyaFromInput;
2388
2489
  })();
package/cli/web.js CHANGED
@@ -1207,122 +1207,135 @@ function buildHtml(safeDefault, appVersion) {
1207
1207
  </div>
1208
1208
 
1209
1209
  <div class="centerBody">
1210
- <section style="margin-bottom: 24px; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; height: 380px; max-height: 450px;">
1211
- <div class="promptBar" style="width: 100%; border-radius: 20px; height: 100%; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden;">
1212
- <div style="flex: 1; display: flex; flex-direction: column;">
1213
- <div class="promptMeta">
1214
- <div class="promptTitle" style="display: flex; align-items: center; gap: 8px;">
1215
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: var(--primary)"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
1216
- <span>Inbox & Prompt</span>
1217
- </div>
1218
- <div id="status" class="small">pronto</div>
1210
+ <!-- Unified Input + Chat Panel -->
1211
+ <section style="margin-bottom: 24px; display: grid; grid-template-columns: 1fr 1.6fr; gap: 16px; height: 420px; max-height: 480px;">
1212
+
1213
+ <!-- Left: Unified Input -->
1214
+ <div class="promptBar" style="width: 100%; border-radius: 20px; height: 100%; display: flex; flex-direction: column; overflow: hidden;">
1215
+ <div class="promptMeta" style="flex-shrink:0;">
1216
+ <div class="promptTitle" style="display: flex; align-items: center; gap: 8px;">
1217
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: var(--primary)"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
1218
+ <span>Freya Input</span>
1219
1219
  </div>
1220
- <textarea id="inboxText" placeholder="Cole updates do dia (status, blockers, decisões, ideias) ou faça uma pergunta para a Freya..." style="resize:none; flex: 1; min-height: 0;"></textarea>
1220
+ <div id="status" class="small">pronto</div>
1221
1221
  </div>
1222
- <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 12px;">
1223
- <div class="promptActions" style="margin: 0;">
1224
- <button class="btn primary small" type="button" onclick="saveAndPlan()">Salvar + Processar</button>
1225
- <button class="btn small" type="button" onclick="runSuggestedReports()">Rodar relatórios sugeridos</button>
1226
- </div>
1227
- <div class="promptToggles" style="margin: 0;">
1228
- <label class="toggleRow">
1222
+
1223
+ <textarea id="inboxText" placeholder="Cole updates, decisões, blockers... ou faça uma pergunta à Freya.&#10;&#10;▸ Salvar & Processar → extrai tarefas e blockers&#10;▸ Perguntar → consulta o histórico (RAG)" style="resize:none; flex: 1; min-height: 0; 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;"
1224
+ onkeydown="if((event.metaKey||event.ctrlKey)&&event.key==='Enter'){event.preventDefault();window.saveAndPlan();}"></textarea>
1225
+
1226
+ <!-- Primary actions -->
1227
+ <div style="padding: 10px 14px 6px; display: flex; gap: 8px; flex-wrap: wrap; flex-shrink:0;">
1228
+ <button class="btn primary small" type="button" onclick="saveAndPlan()" style="flex:1; min-width: 120px;" title="Ctrl+Enter">
1229
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="display:inline;vertical-align:-2px;margin-right:5px"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
1230
+ Salvar & Processar
1231
+ </button>
1232
+ <button class="btn small" type="button" onclick="window.askFreyaFromInput()" style="flex:1; min-width: 90px;" title="Consulta semântica ao histórico">
1233
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline;vertical-align:-2px;margin-right:5px"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
1234
+ Perguntar
1235
+ </button>
1236
+ </div>
1237
+
1238
+ <!-- Toggles + secondary -->
1239
+ <div style="padding: 0 14px 12px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; flex-shrink:0;">
1240
+ <div class="promptToggles" style="margin:0;">
1241
+ <label class="toggleRow" title="Aplica o plano automaticamente após processar">
1229
1242
  <input id="autoApply" type="checkbox" checked style="width:auto; margin: 0;" onchange="toggleAutoApply()" />
1230
1243
  Auto-apply
1231
1244
  </label>
1232
- <label class="toggleRow">
1245
+ <label class="toggleRow" title="Roda relatórios sugeridos após aplicar">
1233
1246
  <input id="autoRunReports" type="checkbox" style="width:auto; margin: 0;" onchange="toggleAutoRunReports()" />
1234
- Auto-run reports
1247
+ Auto-reports
1235
1248
  </label>
1236
1249
  </div>
1250
+ <div style="display:flex; gap:6px;">
1251
+ <button class="btn small" type="button" onclick="runSuggestedReports()" title="Rodar relatórios sugeridos pelo último plan" style="font-size:11px; padding: 3px 8px;">📋 Sugeridos</button>
1252
+ <button class="btn small" type="button" onclick="exportChatObsidian()" style="font-size:11px; padding: 3px 8px;" title="Exportar conversa para Obsidian">⬆ Log</button>
1253
+ </div>
1237
1254
  </div>
1238
1255
  </div>
1239
1256
 
1257
+ <!-- Right: Unified Chat Thread (Oracle responses + Plan outputs) -->
1240
1258
  <div class="panel" style="height: 100%; display: flex; flex-direction: column;">
1241
- <div class="panelHead">
1242
- <b style="display: flex; align-items: center; gap: 8px;">
1243
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
1244
- Consultar Oracle
1259
+ <div class="panelHead" style="flex-shrink:0;">
1260
+ <b style="display: flex; align-items: center; gap: 8px; font-size:13px;">
1261
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--accent)"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
1262
+ Conversa com a Freya
1245
1263
  </b>
1246
1264
  <div class="stack">
1247
- <button class="btn small" type="button" onclick="exportChatObsidian()">Exportar Log</button>
1248
- </div>
1249
- </div>
1250
- <div class="panelBody" style="flex:1; display:flex; flex-direction:column; padding:0; background:var(--glass-bg); min-height:0;">
1251
- <div id="chatThread" style="flex:1; overflow-y:auto; overflow-x:hidden; padding:12px; display:flex; flex-direction:column; gap:8px;"></div>
1252
- <div style="padding: 12px; border-top: 1px solid var(--glass-border); display:flex; gap: 8px; background: var(--paper);">
1253
- <input type="text" id="oracleInput" autocomplete="off" style="flex:1;" placeholder="Pergunte algo à Freya..." onkeydown="if(event.key==='Enter') window.askFreyaInline()" />
1254
- <button class="btn primary small" type="button" onclick="window.askFreyaInline()">Enviar</button>
1265
+ <span id="chatModeTag" style="font-size:11px; padding:2px 8px; border-radius:10px; background:var(--bg2); color:var(--muted); border:1px solid var(--border);">oracle</span>
1255
1266
  </div>
1256
1267
  </div>
1268
+ <div id="chatThread" style="flex:1; overflow-y:auto; overflow-x:hidden; padding:12px; display:flex; flex-direction:column; gap:8px; min-height:0;"></div>
1257
1269
  </div>
1258
1270
  </section>
1259
1271
 
1260
1272
  <div class="centerHead">
1261
1273
  <div>
1262
1274
  <h1 style="margin:0">Seu dia em um painel</h1>
1263
- <div class="subtitle">Workspaces, Hoje (tarefas/bloqueios), relatórios e preview. Use o painel da direita como um chat para capturar updates.</div>
1275
+ <div class="subtitle">Use o campo acima para capturar updates (<b>Salvar &amp; Processar</b>) ou consultar o histórico (<b>Perguntar</b>). As respostas aparecem no chat ao lado.</div>
1264
1276
  </div>
1265
1277
  <div class="statusLine">
1266
1278
  <span class="small" id="last"></span>
1267
1279
  </div>
1268
1280
  </div>
1269
1281
 
1270
- <div class="midGrid">
1271
- <!-- Hoje Panel gets more prominent focus -->
1272
- <section class="panel midSpan">
1273
- <div class="panelHead" style="background: linear-gradient(90deg, var(--paper2), var(--paper)); border-left: 4px solid var(--accent)">
1274
- <b style="color: var(--text); font-size: 14px;">Foco de Hoje</b>
1275
- <div class="stack">
1276
- <button class="btn small" type="button" onclick="refreshToday()">Atualizar</button>
1282
+ <section class="panel" style="display:flex; flex-direction:column; min-height:0; margin-bottom:16px;">
1283
+ <!-- Header with live counters -->
1284
+ <div class="panelHead" style="background: linear-gradient(90deg, var(--paper2), var(--paper)); border-left: 4px solid var(--accent); flex-shrink:0;">
1285
+ <div style="display:flex; align-items:center; gap:10px;">
1286
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--accent); flex-shrink:0;"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1287
+ <b style="color: var(--text); font-size: 14px;">Foco de Hoje</b>
1288
+ <div id="focusSummaryChips" style="display:flex; gap:5px; margin-left:4px;"></div>
1277
1289
  </div>
1278
- </div>
1279
- <div class="panelBody" style="display: flex; flex-direction: column; gap: 16px;">
1280
- <div id="swimlaneContainer" style="display:flex; flex-direction: column; gap: 12px;"></div>
1281
-
1282
- <div style="border-top: 1px solid var(--border); padding-top: 16px;">
1283
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1284
- <div class="small" style="opacity:.8; font-weight: 600; text-transform: uppercase;">Insights Globais de Bloqueios</div>
1285
- <button class="btn small" type="button" onclick="refreshBlockersInsights()">Atualizar insights</button>
1290
+ <div class="stack">
1291
+ <div id="focusProgressWrap" style="display:none; align-items:center; gap:6px; font-size:11px; color:var(--muted);">
1292
+ <div style="width:80px; height:5px; background:var(--border); border-radius:3px; overflow:hidden;">
1293
+ <div id="focusProgressBar" style="height:100%; background:var(--accent); border-radius:3px; transition:width 0.4s; width:0%"></div>
1294
+ </div>
1295
+ <span id="focusProgressLabel"></span>
1286
1296
  </div>
1287
- <div id="blockersInsights" class="help"></div>
1297
+ <button class="btn small" type="button" onclick="refreshToday()">
1298
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="display:inline;vertical-align:-2px;margin-right:4px"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg>
1299
+ Atualizar
1300
+ </button>
1288
1301
  </div>
1289
1302
  </div>
1290
- </section>
1291
1303
 
1292
- <section class="panel">
1293
- <div class="panelHead">
1294
- <b>Relatórios Recentes</b>
1295
- <div class="stack">
1296
- <button class="btn small" type="button" onclick="refreshReports()">Atualizar</button>
1297
- <button class="btn small" type="button" onclick="window.location.href='/reports'">Ver todos →</button>
1298
- </div>
1304
+ <!-- Scrollable swimlanes body -->
1305
+ <div class="panelBody" style="flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:0; padding:0; min-height:0;">
1306
+ <div id="swimlaneContainer" style="display:flex; flex-direction: column; gap: 0; flex:1;"></div>
1299
1307
  </div>
1300
- <div class="panelBody panelScroll" style="max-height: 300px;">
1301
- <input id="reportsFilter" placeholder="Filtrar relatórios..." style="width:100%; margin-bottom:10px" oninput="renderReportsList()" />
1302
- <div id="reportsList" style="display:grid; gap:8px"></div>
1303
- </div>
1304
- </section>
1305
- </div>
1306
- <div class="panel">
1307
- <div class="panelHead"><b>Configurações de publicação</b></div>
1308
- <div class="panelBody">
1309
- <label>Discord webhook URL</label>
1310
- <input id="discord" placeholder="https://discord.com/api/webhooks/..." />
1311
- <div style="height:10px"></div>
1312
-
1313
- <label>Teams webhook URL</label>
1314
- <input id="teams" placeholder="https://..." />
1315
- <div class="help">Os webhooks ficam salvos na workspace em <code>data/settings/settings.json</code>.</div>
1316
-
1317
- <div style="height:10px"></div>
1318
- <label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
1319
- <input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="togglePrettyPublish()" />
1320
- Publicação bonita (cards/embeds)
1321
- </label>
1322
1308
 
1309
+ <!-- Insights strip (collapsed by default, expands when content is available) -->
1310
+ <div id="blockersInsightsWrap" style="flex-shrink:0; border-top: 1px solid var(--border); display:none;">
1311
+ <div style="display:flex; justify-content:space-between; align-items:center; padding: 8px 16px 4px;">
1312
+ <div style="font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:0.5px; color:var(--muted);">
1313
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline;vertical-align:-1px;margin-right:4px"><path d="M12 2a10 10 0 1 0 0 20A10 10 0 0 0 12 2z"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
1314
+ Insights de Bloqueios
1323
1315
  </div>
1316
+ <button class="btn small" type="button" onclick="refreshBlockersInsights()" style="font-size:10px; padding:2px 6px;">↻</button>
1324
1317
  </div>
1318
+ <div id="blockersInsights" style="padding: 4px 16px 12px; font-size:12px; color:var(--muted);"></div>
1325
1319
  </div>
1320
+ </section>
1321
+
1322
+ <div class="panel">
1323
+ <div class="panelHead"><b>Configurações de publicação</b></div>
1324
+ <div class="panelBody">
1325
+ <label>Discord webhook URL</label>
1326
+ <input id="discord" placeholder="https://discord.com/api/webhooks/..." />
1327
+ <div style="height:10px"></div>
1328
+
1329
+ <label>Teams webhook URL</label>
1330
+ <input id="teams" placeholder="https://..." />
1331
+ <div class="help">Os webhooks ficam salvos na workspace em <code>data/settings/settings.json</code>.</div>
1332
+
1333
+ <div style="height:10px"></div>
1334
+ <label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
1335
+ <input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="togglePrettyPublish()" />
1336
+ Publicação bonita (cards/embeds)
1337
+ </label>
1338
+ </div>
1326
1339
  </div>
1327
1340
  </div>
1328
1341
  </main>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js && node scripts/validate-structure.js",