diff --git a/files/client/custom/modules/knowledge-base/src/views/kb/index.js b/files/client/custom/modules/knowledge-base/src/views/kb/index.js index 937aff0..161d744 100644 --- a/files/client/custom/modules/knowledge-base/src/views/kb/index.js +++ b/files/client/custom/modules/knowledge-base/src/views/kb/index.js @@ -833,18 +833,15 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) { const src = this._findSourceById(id); if (!src) return; const titleShort = (src.title || '').slice(0, 60); - if (!confirm(`לעבד מחדש את "${titleShort}"?\n\nהקטעים הקיימים יימחקו והקובץ יעובר parse + chunk + embed מחדש.\nהמטא-דאטה (כותרת, מזהה, תאריכים) נשמרים.`)) return; + if (!confirm(`לעבד מחדש את "${titleShort}"?\n\nהקובץ ינותח מחדש על ידי AI; תקבל הצעות מטא-דאטה (כותרת, סיכום, תוויות) לעריכה במסך הסקירה.\nהקטעים יוחלפו רק לאחר שתאשר את ההצעות.`)) return; const self = this; Espo.Ajax.postRequest('KnowledgeBase/action/reingestSource', {id}) .then(res => { - if (res && res.job_id) { - // Surface in the upload-status banner + jobs list, like an upload would. - self._renderJobStatus({ - original_filename: src.title || ('source #' + id), - status: res.status || 'queued', - }); - self._startJobPolling(res.job_id); - // Refresh sources after the polling reports done. + if (res && res.batch_id) { + // Open the same review UI as a fresh upload — the + // backend created a single-file batch wrapping + // this re-ingest job. + self._openBatch(res.batch_id, src.topic_id || self.topicId); } }) .catch(err => { @@ -1003,6 +1000,18 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) { if (typeof ai === 'string') { try { ai = JSON.parse(ai); } catch (e) { ai = {}; } } + // For re-ingest jobs, metadata_json carries the + // existing source metadata so the form can pre-fill + // values (title/labels/etc.) before the AI classifier + // returns. AI suggestions take precedence once they + // arrive, but the fallback prevents an empty form + // during the classify wait — and keeps existing + // labels visible if the user wants to retain them. + let md = it.metadata_json; + if (typeof md === 'string') { + try { md = JSON.parse(md); } catch (e) { md = {}; } + } + const isReingest = !!(md && md.reingest); let s = it.status; if (s === 'processing' && it.processing_stage === 'classifying') s = 'classifying'; else if (s === 'processing' && it.processing_stage === 'embedding') s = 'committing'; @@ -1011,6 +1020,9 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) { filename: it.original_filename, status: s, ai: (ai && Object.keys(ai).length) ? ai : null, + defaults: isReingest ? md : null, + isReingest: isReingest, + sourceId: it.source_id || null, edits: null, error: it.error_message || null, }; @@ -1158,9 +1170,19 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) { const html = _activeBatch.cards.map((c, idx) => { if (c.status === 'awaiting_review') readyCount++; const ai = c.ai || {}; + const defaults = c.defaults || {}; const e = c.edits || {}; - const val = (k) => this.escape(e[k] != null ? e[k] : (ai[k] || '')); - const labelsStr = e.labelsStr != null ? e.labelsStr : ((ai.labels || []).join(', ')); + // Precedence: user edits > AI suggestion > existing source + // (only set for re-ingest cards). For fresh uploads `defaults` + // is empty so this collapses to the previous behavior. + const val = (k) => this.escape( + e[k] != null ? e[k] : + (ai[k] != null && ai[k] !== '' ? ai[k] : + (defaults[k] || '')) + ); + const aiLabels = (ai.labels || []); + const fallbackLabels = aiLabels.length ? aiLabels : (defaults.labels || []); + const labelsStr = e.labelsStr != null ? e.labelsStr : fallbackLabels.join(', '); let body = ''; if (c.status === 'failed') { body = `
${this.escape(c.error || 'שגיאה')}
`; @@ -1181,9 +1203,13 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) { } else { body = '
ממתין לניתוח…
'; } + const cardIcon = c.isReingest ? '🔄' : '📄'; + const reingestTag = c.isReingest + ? `עיבוד מחדש` + : ''; return `
-
📄 ${this.escape(c.filename)}
+
${cardIcon} ${this.escape(c.filename)} ${reingestTag}
${statusLabel(c.status)} ${c.status !== 'done' && c.status !== 'committing' ? `` : ''} @@ -1294,8 +1320,16 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) { if (!ready.length) return; for (const card of ready) { const ai = card.ai || {}; + const defaults = card.defaults || {}; const e = card.edits || {}; - const labelsStr = e.labelsStr != null ? e.labelsStr : ((ai.labels || []).join(', ')); + // Same precedence as _renderBatchCards: edits > AI > defaults. + const pick = (k) => + (e[k] != null ? e[k] : + (ai[k] != null && ai[k] !== '' ? ai[k] : + (defaults[k] || ''))); + const aiLabels = (ai.labels || []); + const fallbackLabels = aiLabels.length ? aiLabels : (defaults.labels || []); + const labelsStr = e.labelsStr != null ? e.labelsStr : fallbackLabels.join(', '); const newLabels = labelsStr .split(',') .map(s => s.trim()) @@ -1303,12 +1337,12 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) { const payload = { id: card.jobId, kind: e.kind || ai.kind || _activeBatch.kind, - title: (e.title != null ? e.title : (ai.title || card.filename)).trim() || card.filename, - identifier: (e.identifier != null ? e.identifier : (ai.identifier || '')).trim() || null, - published_at: (e.published_at != null ? e.published_at : (ai.published_at || '')) || null, - effective_at: (e.effective_at != null ? e.effective_at : (ai.effective_at || '')) || null, - source_url: (e.source_url != null ? e.source_url : (ai.source_url || '')) || null, - summary: (e.summary != null ? e.summary : (ai.summary || '')) || null, + title: (pick('title') || card.filename).trim() || card.filename, + identifier: (pick('identifier') || '').trim() || null, + published_at: pick('published_at') || null, + effective_at: pick('effective_at') || null, + source_url: pick('source_url') || null, + summary: pick('summary') || null, label_slugs: [], new_labels: newLabels, }; diff --git a/manifest.json b/manifest.json index 3c344d6..b0ef625 100644 --- a/manifest.json +++ b/manifest.json @@ -1,14 +1,14 @@ { "name": "KnowledgeBase", "module": "KnowledgeBase", - "version": "0.8.1", + "version": "0.9.0", "acceptableVersions": [ ">=8.0.0" ], "php": [ ">=8.1" ], - "releaseDate": "2026-04-26", + "releaseDate": "2026-04-28", "author": "klear", "description": "Knowledge Base — Israeli National Insurance law, regulations, and circulars. Hybrid search + ask-shira, powered by shira-hermes KB.", "displayLabel": "מאגר ידע"