diff --git a/assets/css/tools.css b/assets/css/tools.css index 66d3c2d..e3536ac 100644 --- a/assets/css/tools.css +++ b/assets/css/tools.css @@ -1122,6 +1122,31 @@ p { margin: 0.35rem 0 0; } +.timeline-sort-bar { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.75rem; + flex-wrap: wrap; +} +.sort-label { + font-size: 0.8rem; + color: var(--muted); +} +.sort-btn { + font-size: 0.78rem; + font-weight: 500; + padding: 0.22rem 0.7rem; + border-radius: 999px; + border: 1px solid var(--line); + background: var(--bg); + color: var(--muted); + cursor: pointer; + transition: background 0.12s, color 0.12s, border-color 0.12s; +} +.sort-btn:hover { background: var(--soft-teal); color: var(--teal-dark); border-color: var(--teal); } +.sort-btn.is-active { background: var(--teal); color: #fff; border-color: var(--teal); } + .timeline-export { margin-top: 1rem; } .export-csv-btn { diff --git a/assets/js/tools.js b/assets/js/tools.js index c89a369..babe7c4 100644 --- a/assets/js/tools.js +++ b/assets/js/tools.js @@ -254,6 +254,11 @@ const TIMELINE_I18N = { timelineRunning: 'Building timeline…', timelineReadyTitle: 'Ready', timelineReadyDesc: 'Paste text or upload a file, configure options, then run.', + timelineBackground: 'Background events', + timelineIncludeBackground: 'Include narrative / background dates', + timelineBackgroundHint: 'When checked, historical context dates are included (e.g. "born 30.07.2015", "met around 2011/2012"). Uncheck to extract only operational events and deadlines.', + sortDocOrder: 'Document order', + sortChronological: 'Chronological', timelineExportCsv: 'Download CSV', }, no: { @@ -287,6 +292,11 @@ const TIMELINE_I18N = { timelineRunning: 'Bygger tidslinje…', timelineReadyTitle: 'Klar', timelineReadyDesc: 'Lim inn tekst eller last opp en fil, konfigurer alternativene, og kjør.', + timelineBackground: 'Bakgrunnshendelser', + timelineIncludeBackground: 'Inkluder narrative / bakgrunnsdatoer', + timelineBackgroundHint: 'Når avkrysset inkluderes historiske kontekstdatoer (f.eks. "født 30.07.2015", "møttes rundt 2011/2012"). Fjern haken for å hente kun operasjonelle hendelser og frister.', + sortDocOrder: 'Dokumentrekkefølge', + sortChronological: 'Kronologisk', timelineExportCsv: 'Last ned CSV', }, uk: { @@ -320,6 +330,11 @@ const TIMELINE_I18N = { timelineRunning: 'Будую хронологію…', timelineReadyTitle: 'Готово', timelineReadyDesc: 'Вставте текст або завантажте файл, налаштуйте параметри, запустіть.', + timelineBackground: 'Фонові події', + timelineIncludeBackground: 'Включити наративні / фонові дати', + timelineBackgroundHint: 'Якщо позначено, включаються дати з контексту (напр. "народився 30.07.2015", "зустрілися близько 2011/2012"). Зніміть, щоб витягувати лише операційні події.', + sortDocOrder: 'Порядок документа', + sortChronological: 'Хронологічний', timelineExportCsv: 'Завантажити CSV', }, pl: { @@ -353,11 +368,17 @@ const TIMELINE_I18N = { timelineRunning: 'Tworzę oś czasu…', timelineReadyTitle: 'Gotowe', timelineReadyDesc: 'Wklej tekst lub wgraj plik, skonfiguruj opcje, uruchom.', + timelineBackground: 'Zdarzenia w tle', + timelineIncludeBackground: 'Uwzględnij daty narracyjne / kontekstowe', + timelineBackgroundHint: 'Gdy zaznaczone, daty z kontekstu są uwzględniane (np. "urodzony 30.07.2015", "poznali się około 2011/2012"). Odznacz, aby wyodrębniać tylko zdarzenia operacyjne.', + sortDocOrder: 'Kolejność dokumentu', + sortChronological: 'Chronologicznie', timelineExportCsv: 'Pobierz CSV', }, }; let lastTimelineEvents = []; +let lastTimelineEventsOriginal = []; let audioQueue = []; // [{file, status: 'pending'|'processing'|'done'|'error', result}] let lastTranscriptData = null; let lastRedactedText = null; @@ -762,6 +783,21 @@ function currentIncludeRelative() { return document.getElementById('includeRelativeCheck')?.checked ?? true; } +function currentIncludeBackground() { + return document.getElementById('includeBackgroundCheck')?.checked ?? true; +} + +function sortChronological(events) { + const isIso = (d) => /^\d{4}-\d{2}/.test(d); + return [...events].sort((a, b) => { + const da = a.date || '', db = b.date || ''; + if (isIso(da) && isIso(db)) return da.localeCompare(db); + if (isIso(da)) return -1; + if (isIso(db)) return 1; + return 0; + }); +} + function setupTimelineControls() { const switcher = document.getElementById('timelineLangSwitcher'); if (!switcher) return; @@ -1040,10 +1076,11 @@ async function runTool(event) { payload.redact_types = currentRedactTypes(); } if (state.activeTool === 'timeline') { - payload.engine = currentTimelineEngine(); - payload.focus = currentTimelineFocus(); - payload.confidence_filter = currentConfidenceFilter(); - payload.include_relative = currentIncludeRelative(); + payload.engine = currentTimelineEngine(); + payload.focus = currentTimelineFocus(); + payload.confidence_filter = currentConfidenceFilter(); + payload.include_relative = currentIncludeRelative(); + payload.include_background = currentIncludeBackground(); } setBusy(true); @@ -1288,6 +1325,23 @@ function renderResults(data) { sections.push(renderFeedbackWidget()); els.results.innerHTML = sections.join(''); setupFeedbackWidget(data.tool || state.activeTool); + + const sortDoc = document.getElementById('sortDocOrder'); + const sortChr = document.getElementById('sortChronological'); + if (sortDoc && sortChr) { + sortDoc.addEventListener('click', () => { + lastTimelineEvents = [...lastTimelineEventsOriginal]; + document.getElementById('timelineListContainer').innerHTML = renderTimeline(lastTimelineEvents); + sortDoc.classList.add('is-active'); + sortChr.classList.remove('is-active'); + }); + sortChr.addEventListener('click', () => { + lastTimelineEvents = sortChronological(lastTimelineEventsOriginal); + document.getElementById('timelineListContainer').innerHTML = renderTimeline(lastTimelineEvents); + sortChr.classList.add('is-active'); + sortDoc.classList.remove('is-active'); + }); + } } function renderMainFinding(data) { @@ -1305,12 +1359,19 @@ function renderMainFinding(data) { return `
${escapeHtml(lastRedactedText)}
${renderEntityCounts(data.entity_counts)}${dlRow}`; } if (data.tool === 'timeline') { - lastTimelineEvents = data.events || []; + lastTimelineEventsOriginal = data.events || []; + lastTimelineEvents = [...lastTimelineEventsOriginal]; const csvLabel = currentTimelineT('timelineExportCsv') || 'Download CSV'; const csvBtn = lastTimelineEvents.length ? `
` : ''; - return `

${escapeHtml(data.what_we_found || '')}

${renderTimeline(lastTimelineEvents)}${csvBtn}`; + const sortBar = lastTimelineEvents.length > 1 ? ` +
+ Sort: + + +
` : ''; + return `

${escapeHtml(data.what_we_found || '')}

${sortBar}
${renderTimeline(lastTimelineEvents)}
${csvBtn}`; } if (data.tool === 'summarize') { return [