@cccarv82/freya 1.0.51 → 1.0.53

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 (2) hide show
  1. package/cli/web.js +109 -21
  2. package/package.json +1 -1
package/cli/web.js CHANGED
@@ -8,6 +8,34 @@ const { spawn } = require('child_process');
8
8
  const { searchWorkspace } = require('../scripts/lib/search-utils');
9
9
  const { searchIndex } = require('../scripts/lib/index-utils');
10
10
 
11
+ function readAppVersion() {
12
+ const pkgPath = path.join(__dirname, '..', 'package.json');
13
+ try {
14
+ const json = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
15
+ if (json && typeof json.version === 'string' && json.version.trim() !== '') return json.version.trim();
16
+ } catch {
17
+ // Fall back below.
18
+ }
19
+ return 'unknown';
20
+ }
21
+
22
+ function escapeHtml(str) {
23
+ return String(str)
24
+ .replace(/&/g, '&')
25
+ .replace(/</g, '&lt;')
26
+ .replace(/>/g, '&gt;')
27
+ .replace(/"/g, '&quot;')
28
+ .replace(/'/g, '&#39;');
29
+ }
30
+
31
+ const APP_VERSION = readAppVersion();
32
+
33
+ const CHAT_ID_PATTERNS = [
34
+ /\bPTI\d{4,}-\d+\b/gi,
35
+ /\bINC\d+\b/gi,
36
+ /\bCHG\d+\b/gi
37
+ ];
38
+
11
39
  function guessNpmCmd() {
12
40
  // We'll execute via cmd.exe on Windows for reliability.
13
41
  return process.platform === 'win32' ? 'npm' : 'npm';
@@ -615,6 +643,61 @@ function isAllowedChatSearchPath(relPath) {
615
643
  return relPath.startsWith('data/') || relPath.startsWith('logs/') || relPath.startsWith('docs/');
616
644
  }
617
645
 
646
+ function extractChatIds(text) {
647
+ const tokens = new Set();
648
+ const q = String(text || '');
649
+ for (const re of CHAT_ID_PATTERNS) {
650
+ const matches = q.match(re);
651
+ if (matches) {
652
+ for (const m of matches) tokens.add(m.toUpperCase());
653
+ }
654
+ }
655
+ return Array.from(tokens);
656
+ }
657
+
658
+ function extractProjectToken(text) {
659
+ const raw = String(text || '');
660
+ const m = raw.match(/project\s*[:=]\s*([A-Za-z0-9_\/-]+)/i);
661
+ if (m && m[1]) return m[1].trim();
662
+ const m2 = raw.match(/project\s*\(([^)]+)\)/i);
663
+ if (m2 && m2[1]) return m2[1].trim();
664
+ return '';
665
+ }
666
+
667
+ function projectFromPath(relPath) {
668
+ const p = String(relPath || '');
669
+ const m = p.match(/data\/Clients\/([^/]+)\/([^/]+)/i);
670
+ if (m && m[1] && m[2]) return `${m[1]}/${m[2]}`;
671
+ return '';
672
+ }
673
+
674
+ function matchKey(m) {
675
+ const ids = extractChatIds(`${m.file || ''} ${m.snippet || ''}`);
676
+ if (ids.length) return `id:${ids[0]}`;
677
+ const proj = projectFromPath(m.file) || extractProjectToken(`${m.file || ''} ${m.snippet || ''}`);
678
+ if (proj) return `proj:${proj.toLowerCase()}`;
679
+ return `file:${m.file || ''}`;
680
+ }
681
+
682
+ function mergeMatches(primary, secondary, limit = 8) {
683
+ const list = [];
684
+ const seen = new Set();
685
+ const push = (m) => {
686
+ if (!m || !m.file || !isAllowedChatSearchPath(m.file)) return;
687
+ const key = matchKey(m);
688
+ if (seen.has(key)) return;
689
+ seen.add(key);
690
+ list.push(m);
691
+ };
692
+
693
+ (primary || []).forEach(push);
694
+ (secondary || []).forEach(push);
695
+
696
+ const total = list.length;
697
+ const trimmed = list.slice(0, Math.max(1, Math.min(20, limit)));
698
+ return { matches: trimmed, total };
699
+ }
700
+
618
701
  async function copilotSearch(workspaceDir, query, opts = {}) {
619
702
  const q = String(query || '').trim();
620
703
  if (!q) return { ok: false, error: 'Missing query' };
@@ -701,8 +784,8 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
701
784
  return { ok: true, answer, matches, evidence };
702
785
  }
703
786
 
704
- function buildChatAnswer(query, matches, summary, evidence, answer) {
705
- const count = matches.length;
787
+ function buildChatAnswer(query, matches, summary, evidence, answer, totalCount) {
788
+ const count = typeof totalCount === 'number' ? totalCount : matches.length;
706
789
  let summaryText = String(summary || '').trim();
707
790
  let answerText = String(answer || '').trim();
708
791
  if (!answerText) {
@@ -718,7 +801,7 @@ function buildChatAnswer(query, matches, summary, evidence, answer) {
718
801
 
719
802
  const lines = [];
720
803
  lines.push(`Encontrei ${count} registro(s).`);
721
- lines.push(`Resumo: ${answerText}`);
804
+ lines.push(`Resposta curta: ${answerText}`);
722
805
 
723
806
  const evidences = Array.isArray(evidence) && evidence.length
724
807
  ? evidence
@@ -727,15 +810,15 @@ function buildChatAnswer(query, matches, summary, evidence, answer) {
727
810
  if (!evidences.length) return lines.join('\n');
728
811
 
729
812
  lines.push('');
730
- lines.push('Principais evidências:');
813
+ lines.push('Detalhes:');
731
814
  for (const m of evidences.slice(0, 5)) {
732
- const parts = [];
733
- if (m.date) parts.push(`**${m.date}**`);
734
- if (m.file) parts.push('`' + m.file + '`');
735
- const prefix = parts.length ? parts.join(' — ') + ':' : '';
736
815
  const detail = (m.detail || m.snippet || '').toString().trim();
737
816
  if (!detail) continue;
738
- lines.push(`- ${prefix} ${detail}`);
817
+ const meta = [];
818
+ if (m.file) meta.push(m.file);
819
+ if (m.date) meta.push(m.date);
820
+ const suffix = meta.length ? ` (${meta.join(' · ')})` : '';
821
+ lines.push(`- ${detail}${suffix}`);
739
822
  }
740
823
 
741
824
  return lines.join('\n');
@@ -795,10 +878,11 @@ async function pickDirectoryNative() {
795
878
  function html(defaultDir) {
796
879
  // Aesthetic: “clean workstation” — light-first UI inspired by modern productivity apps.
797
880
  const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
798
- return buildHtml(safeDefault);
881
+ return buildHtml(safeDefault, APP_VERSION);
799
882
  }
800
883
 
801
- function buildHtml(safeDefault) {
884
+ function buildHtml(safeDefault, appVersion) {
885
+ const safeVersion = escapeHtml(appVersion || 'unknown');
802
886
  return `<!doctype html>
803
887
  <html>
804
888
  <head>
@@ -861,6 +945,7 @@ function buildHtml(safeDefault) {
861
945
  <div class="topbar">
862
946
  <div class="brand"><span class="spark"></span> Assistente de status local-first</div>
863
947
  <div class="actions">
948
+ <span class="chip" id="chipVersion">v${safeVersion}</span>
864
949
  <span class="chip" id="chipPort">127.0.0.1:3872</span>
865
950
  <button class="toggle" id="themeToggle" onclick="toggleTheme()">Escuro</button>
866
951
  </div>
@@ -1757,24 +1842,27 @@ async function cmdWeb({ port, dir, open, dev }) {
1757
1842
  if (!query) return safeJson(res, 400, { error: 'Missing query' });
1758
1843
 
1759
1844
  const copilotResult = await copilotSearch(workspaceDir, query, { limit: 8 });
1845
+ const indexMatches = searchIndex(workspaceDir, query, { limit: 12 });
1846
+ const baseMatches = indexMatches.length
1847
+ ? indexMatches
1848
+ : searchWorkspace(workspaceDir, query, { limit: 12 });
1849
+
1760
1850
  if (copilotResult.ok) {
1761
- const matches = copilotResult.matches || [];
1851
+ const merged = mergeMatches(copilotResult.matches || [], baseMatches, 8);
1762
1852
  const answer = buildChatAnswer(
1763
1853
  query,
1764
- matches,
1854
+ merged.matches,
1765
1855
  copilotResult.summary,
1766
1856
  copilotResult.evidence,
1767
- copilotResult.answer
1857
+ copilotResult.answer,
1858
+ merged.total
1768
1859
  );
1769
- return safeJson(res, 200, { ok: true, sessionId, answer, matches });
1860
+ return safeJson(res, 200, { ok: true, sessionId, answer, matches: merged.matches });
1770
1861
  }
1771
1862
 
1772
- const indexMatches = searchIndex(workspaceDir, query, { limit: 8 });
1773
- const matches = indexMatches.length
1774
- ? indexMatches
1775
- : searchWorkspace(workspaceDir, query, { limit: 8 });
1776
- const answer = buildChatAnswer(query, matches, '');
1777
- return safeJson(res, 200, { ok: true, sessionId, answer, matches });
1863
+ const merged = mergeMatches(baseMatches, [], 8);
1864
+ const answer = buildChatAnswer(query, merged.matches, '', [], '', merged.total);
1865
+ return safeJson(res, 200, { ok: true, sessionId, answer, matches: merged.matches });
1778
1866
  }
1779
1867
 
1780
1868
  // Chat persistence (per session)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "1.0.51",
3
+ "version": "1.0.53",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js",