@cccarv82/freya 2.5.4 → 2.5.5

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 +80 -13
  2. package/cli/web.js +19 -13
  3. package/package.json +1 -1
package/cli/web-ui.js CHANGED
@@ -1905,11 +1905,13 @@
1905
1905
  }
1906
1906
  }
1907
1907
 
1908
+ state.slugRules = []; // Store the active project rules
1909
+
1908
1910
  async function reloadSlugRules() {
1909
1911
  try {
1910
1912
  const r = await api('/api/project-slug-map/get', { dir: dirOrDefault() });
1911
- const el = $('slugRules');
1912
- if (el) el.value = JSON.stringify(r.map || { rules: [] }, null, 2);
1913
+ state.slugRules = (r.map && Array.isArray(r.map.rules)) ? r.map.rules : [];
1914
+ renderSlugRules();
1913
1915
  setPill('ok', 'rules loaded');
1914
1916
  setTimeout(() => setPill('ok', 'pronto'), 800);
1915
1917
  } catch (e) {
@@ -1918,18 +1920,82 @@
1918
1920
  }
1919
1921
  }
1920
1922
 
1923
+ function renderSlugRules() {
1924
+ const container = $('slugListContainer');
1925
+ if (!container) return;
1926
+
1927
+ if (state.slugRules.length === 0) {
1928
+ container.innerHTML = '<div class="help" style="margin-bottom: 10px;">Nenhuma regra cadastrada no momento.</div>';
1929
+ return;
1930
+ }
1931
+
1932
+ let html = '<div style="display:flex; flex-direction:column; gap:8px; margin-bottom: 16px;">';
1933
+ state.slugRules.forEach((rule, index) => {
1934
+ html += `
1935
+ <div style="display:flex; align-items:center; justify-content:space-between; background: var(--bg2); padding: 10px 14px; border-radius: 6px; border: 1px solid var(--border);">
1936
+ <div style="display:flex; gap: 12px; align-items:center; flex-wrap: wrap;">
1937
+ <span style="font-size: 13px;"><span style="color:var(--text-muted); font-size:12px;">Se o texto contiver a palavra:</span> <b style="color: var(--primary);">${escapeHtml(rule.contains)}</b></span>
1938
+ <span style="color:var(--text-muted)">→</span>
1939
+ <span style="font-size: 13px;"><span style="color:var(--text-muted); font-size:12px;">Atribuir ao projeto:</span> <code class="md-inline">${escapeHtml(rule.slug)}</code></span>
1940
+ </div>
1941
+ <button class="btn small" type="button" onclick="window.removeSlugRule(${index})" title="Remover regra">Excluir</button>
1942
+ </div>
1943
+ `;
1944
+ });
1945
+ html += '</div>';
1946
+ container.innerHTML = html;
1947
+ }
1948
+
1949
+ function addSlugRule() {
1950
+ const kw = $('newSlugKeyword');
1951
+ const tg = $('newSlugTarget');
1952
+ if (!kw || !tg) return;
1953
+ const contains = kw.value.trim().toLowerCase();
1954
+ const slug = tg.value.trim().toLowerCase();
1955
+
1956
+ if (!contains || !slug) {
1957
+ setPill('err', 'Preencha ambos os campos');
1958
+ setTimeout(() => setPill('ok', 'pronto'), 1500);
1959
+ return;
1960
+ }
1961
+
1962
+ // Check for duplicates
1963
+ if (state.slugRules.some(r => r.contains === contains)) {
1964
+ setPill('err', 'Palavra-chave já existe');
1965
+ setTimeout(() => setPill('ok', 'pronto'), 1500);
1966
+ return;
1967
+ }
1968
+
1969
+ state.slugRules.push({ contains, slug });
1970
+ kw.value = '';
1971
+ tg.value = '';
1972
+ renderSlugRules();
1973
+
1974
+ // Auto-save when adding
1975
+ saveSlugRules();
1976
+ }
1977
+
1978
+ function removeSlugRule(index) {
1979
+ if (index >= 0 && index < state.slugRules.length) {
1980
+ state.slugRules.splice(index, 1);
1981
+ renderSlugRules();
1982
+ // Auto-save when removing
1983
+ saveSlugRules();
1984
+ }
1985
+ }
1986
+
1921
1987
  async function saveSlugRules() {
1922
1988
  try {
1923
- const el = $('slugRules');
1924
- if (!el) return;
1925
- const raw = String(el.value || '').trim();
1926
- if (!raw) throw new Error('Rules JSON is empty');
1927
- let map;
1928
- try { map = JSON.parse(raw); } catch (e) { throw new Error('Invalid JSON: ' + (e.message || e)); }
1929
-
1930
1989
  setPill('run', 'saving rules…');
1990
+ const map = { rules: state.slugRules };
1931
1991
  const r = await api('/api/project-slug-map/save', { dir: dirOrDefault(), map });
1932
- if (el) el.value = JSON.stringify(r.map || map, null, 2);
1992
+
1993
+ // Update state with confirmed save
1994
+ if (r.map && Array.isArray(r.map.rules)) {
1995
+ state.slugRules = r.map.rules;
1996
+ renderSlugRules();
1997
+ }
1998
+
1933
1999
  setPill('ok', 'rules saved');
1934
2000
  setTimeout(() => setPill('ok', 'pronto'), 800);
1935
2001
  } catch (e) {
@@ -2046,7 +2112,7 @@
2046
2112
  setPill('run', 'applying…');
2047
2113
  await applyPlan();
2048
2114
  const a = state.lastApplied || {};
2049
- setPill('ok', `applied (${a.tasks || 0}t, ${a.blockers || 0}b)`);
2115
+ setPill('ok', `applied(${a.tasks || 0}t, ${a.blockers || 0}b)`);
2050
2116
  if (state.autoRunReports) {
2051
2117
  await runSuggestedReports();
2052
2118
  }
@@ -2082,7 +2148,7 @@
2082
2148
  let out = '## Ran suggested reports\n\n';
2083
2149
  for (const name of uniq) {
2084
2150
  const r = await api('/api/report', { dir: dirOrDefault(), script: name === 'status' ? 'status' : name });
2085
- out += `### ${name}\n` + (r.reportPath ? `- file: ${r.reportPath}\n` : '') + '\n';
2151
+ out += `### ${name} \n` + (r.reportPath ? ` - file: ${r.reportPath} \n` : '') + '\n';
2086
2152
  }
2087
2153
 
2088
2154
  setOut(out);
@@ -2233,11 +2299,12 @@
2233
2299
  window.pickDir = pickDir;
2234
2300
  window.runReport = runReport;
2235
2301
  window.publish = publish;
2236
- window.saveSettings = saveSettings;
2237
2302
  window.refreshReports = refreshReports;
2238
2303
  window.refreshToday = refreshToday;
2239
2304
  window.reloadSlugRules = reloadSlugRules;
2240
2305
  window.saveSlugRules = saveSlugRules;
2306
+ window.addSlugRule = addSlugRule;
2307
+ window.removeSlugRule = removeSlugRule;
2241
2308
  window.exportObsidian = exportObsidian;
2242
2309
  window.rebuildIndex = rebuildIndex;
2243
2310
  window.renderReportsList = renderReportsList;
package/cli/web.js CHANGED
@@ -4000,25 +4000,31 @@ function buildSettingsHtml(safeDefault, appVersion) {
4000
4000
 
4001
4001
  <div class="devGrid" style="grid-template-columns: 1fr;">
4002
4002
  <div class="panel">
4003
- <div class="panelHead"><b>Mapeamento Inteligente de Projetos (Slugs)</b></div>
4003
+ <div class="panelHead" style="display:flex; justify-content:space-between; align-items:center;">
4004
+ <b>Mapeamento Inteligente de Projetos (Slugs)</b>
4005
+ <span class="chip" style="font-weight:normal; font-size:11px;">Rules</span>
4006
+ </div>
4004
4007
  <div class="panelBody">
4005
4008
  <p style="margin-top: 0; color: #666; font-size: 13px;">
4006
- A FREYA tenta adivinhar automaticamente a qual projeto uma tarefa pertence.
4007
- Se você quiser **garantir** que uma palavra sempre caia em um projeto específico, adicione uma regra abaixo.
4009
+ A FREYA tenta adivinhar automaticamente a qual projeto uma tarefa pertence com base no LLM.
4010
+ Se você quiser <b>garantir</b> que uma palavra sempre caia em um projeto específico, adicione uma regra abaixo.
4008
4011
  </p>
4009
4012
 
4010
- <div style="background: var(--surface); padding: 12px; border-radius: 6px; font-size: 12px; font-family: monospace; color: #888; margin-bottom: 16px;">
4011
- <b>Exemplo de uso:</b><br>
4012
- Se o cliente é a "Vale" e o projeto é "Trato", e você quer que qualquer frase com a palavra "trato" para "vale/trato":<br>
4013
- <code>{ "contains": "trato", "slug": "vale/trato" }</code>
4013
+ <div class="slug-adder" style="display: flex; gap: 8px; margin-bottom: 24px; align-items: flex-end; background: var(--bg2); padding: 12px; border-radius: 8px; border: 1px solid var(--border);">
4014
+ <div style="flex: 1;">
4015
+ <label style="font-size: 12px; color: var(--text-muted); margin-bottom: 4px; display: block;">Palavra-chave (ex: fideliza)</label>
4016
+ <input type="text" id="newSlugKeyword" placeholder="Palavra a buscar..." style="width: 100%;" />
4017
+ </div>
4018
+ <div style="flex: 1;">
4019
+ <label style="font-size: 12px; color: var(--text-muted); margin-bottom: 4px; display: block;">Projeto Alvo (ex: vivo/fidelizacao)</label>
4020
+ <input type="text" id="newSlugTarget" placeholder="Slug do projeto..." style="width: 100%;" />
4021
+ </div>
4022
+ <button class="btn primary" type="button" onclick="window.addSlugRule()" style="height: 36px;">Adicionar Regra</button>
4014
4023
  </div>
4015
4024
 
4016
- <label>Regras de Inferência (Formato JSON)</label>
4017
- <textarea id="slugRules" rows="12" style="font-family: monospace; font-size: 13px;" placeholder='{\n "rules": [\n {\n "contains": "palavra-chave",\n "slug": "cliente/projeto"\n }\n ]\n}'></textarea>
4018
-
4019
- <div class="stack" style="margin-top:20px; flex-direction: row; justify-content: flex-end;">
4020
- <button class="btn" type="button" onclick="window.reloadSlugRules()">Restaurar</button>
4021
- <button class="btn primary" type="button" onclick="window.saveSlugRules()">Salvar Regras</button>
4025
+ <h3 style="font-size: 14px; margin-top: 24px; margin-bottom: 12px;">Regras Ativas</h3>
4026
+ <div id="slugListContainer">
4027
+ <div class="help">Carregando regras...</div>
4022
4028
  </div>
4023
4029
  </div>
4024
4030
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "2.5.4",
3
+ "version": "2.5.5",
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",