Add timeline sort toggle (doc order / chronological) with CSS

- Wire sortDocOrder / sortChronological click handlers in renderResults()
- Add .timeline-sort-bar and .sort-btn styles to tools.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 01:50:13 +02:00
parent d429e785e8
commit e156ed4553
2 changed files with 92 additions and 6 deletions
+67 -6
View File
@@ -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 `<pre class="redacted-output">${escapeHtml(lastRedactedText)}</pre>${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
? `<div class="timeline-export"><button type="button" id="exportCsvBtn" class="export-csv-btn">${escapeHtml(csvLabel)}</button></div>`
: '';
return `<p>${escapeHtml(data.what_we_found || '')}</p>${renderTimeline(lastTimelineEvents)}${csvBtn}`;
const sortBar = lastTimelineEvents.length > 1 ? `
<div class="timeline-sort-bar">
<span class="sort-label">Sort:</span>
<button type="button" class="sort-btn is-active" id="sortDocOrder">${escapeHtml(currentTimelineT('sortDocOrder') || 'Document order')}</button>
<button type="button" class="sort-btn" id="sortChronological">${escapeHtml(currentTimelineT('sortChronological') || 'Chronological')}</button>
</div>` : '';
return `<p>${escapeHtml(data.what_we_found || '')}</p>${sortBar}<div id="timelineListContainer">${renderTimeline(lastTimelineEvents)}</div>${csvBtn}`;
}
if (data.tool === 'summarize') {
return [