diff --git a/tests/test_web_app.py b/tests/test_web_app.py index b8c28f0..0ff46d6 100644 --- a/tests/test_web_app.py +++ b/tests/test_web_app.py @@ -1,4 +1,5 @@ import json +import re import tempfile import unittest from datetime import datetime @@ -10,6 +11,13 @@ from web import app as web_app +REPO_ROOT = Path(__file__).resolve().parents[1] + + +def _read(path: str) -> str: + return (REPO_ROOT / path).read_text(encoding="utf-8") + + class WebAppTests(unittest.TestCase): def setUp(self): self.client = web_app.app.test_client() @@ -47,6 +55,193 @@ def test_manual_post_api_returns_gone_in_fixed_flow_mode(self): self.assertIn("removed", payload["error"].lower()) +class DashboardRefreshTests(unittest.TestCase): + def setUp(self): + self.client = web_app.app.test_client() + + def test_dashboard_and_stats_routes_return_200(self): + with tempfile.TemporaryDirectory() as tmpdir: + old_db_path = database.DB_PATH + old_database_url = database.DATABASE_URL + for attr in ("pg_conn", "sqlite_conn", "conn"): + if hasattr(database._local, attr): + try: + getattr(database._local, attr).close() + except Exception: + pass + setattr(database._local, attr, None) + try: + database.DATABASE_URL = "" + database.DB_PATH = Path(tmpdir) / "stats.db" + database.init_db() + + self.assertEqual(self.client.get("/").status_code, 200) + self.assertEqual(self.client.get("/api/stats").status_code, 200) + finally: + for attr in ("pg_conn", "sqlite_conn", "conn"): + if hasattr(database._local, attr): + try: + getattr(database._local, attr).close() + except Exception: + pass + setattr(database._local, attr, None) + database.DATABASE_URL = old_database_url + database.DB_PATH = old_db_path + + def test_dashboard_main_tabs_are_phase1_six_with_advanced_collapsed(self): + html = _read("web/templates/index.html") + main_tabs = re.findall(r' + `; }).join(''); html += `
-

Research Insights (${insights.length})

+

${esc(tr('explore.researchInsights', { count: insights.length }))}

${insightHtml}
`; } - // Universal patterns for this node + // Cross-node patterns for this node const patterns = data._patterns || []; if (patterns.length > 0) { const patHtml = patterns.map(p => { let domains = []; try { domains = JSON.parse(p.domains || '[]'); } catch(e) {} const levelBadge = p.abstraction_level === 'universal' - ? 'Universal' - : 'Cross-domain'; + ? `${esc(tr('explore.universal'))}` + : `${esc(tr('explore.crossDomain'))}`; return `
${levelBadge} ${esc((p.pattern_type || '').replace(/_/g, ' '))}
${esc(p.pattern_text)}
- ${domains.length ? `
Also applies to: ${domains.map(d => `${esc(d)}`).join(' ')}
` : ''} + ${domains.length ? `
${esc(tr('explore.alsoAppliesTo'))} ${domains.map(d => `${esc(d)}`).join(' ')}
` : ''}
`; }).join(''); html += `
-

Universal Patterns (${patterns.length})

+

${esc(tr('explore.universalPatterns', { count: patterns.length }))}

${patHtml}
`; } @@ -688,9 +706,9 @@ function renderExploreChildren(children) {
${esc(c.name)}
- ${c.paper_count || 0} papers - ${c.method_count || 0} methods - ${c.gap_count ? `${c.gap_count} gaps` : ''} + ${esc(tr('common.papersCount', { count: c.paper_count || 0 }))} + ${esc(tr('common.methodsCount', { count: c.method_count || 0 }))} + ${c.gap_count ? `${esc(tr('common.gapsCount', { count: c.gap_count }))}` : ''}
`).join('')}`; @@ -715,7 +733,7 @@ function renderRadialGraph(svgId, parentNode, children, targetHeight, isPreview) .attr('x', cx).attr('y', cy - 8) .attr('text-anchor', 'middle') .attr('fill', '#9a9088').attr('font-size', '14px').attr('font-weight', '600') - .text('Leaf domain \u2014 see the detailed analysis below'); + .text(tr('explore.leafDomain')); svg.append('text') .attr('x', cx).attr('y', cy + 16) .attr('text-anchor', 'middle') @@ -806,7 +824,7 @@ function renderRadialGraph(svgId, parentNode, children, targetHeight, isPreview) .attr('text-anchor', 'middle').attr('dy', isPreview ? 10 : 12) .attr('fill', d => d.paper_count > 0 ? '#c4704b' : '#b5ada4') .attr('font-size', isPreview ? '8px' : '10px').attr('font-weight', '700') - .text(d => d.paper_count > 0 ? d.paper_count + 'p' : 'empty'); + .text(d => d.paper_count > 0 ? tr('common.paperShort', { count: d.paper_count }) : tr('common.emptyGraphNode')); if (!isPreview) { childG.filter(d => d.gap_count > 0 || d.method_count > 0) @@ -818,7 +836,7 @@ function renderRadialGraph(svgId, parentNode, children, targetHeight, isPreview) .text(d => { const parts = []; if (d.method_count > 0) parts.push(d.method_count + 'M'); - if (d.gap_count > 0) parts.push(d.gap_count + ' gaps'); + if (d.gap_count > 0) parts.push(tr('common.gapsCount', { count: d.gap_count })); return parts.join(' | '); }); } @@ -842,11 +860,11 @@ function renderRadialGraph(svgId, parentNode, children, targetHeight, isPreview)
${esc(d.name)}
${esc(trunc(d.description, 160))}
- ${d.paper_count} papers - ${d.method_count} methods - ${d.gap_count} gaps + ${d.paper_count} ${esc(tr('common.papersUnit'))} + ${d.method_count} ${esc(tr('common.methodsUnit'))} + ${d.gap_count} ${esc(tr('common.gapsUnit'))}
-
Click to explore
+
${esc(tr('common.clickToExplore'))}
`; tip.classList.add('visible'); positionTooltip(e); @@ -882,14 +900,16 @@ function gapColor(gapCount, maxGap) { async function loadTaxonomyDropdown() { if (taxonomyFlat.length > 0) return; // already loaded + taxonomyLoaded = true; try { taxonomyFlat = await api('/api/taxonomy'); const sel = el('evidenceNodeSelect'); - sel.innerHTML = ''; + sel.innerHTML = ``; for (const n of taxonomyFlat) { sel.innerHTML += ``; } } catch (e) { + taxonomyLoaded = false; console.error('Taxonomy dropdown error:', e); } } @@ -898,11 +918,11 @@ async function loadEvidenceForNode(nodeId) { if (!nodeId) { el('evidenceMatrixContainer').innerHTML = ''; el('evidenceGapsCard').style.display = 'none'; - el('evidenceHint').textContent = 'Select a leaf node to view the evidence matrix.'; + el('evidenceHint').textContent = tr('evidence.hint'); return; } - el('evidenceHint').textContent = 'Loading...'; + el('evidenceHint').textContent = tr('common.loading'); try { const data = await api(`/api/taxonomy/${nodeId}`); @@ -910,30 +930,30 @@ async function loadEvidenceForNode(nodeId) { if (m && m.methods && m.methods.length > 0 && m.datasets && m.datasets.length > 0) { renderMatrix(el('evidenceMatrixContainer'), m); - el('evidenceHint').textContent = `${m.methods.length} methods x ${m.datasets.length} datasets`; + el('evidenceHint').textContent = `${tr('common.methodsCount', { count: m.methods.length })} x ${tr('common.datasetsCount', { count: m.datasets.length })}`; } else { - el('evidenceMatrixContainer').innerHTML = '

No structured evidence for this node. Try a leaf node with papers.

'; - el('evidenceHint').textContent = data.is_leaf ? 'No evidence data yet.' : 'Select a leaf node.'; + el('evidenceMatrixContainer').innerHTML = `

${esc(tr('empty.evidenceStructured'))}

`; + el('evidenceHint').textContent = data.is_leaf ? tr('empty.evidenceBenchmark') : tr('evidence.option').replace(/-/g, '').trim(); } // Gaps const gapsCard = el('evidenceGapsCard'); if (data.gaps && data.gaps.length > 0) { gapsCard.style.display = ''; - el('evidenceGapsTitle').textContent = `Matrix Gaps (${data.gaps.length})`; + el('evidenceGapsTitle').textContent = `${tr('evidence.gaps')} (${data.gaps.length})`; renderGaps(el('evidenceGapsBody'), data.gaps); } else { gapsCard.style.display = 'none'; } } catch (e) { console.error('Evidence load error:', e); - el('evidenceHint').textContent = 'Error loading data.'; + el('evidenceHint').textContent = tr('common.errorLoadingData'); } } function renderMatrix(container, matrix) { if (!matrix.methods.length || !matrix.datasets.length) { - container.innerHTML = '

No results data yet.

'; + container.innerHTML = `

${esc(tr('empty.resultData'))}

`; return; } @@ -947,17 +967,17 @@ function renderMatrix(container, matrix) { const defaultMetric = metrics[0] || ''; let html = '
'; - html += ''; + html += ``; html += ''; - html += `${matrix.methods.length} methods x ${matrix.datasets.length} datasets`; + html += `${esc(tr('common.methodsCount', { count: matrix.methods.length }))} x ${esc(tr('common.datasetsCount', { count: matrix.datasets.length }))}`; html += '
'; html += '
'; - html += ''; + html += ``; for (const ds of matrix.datasets) { html += ``; } @@ -972,9 +992,9 @@ function renderMatrix(container, matrix) { if (cell) { const cls = cell.is_sota ? 'cell-sota' : 'cell-filled'; const val = cell.value != null ? Number(cell.value).toFixed(1) : '-'; - html += ``; + html += ``; } else { - html += ``; + html += ``; } } html += ''; @@ -1004,11 +1024,11 @@ function updateMatrixMetric(selectEl) { const val = cell.value != null ? Number(cell.value).toFixed(1) : '-'; td.textContent = val; td.className = 'matrix-cell ' + (cell.is_sota ? 'cell-sota' : 'cell-filled'); - td.title = `${method} on ${ds}: ${val}`; + td.title = `${tr('common.onDataset', { method, dataset: ds })}: ${val}`; } else { td.textContent = '-'; td.className = 'matrix-cell cell-empty'; - td.title = 'No data'; + td.title = tr('common.noData'); } }); }); @@ -1055,7 +1075,7 @@ function renderPapers() { } if (filtered.length === 0) { - list.innerHTML = '

No papers found.

'; + list.innerHTML = `

${esc(tr('empty.papers'))}

`; return; } @@ -1069,7 +1089,7 @@ function renderPapers() { ${esc(p.status || '')}
-
Loading details...
+
${esc(tr('common.loadingDetails'))}
`; }).join(''); @@ -1122,13 +1142,13 @@ function renderPaperPipelineRows(rows) { const papers = (rows || []).slice(0, 20); count.textContent = papers.length; if (!papers.length) { - list.innerHTML = '

No papers are moving through the pipeline right now.

'; + list.innerHTML = `

${esc(tr('empty.paperProgress'))}

`; return; } list.innerHTML = papers.map(item => `
-
${esc(trunc(item.title || item.id || 'Untitled paper', 120))}
+
${esc(trunc(item.title || item.id || tr('common.untitledPaper'), 120))}
${statusBadge(item.processing_stage || item.status || 'queued', toneForPaperStage(item.processing_stage || item.status))}
@@ -1155,7 +1175,7 @@ function renderPaperGenerationRows(jobs, manuscripts) { count.textContent = total; if (!total) { - list.innerHTML = '

No paper-generation jobs are active right now.

'; + list.innerHTML = `

${esc(tr('empty.paperGeneration'))}

`; return; } @@ -1165,19 +1185,19 @@ function renderPaperGenerationRows(jobs, manuscripts) { return `
-
${esc(trunc(job.title || `Idea #${job.deep_insight_id}`, 120))}
+
${esc(trunc(job.title || tr('label.discoveryId', { id: job.deep_insight_id }), 120))}
${statusBadge(stage, toneForPaperStage(job.stage || job.status))}
- Idea #${esc(job.deep_insight_id || '-')} - ${job.experiment_status ? `Experiment: ${esc(job.experiment_status)}` : ''} + ${esc(tr('label.discoveryId', { id: job.deep_insight_id || '-' }))} + ${job.experiment_status ? `${esc(tr('label.experimentStatus', { status: job.experiment_status }))}` : ''} ${job.updated_at ? `${esc(timeAgo(job.updated_at))}` : ''}
${job.last_note ? `
${esc(trunc(job.last_note, 220))}
` : ''} ${job.last_error ? `
${esc(trunc(job.last_error, 220))}
` : ''}
- - ${previewUrl ? `` : ''} + + ${previewUrl ? `` : ''}
`; @@ -1186,17 +1206,17 @@ function renderPaperGenerationRows(jobs, manuscripts) { const manuscriptCards = activeManuscripts.map(row => `
-
${esc(trunc(row.insight_title || `Manuscript #${row.id}`, 120))}
+
${esc(trunc(row.insight_title || tr('label.manuscriptId', { id: row.id }), 120))}
${statusBadge(row.status || 'drafting', toneForPaperStage(row.status))}
- Manuscript #${esc(row.id || '-')} - ${row.hypothesis_verdict ? `Verdict: ${esc(row.hypothesis_verdict)}` : ''} + ${esc(tr('label.manuscriptId', { id: row.id || '-' }))} + ${row.hypothesis_verdict ? `${esc(tr('label.verdict', { verdict: row.hypothesis_verdict }))}` : ''} ${row.updated_at ? `${esc(timeAgo(row.updated_at))}` : ''}
${row.workdir ? `
${esc(trunc(row.workdir, 220))}
` : ''}
- ${row.deep_insight_id ? `` : ''} + ${row.deep_insight_id ? `` : ''}
`); @@ -1205,6 +1225,7 @@ function renderPaperGenerationRows(jobs, manuscripts) { } async function loadPaperProgressTab() { + paperProgressLoaded = true; try { const [automation, jobs, manuscripts] = await Promise.all([ api('/api/automation'), @@ -1217,18 +1238,19 @@ async function loadPaperProgressTab() { return status && !['stale', 'completed', 'ready'].includes(status); }); renderMiniStatGrid('paperProgressStats', [ - { label: 'Pipeline Papers', value: (current.papers || []).length }, - { label: 'Paper Jobs', value: (jobs || []).filter(job => !['completed', 'failed'].includes(String(job.status || '').toLowerCase())).length }, - { label: 'Active Manuscripts', value: activeManuscripts.length }, - { label: 'Blocked Jobs', value: ((automation || {}).auto_research || {}).blocked || 0 }, + { label: tr('paperProgress.stat.sourcePapers'), value: (current.papers || []).length }, + { label: tr('paperProgress.stat.paperJobs'), value: (jobs || []).filter(job => !['completed', 'failed'].includes(String(job.status || '').toLowerCase())).length }, + { label: tr('paperProgress.stat.activeManuscripts'), value: activeManuscripts.length }, + { label: tr('paperProgress.stat.blockedJobs'), value: ((automation || {}).auto_research || {}).blocked || 0 }, ]); renderPaperPipelineRows(current.papers || []); renderPaperGenerationRows(jobs || [], manuscripts || []); } catch (e) { + paperProgressLoaded = false; const listA = el('paperProgressPipelineList'); const listB = el('paperProgressGenerationList'); - if (listA) listA.innerHTML = `

Failed to load paper progress: ${esc(e.message)}

`; - if (listB) listB.innerHTML = `

Failed to load paper generation jobs: ${esc(e.message)}

`; + if (listA) listA.innerHTML = `

${esc(tr('common.failedToLoad', { message: e.message }))}

`; + if (listB) listB.innerHTML = `

${esc(tr('common.failedToLoad', { message: e.message }))}

`; } } @@ -1247,7 +1269,7 @@ function renderGeneratedPapers(manuscripts) { const rows = (manuscripts || []).slice(0, 100); count.textContent = rows.length; if (!rows.length) { - list.innerHTML = '

No manuscript runs have been generated yet.

'; + list.innerHTML = `

${esc(tr('empty.generated'))}

`; return; } list.innerHTML = rows.map(row => { @@ -1255,23 +1277,23 @@ function renderGeneratedPapers(manuscripts) { return `
-
${esc(trunc(row.insight_title || `Manuscript #${row.id}`, 130))}
+
${esc(trunc(row.insight_title || tr('label.manuscriptId', { id: row.id }), 130))}
${statusBadge(row.status || 'generated', manuscriptTone(row.status))}
- Manuscript #${esc(row.id || '-')} - ${row.experiment_run_id ? `Run #${esc(row.experiment_run_id)}` : ''} - ${row.hypothesis_verdict ? `Verdict: ${esc(row.hypothesis_verdict)}` : ''} + ${esc(tr('label.manuscriptId', { id: row.id || '-' }))} + ${row.experiment_run_id ? `${esc(tr('label.experimentRun', { id: row.experiment_run_id }))}` : ''} + ${row.hypothesis_verdict ? `${esc(tr('label.verdict', { verdict: row.hypothesis_verdict }))}` : ''} ${row.updated_at ? `${esc(timeAgo(row.updated_at))}` : ''}
- ${row.created_at ? `Generated: ${esc(fmtDateTime(row.created_at))}` : ''} - ${row.updated_at ? `Updated: ${esc(fmtDateTime(row.updated_at))}` : ''} + ${row.created_at ? `${esc(tr('common.generated', { time: fmtDateTime(row.created_at) }))}` : ''} + ${row.updated_at ? `${esc(tr('common.updated', { time: fmtDateTime(row.updated_at) }))}` : ''}
${row.workdir ? `
${esc(trunc(row.workdir, 220))}
` : ''} -
Use the paper page to open only the assets that actually exist for this manuscript.
+
${esc(tr('label.usePaperPage'))}
- ${preview ? `` : ''} + ${preview ? `` : ''}
`; @@ -1279,6 +1301,7 @@ function renderGeneratedPapers(manuscripts) { } async function loadGeneratedPapersTab() { + generatedPapersLoaded = true; try { const manuscripts = await api('/api/manuscripts?limit=100'); const counts = (manuscripts || []).reduce((acc, row) => { @@ -1288,15 +1311,16 @@ async function loadGeneratedPapersTab() { return acc; }, { total: 0 }); renderMiniStatGrid('generatedPapersStats', [ - { label: 'Total Manuscripts', value: counts.total || 0 }, - { label: 'Ready', value: counts.ready || counts.bundle_ready || 0 }, - { label: 'Stale', value: counts.stale || 0 }, - { label: 'Drafting', value: counts.drafting || 0 }, + { label: tr('generated.stat.total'), value: counts.total || 0 }, + { label: tr('generated.stat.ready'), value: counts.ready || counts.bundle_ready || 0 }, + { label: tr('generated.stat.stale'), value: counts.stale || 0 }, + { label: tr('generated.stat.drafting'), value: counts.drafting || 0 }, ]); renderGeneratedPapers(manuscripts || []); } catch (e) { + generatedPapersLoaded = false; const list = el('generatedPapersList'); - if (list) list.innerHTML = `

Failed to load generated papers: ${esc(e.message)}

`; + if (list) list.innerHTML = `

${esc(tr('common.failedToLoad', { message: e.message }))}

`; } } @@ -1325,30 +1349,31 @@ async function togglePaper(rowEl) { let html = ''; if (claims.length > 0) { - html += '
Claims & Insights'; + html += `
${esc(tr('label.claim'))}`; for (const c of claims.slice(0, 8)) { html += `
${esc(c.claim_text || c.claim_type || '')}
`; } html += '
'; } if (results.length > 0) { - html += '
Results'; + html += `
${esc(tr('label.results'))}`; for (const r of results.slice(0, 8)) { const val = r.metric_value != null ? Number(r.metric_value).toFixed(2) : ''; - html += `
${esc(r.method_name || '')} on ${esc(r.dataset_name || '')}${val ? ': ' + val + '' : ''} ${esc(r.metric_name || '')}
`; + html += `
${esc(tr('common.onDataset', { method: r.method_name || '', dataset: r.dataset_name || '' }))}${val ? ': ' + val + '' : ''} ${esc(r.metric_name || '')}
`; } html += '
'; } - if (!html) html = '

No claims or results extracted yet.

'; + if (!html) html = `

${esc(tr('empty.claimsResults'))}

`; body.innerHTML = html; } catch (e) { - body.innerHTML = '

Failed to load details.

'; + body.innerHTML = `

${esc(tr('common.failedToLoadDetails'))}

`; } } // ── Opportunities Tab ──────────────────────────────────────────────── async function loadInsightsTab() { + insightsLoaded = true; const typeFilter = el('insightTypeFilter')?.value || ''; const sortFilter = el('insightSortFilter')?.value || 'score'; try { @@ -1374,7 +1399,7 @@ async function loadInsightsTab() { const list = el('insightsList'); if (!insights.length) { - list.innerHTML = '

No insights discovered yet. Run the pipeline to analyze papers.

'; + list.innerHTML = `

${esc(tr('empty.researchInsights'))}

`; return; } @@ -1401,15 +1426,16 @@ async function loadInsightsTab() {
${esc(ins.title)}
${ins.rank_rationale ? `
${esc(ins.rank_rationale)}
` : ''} ${paperLinks ? `
${paperLinks}
` : ''} -
Hypothesis: ${esc(ins.hypothesis)}
-
Experiment: ${esc(ins.experiment)}
- ${ins.impact ? `
Impact: ${esc(ins.impact)}
` : ''} +
${esc(tr('label.hypothesis'))} ${esc(ins.hypothesis)}
+
${esc(tr('label.experiment'))} ${esc(ins.experiment)}
+ ${ins.impact ? `
${esc(tr('label.impact'))} ${esc(ins.impact)}
` : ''}
- +
`; }).join(''); } catch (e) { + insightsLoaded = false; console.error('Insights tab error:', e); } } @@ -1430,14 +1456,19 @@ async function loadOpportunities() { } const insightTypeColors = { - contradiction_analysis: { color: '#c4453a', label: 'Contradiction' }, - method_transfer: { color: '#c4704b', label: 'Method Transfer' }, - assumption_challenge: { color: '#a8842a', label: 'Assumption Challenge' }, - ignored_limitation: { color: '#7c5cbf', label: 'Ignored Limitation' }, - paradigm_exhaustion: { color: '#9a9088', label: 'Paradigm Exhaustion' }, - cross_domain_bridge: { color: '#2e86ab', label: 'Cross-Domain Bridge' }, + contradiction_analysis: { color: '#c4453a', labelKey: 'insights.type.contradiction' }, + method_transfer: { color: '#c4704b', labelKey: 'insights.type.methodTransfer' }, + assumption_challenge: { color: '#a8842a', labelKey: 'insights.type.assumptionChallenge' }, + ignored_limitation: { color: '#7c5cbf', labelKey: 'insights.type.ignoredLimitation' }, + paradigm_exhaustion: { color: '#9a9088', labelKey: 'insights.type.paradigmExhaustion' }, + cross_domain_bridge: { color: '#2e86ab', labelKey: 'insights.type.crossDomainBridge' }, }; +function insightTypeLabel(type) { + const meta = insightTypeColors[type] || {}; + return meta.labelKey ? tr(meta.labelKey) : String(type || '').replace(/_/g, ' '); +} + function renderOpportunities() { const list = el('oppList'); const typeFilter = el('oppTypeFilter').value; @@ -1447,13 +1478,13 @@ function renderOpportunities() { const currentVal = select.value; if (select.options.length <= 1 && allOpportunities.length > 0) { // Clear old hardcoded options - select.innerHTML = ''; + select.innerHTML = ``; const types = [...new Set(allOpportunities.map(o => o.insight_type))].filter(Boolean).sort(); for (const t of types) { const meta = insightTypeColors[t] || {}; const opt = document.createElement('option'); opt.value = t; - opt.textContent = meta.label || t.replace(/_/g, ' '); + opt.textContent = insightTypeLabel(t); select.appendChild(opt); } if (currentVal) select.value = currentVal; @@ -1465,12 +1496,12 @@ function renderOpportunities() { } if (filtered.length === 0) { - list.innerHTML = '

No research insights yet. Run the pipeline to discover genuine research opportunities.

'; + list.innerHTML = `

${esc(tr('empty.opportunities'))}

`; return; } list.innerHTML = filtered.map(ins => { - const meta = insightTypeColors[ins.insight_type] || { color: '#888', label: ins.insight_type }; + const meta = insightTypeColors[ins.insight_type] || { color: '#888' }; // Parse supporting papers for links let papers = []; try { papers = JSON.parse(ins.supporting_papers || '[]'); } catch(e) {} @@ -1479,29 +1510,29 @@ function renderOpportunities() { ).join(' '); return `
-
${esc(meta.label)}
+
${esc(insightTypeLabel(ins.insight_type))}
${esc(ins.title)}
- N:${ins.novelty_score || '?'}/5 - F:${ins.feasibility_score || '?'}/5 + N:${ins.novelty_score || '?'}/5 + F:${ins.feasibility_score || '?'}/5
${paperLinks ? `
${paperLinks}
` : ''} ${ins.evidence ? `
- +
${esc(ins.evidence)}
` : ''}
- +
${esc(ins.hypothesis)}
${ins.experiment ? `
- +
${esc(ins.experiment)}
` : ''} ${ins.impact ? `
- +
${esc(ins.impact)}
` : ''}
Method \\ Dataset
${esc(tr('common.methodDataset'))}${esc(trunc(ds, 16))}${val}${val}--
'; - for (const s of tr.signal_types) { + if (track.signal_types && track.signal_types.length) { + html += `

${esc(tr('meta.signalTypePerformance'))}

`; + html += `
SignalTotalConfirmedRefutedHit Rate
`; + for (const s of track.signal_types) { html += ``; } html += '
${esc(tr('meta.signal'))}${esc(tr('meta.total'))}${esc(tr('meta.confirmed'))}${esc(tr('meta.refuted'))}${esc(tr('meta.hitRate'))}
${esc(s.signal_type)}${s.hypothesis_count}${s.confirmed_count}${s.refuted_count}${((s.hit_rate || 0) * 100).toFixed(1)}%
'; @@ -2098,7 +2079,7 @@ function renderMetaReport(meta) { const weights = meta.signal_weights || {}; if (Object.keys(weights).length) { - html += '

Learned Signal Weights

'; + html += `

${esc(tr('meta.learnedSignalWeights'))}

`; for (const [k, v] of Object.entries(weights)) { const color = v > 1.5 ? '#3d8b5e' : v < 0.5 ? '#c4453a' : '#a8842a'; html += `${esc(k)}: ${v}x`; @@ -2118,7 +2099,7 @@ async function loadProviders() { renderProviders(providers); } catch (e) { console.error('Providers load error:', e); - el('providersList').innerHTML = '

Failed to load provider data.

'; + el('providersList').innerHTML = `

${esc(tr('providers.failed'))}

`; } } @@ -2131,13 +2112,13 @@ function renderProviders(providers) { // Convert object to array const arr = Object.entries(providers).map(([k, v]) => ({ name: k, ...v })); if (arr.length === 0) { - list.innerHTML = '

No provider data available.

'; + list.innerHTML = `

${esc(tr('empty.providers'))}

`; return; } renderProviderCards(arr); return; } - list.innerHTML = '

No provider data available.

'; + list.innerHTML = `

${esc(tr('empty.providers'))}

`; return; } @@ -2161,24 +2142,24 @@ function renderProviderCards(providers) { const barPct = Math.round((calls / maxCalls) * 100); return `
-
${esc(p.name || p.provider || 'Unknown')}
+
${esc(p.name || p.provider || tr('providers.unknown'))}
${esc(p.base_url || p.url || '')}
${fmt(calls)} - Calls + ${esc(tr('providers.calls'))}
${fmt(tokens)} - Tokens + ${esc(tr('providers.tokens'))}
${fmt(errors)} - Errors + ${esc(tr('providers.errors'))}
${latency ? latency.toFixed(1) + 's' : '-'} - Avg Latency + ${esc(tr('providers.avgLatency'))}
@@ -2195,6 +2176,40 @@ function startProviderRefresh() { }, 10000); } +// ── Progressive Loading ────────────────────────────────────────────── + +function runWhenIdle(fn, timeout = 700) { + if ('requestIdleCallback' in window) { + window.requestIdleCallback(fn, { timeout }); + } else { + setTimeout(fn, timeout); + } +} + +async function prefetchInactiveTabs() { + if (inactiveTabsPrefetched) return; + inactiveTabsPrefetched = true; + + const tasks = [ + () => loadTaxonomyDropdown(), + () => loadGeneratedPapersTab(), + () => loadInsightsTab(), + () => loadPapers(), + () => loadPaperProgressTab(), + () => loadDiscoveriesTab(), + () => loadExperimentsTab(), + () => loadProviders(), + ]; + + for (const task of tasks) { + try { + await task(); + } catch (e) { + console.debug('Idle prefetch skipped:', e); + } + } +} + // ── Search ─────────────────────────────────────────────────────────── function initSearch() { @@ -2231,7 +2246,7 @@ async function performSearch(query) { const data = await api(`/api/search?q=${encodeURIComponent(query)}`); renderSearchResults(data); } catch (e) { - results.innerHTML = '

Search failed.

'; + results.innerHTML = `

${esc(tr('search.failed'))}

`; results.classList.add('open'); } } @@ -2241,18 +2256,18 @@ function renderSearchResults(data) { let html = ''; if (data.nodes && data.nodes.length) { - html += '
Taxonomy Nodes
'; + html += `
${esc(tr('search.researchAreas'))}
`; for (const n of data.nodes) { html += `
${esc(n.name)}
-
${esc(n.id)} \u00B7 ${n.paper_count || 0} papers
+
${esc(n.id)} \u00B7 ${esc(tr('common.papersCount', { count: n.paper_count || 0 }))}
`; } html += '
'; } if (data.papers && data.papers.length) { - html += '
Papers
'; + html += `
${esc(tr('search.sourcePapers'))}
`; for (const p of data.papers.slice(0, 8)) { html += `
${esc(trunc(p.title, 70))}
@@ -2263,32 +2278,32 @@ function renderSearchResults(data) { } if (data.methods && data.methods.length) { - html += '
Methods
'; + html += `
${esc(tr('search.methods'))}
`; for (const m of data.methods) { html += `
${esc(m.name)}
-
${m.paper_count || 0} papers \u00B7 ${m.result_count || 0} results
+
${esc(tr('common.papersCount', { count: m.paper_count || 0 }))} \u00B7 ${esc(tr('common.resultsCount', { count: m.result_count || 0 }))}
`; } html += '
'; } if (data.opportunities && data.opportunities.length) { - html += '
Opportunities
'; + html += `
${esc(tr('search.opportunities'))}
`; for (const o of data.opportunities) { html += `
${esc(o.title)}
-
${esc(o.node_name || o.node_id)} \u00B7 score ${o.value_score || '?'}/5
+
${esc(o.node_name || o.node_id)} \u00B7 ${esc(tr('common.scoreValue', { score: o.value_score || '?' }))}
`; } html += '
'; } if (data.gaps && data.gaps.length) { - html += '
Gaps
'; + html += `
${esc(tr('search.gaps'))}
`; for (const g of data.gaps) { html += `
-
${esc(g.method_name)} on ${esc(g.dataset_name)}
+
${esc(tr('common.onDataset', { method: g.method_name, dataset: g.dataset_name }))}
${esc(trunc(g.gap_description, 90))}
`; } @@ -2296,7 +2311,7 @@ function renderSearchResults(data) { } if (!html) { - html = '

No results found.

'; + html = `

${esc(tr('empty.search'))}

`; } results.innerHTML = html; @@ -2340,49 +2355,49 @@ window._dg = { let html = `
-

Idea #${insight.id}: ${esc(insight.title || '')}

- 主实验: ${esc(canonical ? `Run #${canonical.id} / ${canonical.status}` : 'not started')} +

${esc(tr('label.discoveryId', { id: insight.id }))}: ${esc(insight.title || '')}

+ ${esc(tr('label.experimentRun', { id: canonical ? `${canonical.id} / ${canonical.status}` : tr('empty.experimentRunCreated') }))}
-

Idea Progress

-

Auto Research: ${esc(auto.status || 'not_started')} ${auto.stage ? `/ ${esc(auto.stage)}` : ''}

-

Submission: ${esc(insight.submission_status || 'not_started')} | Run count: ${runs.length}

-

Workspace: ${esc(data.workspace_root || '-')}

+

${esc(tr('experiments.discoveryProgress'))}

+

${esc(tr('label.autoResearch', { status: `${auto.status || 'not_started'}${auto.stage ? ` / ${auto.stage}` : ''}` }))}

+

${esc(tr('label.submissionRunCount', { status: insight.submission_status || 'not_started', count: runs.length }))}

+

${esc(tr('label.workspace', { path: data.workspace_root || '-' }))}

${renderTrackChips(data.planned_tracks)}
${renderManuscriptBlockers(plan.manuscript_blockers || {}, 8)} - ${auto.last_note ? `

Latest: ${esc(auto.last_note)}

` : ''} - ${auto.last_error ? `

Error: ${esc(auto.last_error)}

` : ''} + ${auto.last_note ? `

${esc(tr('common.latest'))} ${esc(auto.last_note)}

` : ''} + ${auto.last_error ? `

${esc(tr('common.error'))} ${esc(auto.last_error)}

` : ''}
-

实验区

-

实验根目录: ${esc(data.experiment_root || '-')}

-

Canonical Run: ${esc(data.canonical_run_id || canonical?.id || '-')}

+

${esc(tr('label.experimentArea'))}

+

${esc(tr('label.experimentPath', { path: '' }))} ${esc(data.experiment_root || '-')}

+

${esc(tr('label.canonicalRun', { run: '' }))} ${esc(data.canonical_run_id || canonical?.id || '-')}

-

实验方案区

-

方案根目录: ${esc(data.plan_root || '-')}

- ${jsonPreview(plan.latest_status, '暂无 latest_status.json')} +

${esc(tr('label.experimentPlanArea'))}

+

${esc(tr('label.planPath', { path: '' }))} ${esc(data.plan_root || '-')}

+ ${jsonPreview(plan.latest_status, tr('common.noLatestStatus'))}
-

论文区

-

论文根目录: ${esc(data.paper_root || '-')}

+

${esc(tr('label.generatedManuscriptArea'))}

+

${esc(tr('label.generatedManuscriptPath', { path: '' }))} ${esc(data.paper_root || '-')}

- ${paperUrls.index ? `` : ''} - ${paperUrls.pdf ? `` : ''} - ${paperUrls.tex ? `` : ''} + ${paperUrls.index ? `` : ''} + ${paperUrls.pdf ? `` : ''} + ${paperUrls.tex ? `` : ''}
${renderPaperAssetLinks(insight.id, paperAssets)}
`; - html += `

方案快照

- ${jsonPreview(plan.experiment_spec, '暂无 experiment_spec.json')} - ${jsonPreview(plan.manuscript_blockers, 'No manuscript blockers')} - ${jsonPreview(plan.manuscript_input_state, '暂无 manuscript_input_state.json')}`; + html += `

${esc(tr('label.planSnapshot'))}

+ ${jsonPreview(plan.experiment_spec, tr('common.noExperimentSpec'))} + ${jsonPreview(plan.manuscript_blockers, tr('common.noManuscriptBlockers'))} + ${jsonPreview(plan.manuscript_input_state, tr('common.noManuscriptInputState'))}`; if (runs.length) { - html += '

Experiment History

'; + html += `

${esc(tr('label.experimentHistory'))}

`; for (const run of runs) { const color = experimentStatusColor(run.status); const artifactSummary = Object.entries(run.artifact_counts || {}) @@ -2391,30 +2406,30 @@ window._dg = { ? `${esc(run.hypothesis_verdict.toUpperCase())}` : ''; const badges = []; - if (canonical && canonical.id === run.id) badges.push('主实验'); - if (run.has_plot_artifacts) badges.push('可视化'); - if (run.has_bundle) badges.push('论文包'); + if (canonical && canonical.id === run.id) badges.push(tr('label.canonical')); + if (run.has_plot_artifacts) badges.push(tr('label.plot')); + if (run.has_bundle) badges.push(tr('label.submissionBundleBadge')); html += `
- Run #${run.id} + ${esc(tr('label.experimentRun', { id: run.id }))} ${esc(run.status || 'unknown')} ${verdict} ${badges.map(label => `${esc(label)}`).join('')}
- Iterations: ${run.iterations_total || 0} (${run.iterations_kept || 0} kept) - Claims: ${run.claim_count || 0} - ${run.effect_pct != null ? `Effect: ${run.effect_pct.toFixed(2)}%` : ''} - ${artifactSummary ? `Artifacts: ${esc(artifactSummary)}` : ''} + ${esc(tr('label.iterations', { total: run.iterations_total || 0, kept: run.iterations_kept || 0 }))} + ${esc(tr('common.claimsCount', { count: run.claim_count || 0 }))} + ${run.effect_pct != null ? `${esc(tr('label.effect', { effect: run.effect_pct.toFixed(2) + '%' }))}` : ''} + ${artifactSummary ? `${esc(tr('label.artifacts', { artifacts: artifactSummary }))}` : ''}
${run.error_message ? `
${esc(trunc(run.error_message, 220))}
` : ''}
- +
`; } } else { - html += '

No runs yet for this idea.

'; + html += `

${esc(tr('empty.experimentRuns'))}

`; } html += '
'; @@ -2424,7 +2439,7 @@ window._dg = { modal.innerHTML = `
${html}`; document.body.appendChild(modal); } catch (e) { - alert('Failed to load idea history: ' + e.message); + alert(tr('common.failedToLoadIdeaHistory', { message: e.message })); } }, @@ -2437,19 +2452,19 @@ window._dg = { let html = `
-

Experiment #${run.id}: ${esc(run.insight_title || '')}

- Status: ${esc(run.status)} | Verdict: ${esc(run.hypothesis_verdict || 'pending')} +

${esc(tr('label.experimentRun', { id: `#${run.id}` }))}: ${esc(run.insight_title || '')}

+ ${esc(tr('label.statusVerdict', { status: run.status, verdict: run.hypothesis_verdict || 'pending' }))}
-

Metrics

-

Baseline: ${run.baseline_metric_value || '?'} | Best: ${run.best_metric_value || '?'} | Effect: ${run.effect_pct != null ? run.effect_pct.toFixed(2) + '%' : '?'}

-

Iterations: ${run.iterations_total || 0} total, ${run.iterations_kept || 0} kept

- ${run.codebase_url ? `

Codebase: ${esc(run.codebase_url)}

` : ''} - ${run.error_message ? `

Error: ${esc(run.error_message)}

` : ''}`; +

${esc(tr('label.metrics'))}

+

${esc(tr('label.baseline', { value: run.baseline_metric_value || '?' }))} | ${esc(tr('label.best', { value: run.best_metric_value || '?' }))} | ${esc(tr('label.effect', { effect: run.effect_pct != null ? run.effect_pct.toFixed(2) + '%' : '?' }))}

+

${esc(tr('label.iterations', { total: run.iterations_total || 0, kept: run.iterations_kept || 0 }))}

+ ${run.codebase_url ? `

${esc(tr('label.codebase'))} ${esc(run.codebase_url)}

` : ''} + ${run.error_message ? `

${esc(tr('common.error'))} ${esc(run.error_message)}

` : ''}`; if (iters.length) { - html += '

Iteration History

'; + html += `

${esc(tr('label.iterationHistory'))}

#PhaseMetricStatusDescription
`; for (const it of iters.slice(-30)) { const sColor = it.status === 'keep' ? '#3d8b5e' : it.status === 'crash' ? '#c4453a' : '#9a9088'; html += ``; @@ -2458,7 +2473,7 @@ window._dg = { } if (claims.length) { - html += '

Experimental Claims

'; + html += `

${esc(tr('label.experimentalClaims'))}

`; for (const cl of claims) { const vColor = cl.verdict === 'confirmed' ? '#3d8b5e' : cl.verdict === 'refuted' ? '#c4453a' : '#a8842a'; html += `
@@ -2475,7 +2490,7 @@ window._dg = { modal.innerHTML = `
${html}`; document.body.appendChild(modal); } catch (e) { - alert('Failed to load: ' + e.message); + alert(tr('common.failedToLoad', { message: e.message })); } }, @@ -2508,14 +2523,14 @@ window._dg = {

${esc(res.title)}

- ${res.paper_count} papers · ${res.claim_count} claims · ${res.contradiction_count} contradictions + ${esc(tr('common.papersCount', { count: res.paper_count }))} · ${esc(tr('common.claimsCount', { count: res.claim_count }))} · ${esc(tr('common.contradictionsCount', { count: res.contradiction_count }))}
${bodyHtml}
`; document.body.appendChild(modal); } catch (e) { - alert('Failed to load proposal: ' + e.message); + alert(tr('common.failedToLoadProposal', { message: e.message })); } }, }; @@ -2523,8 +2538,19 @@ window._dg = { // ── Init ───────────────────────────────────────────────────────────── function init() { + if (window.dgI18n) { + window.dgI18n.applyI18n(document); + $$('[data-lang]').forEach(btn => { + btn.addEventListener('click', () => window.dgI18n.setLanguage(btn.dataset.lang)); + }); + document.addEventListener('deepgraph:languagechange', () => { + updateLiveBadge(); + if (taxonomyLoaded) loadTaxonomyDropdown(); + }); + } + // Nav items - $$('.nav-item').forEach(btn => { + $$('[data-tab]').forEach(btn => { btn.addEventListener('click', () => switchTab(btn.dataset.tab)); }); @@ -2539,17 +2565,29 @@ function init() { // Discovery filters + generate button const dtf = el('discoveryTierFilter'); - if (dtf) dtf.addEventListener('change', loadDiscoveriesTab); + if (dtf) dtf.addEventListener('change', () => { + discoveriesLoaded = false; + loadDiscoveriesTab(); + }); // Experiment filters const esf = el('experimentStatusFilter'); - if (esf) esf.addEventListener('change', loadExperimentsTab); + if (esf) esf.addEventListener('change', () => { + experimentsLoaded = false; + loadExperimentsTab(); + }); // Insight filters const itf = el('insightTypeFilter'); const isf = el('insightSortFilter'); - if (itf) itf.addEventListener('change', loadInsightsTab); - if (isf) isf.addEventListener('change', loadInsightsTab); + if (itf) itf.addEventListener('change', () => { + insightsLoaded = false; + loadInsightsTab(); + }); + if (isf) isf.addEventListener('change', () => { + insightsLoaded = false; + loadInsightsTab(); + }); // Evidence node select el('evidenceNodeSelect').addEventListener('change', (e) => { @@ -2570,15 +2608,21 @@ function init() { // Initial data loads refreshStats(); loadRecentlyDiscovered(); - loadOverviewGraph(); - loadProcessingPapers(); startSSE(); // Stats refresh every 15s statsTimer = setInterval(refreshStats, 15000); - // Processing panel refresh every 3s (also fetches from API) - setInterval(loadProcessingPapers, 3000); + // Idle work after first text content renders. + runWhenIdle(loadOverviewGraph, 300); + runWhenIdle(prefetchInactiveTabs, 900); + + const overviewAdvanced = el('overviewAdvanced'); + if (overviewAdvanced) { + overviewAdvanced.addEventListener('toggle', () => { + if (overviewAdvanced.open) loadProcessingPapers(); + }); + } // Periodically refresh recently discovered (every 30s) setInterval(loadRecentlyDiscovered, 30000); @@ -2588,6 +2632,7 @@ function init() { if (activeTab === 'paper-progress') loadPaperProgressTab(); if (activeTab === 'generated-papers') loadGeneratedPapersTab(); if (activeTab === 'discoveries') loadDiscoveriesTab(); + if (el('overviewAdvanced')?.open) loadProcessingPapers(); }, 10000); } diff --git a/web/static/js/i18n.js b/web/static/js/i18n.js new file mode 100644 index 0000000..8f86ebe --- /dev/null +++ b/web/static/js/i18n.js @@ -0,0 +1,867 @@ +/* DeepGraph dashboard i18n. Keep en/zh key sets identical. */ +(function () { + "use strict"; + + const I18N = { + en: { + "app.live.idle": "IDLE", + "app.live.live": "LIVE", + "topbar.search.placeholder": "Search source papers, methods, research insights...", + "lang.en": "EN", + "lang.zh": "ZH", + "nav.overview": "Overview", + "nav.explore": "Explore", + "nav.evidence": "Evidence", + "nav.generated": "Generated", + "nav.insights": "Insights", + "nav.papers": "Papers", + "nav.advanced": "Advanced", + "nav.paperProgress": "Paper Progress", + "nav.discoveries": "Discoveries", + "nav.experiments": "Experiments", + "nav.feed": "Feed", + "nav.providers": "Providers", + "nav.agenda": "Agenda", + "footer.fixedFlow": "Fixed-flow mode: pipeline, discovery, experiment runs, and generated manuscripts run automatically.", + "overview.sourcePapers": "Source Paper", + "overview.results": "Result", + "overview.researchAreas": "Research Area", + "overview.contradictions": "Contradiction", + "overview.researchInsights": "Research Insight", + "overview.tokens": "Token", + "overview.experimentRuns": "Experiment Run", + "overview.discoveries": "Discovery", + "overview.submissionBundles": "Submission Bundle", + "overview.latest": "Latest Activity", + "overview.latestEmpty": "Run the pipeline to discover gaps, contradictions, opportunities, and discoveries.", + "overview.processing": "Current Processing", + "overview.idle": "Idle", + "overview.graph": "Research Area Explorer", + "overview.openExplore": "Open in Explore", + "overview.advanced": "Advanced Process Metrics", + "explore.title": "Research Area Explorer", + "explore.summary": "Research Area Summary", + "explore.children": "Sub-area", + "evidence.title": "Benchmark Matrix", + "evidence.select": "Select research area:", + "evidence.option": "-- Select a leaf research area --", + "evidence.hint": "Select a leaf research area to view the benchmark matrix.", + "evidence.gaps": "Matrix Gaps", + "papers.title": "Source Paper", + "papers.filter": "Filter source papers...", + "papers.allStatuses": "All statuses", + "papers.status.extracted": "Extracted", + "papers.status.reasoned": "Reasoned", + "papers.status.processing": "Processing", + "papers.status.fetched": "Fetched", + "papers.status.error": "Error", + "paperProgress.pipeline": "Source Papers In Progress", + "paperProgress.generation": "Generated Manuscript Progress", + "paperProgress.stat.sourcePapers": "Source Papers In Progress", + "paperProgress.stat.paperJobs": "Paper Jobs", + "paperProgress.stat.activeManuscripts": "Active Manuscripts", + "paperProgress.stat.blockedJobs": "Blocked Jobs", + "generated.title": "Generated Manuscript", + "generated.stat.total": "Generated Manuscript", + "generated.stat.ready": "Ready", + "generated.stat.stale": "Stale", + "generated.stat.drafting": "Drafting", + "discoveries.title": "Discovery", + "discoveries.allTiers": "All tiers", + "discoveries.tier1": "Tier 1: Paradigm", + "discoveries.tier2": "Tier 2: Discovery", + "empty.overviewLatest": "Run the pipeline to discover gaps, contradictions, opportunities, and discoveries.", + "empty.processing": "Idle", + "empty.exploreSummary": "No summary generated yet.", + "empty.workstreams": "No workstreams yet.", + "empty.gaps": "No gaps yet.", + "empty.entities": "No entities yet.", + "empty.relations": "No relations yet.", + "empty.evidenceStructured": "No structured benchmark data for this research area. Try a leaf research area with source papers.", + "empty.evidenceBenchmark": "No benchmark data yet.", + "empty.resultData": "No result data yet.", + "empty.papers": "No source papers found.", + "empty.paperProgress": "No source papers are moving through the pipeline right now.", + "empty.paperGeneration": "No generated manuscript jobs are active right now.", + "empty.generated": "No generated manuscript runs are ready yet.", + "empty.claimsResults": "No claims or results extracted yet.", + "empty.researchInsights": "No research insights discovered yet. Run the pipeline to analyze source papers.", + "empty.opportunities": "No research insights yet. Run the pipeline to discover genuine research opportunities.", + "empty.discoveries": "No ready discoveries yet. Automatic discovery is still filtering candidates.", + "empty.autoResearch": "No auto research jobs yet.", + "empty.experiments": "No experiment runs are active yet. The automatic queue will start them when a discovery is ready.", + "empty.experimentGroups": "No discoveries are active yet. The automatic queue will start them when ready.", + "empty.providers": "No provider data available.", + "empty.search": "No results found.", + "empty.manuscriptAssets": "No generated manuscript assets.", + "empty.experimentRuns": "No experiment runs yet for this discovery.", + "empty.experimentRunCreated": "No experiment run created yet", + "common.secondsAgo": "{count}s ago", + "common.minutesAgo": "{count}m ago", + "common.hoursAgo": "{count}h ago", + "common.daysAgo": "{count}d ago", + "common.eventsCount": "{count} events", + "common.papersCount": "{count} papers", + "common.papersUnit": "papers", + "common.methodsCount": "{count} methods", + "common.methodsUnit": "methods", + "common.datasetsCount": "{count} datasets", + "common.gapsCount": "{count} gaps", + "common.gapsUnit": "gaps", + "common.mentionsCount": "{count} mentions", + "common.resultsCount": "{count} results", + "common.claimsCount": "{count} claims", + "common.contradictionsCount": "{count} contradictions", + "common.paperShort": "{count}p", + "common.loading": "Loading...", + "common.loadingDetails": "Loading details...", + "common.failedToLoad": "Failed to load: {message}", + "common.failedToLoadDetails": "Failed to load details.", + "common.failedToLoadProposal": "Failed to load proposal: {message}", + "common.failedToLoadIdeaHistory": "Failed to load idea history: {message}", + "common.errorLoadingData": "Error loading data.", + "common.metric": "Metric:", + "common.noData": "No data", + "common.noneOption": "(none)", + "common.methodDataset": "Method / Dataset", + "common.clickToExplore": "Click to explore", + "common.emptyGraphNode": "empty", + "common.untitled": "Untitled", + "common.untitledPaper": "Untitled paper", + "common.unknown": "Unknown", + "common.scoreValue": "score {score}/5", + "common.valueScore": "value {score}/5", + "common.onDataset": "{method} on {dataset}", + "common.latest": "Latest:", + "common.error": "Error:", + "common.viewDetails": "View Details", + "common.viewRunDetails": "View Run Details", + "common.openPdf": "Open PDF", + "common.openTex": "Open TeX", + "common.generated": "Generated: {time}", + "common.updated": "Updated: {time}", + "common.noLatestStatus": "No latest_status.json", + "common.noExperimentSpec": "No experiment_spec.json", + "common.noManuscriptBlockers": "No manuscript blockers", + "common.noManuscriptInputState": "No manuscript_input_state.json", + "common.openArxiv": "Open on arXiv", + "explore.workstream": "Workstream", + "explore.openGap": "Open gap", + "explore.whyNow": "Why now: {text}", + "explore.whatPeopleWorking": "What People Are Working On", + "explore.whereGapsAre": "Where The Gaps Are", + "explore.recurringThemes": "Recurring Themes", + "explore.methodsDatasets": "Methods & Datasets", + "explore.noneYet": "None yet", + "explore.paperCluster": "Paper Cluster", + "explore.paperClusters": "Paper Clusters", + "explore.sharedEntities": "shared entities: {entities}", + "explore.clusterWeakSignals": "This node has {count} papers, but the current graph signals were not strong enough to form stable clusters yet.", + "explore.coreEntities": "Core Entities", + "explore.keyLinks": "Key Links", + "explore.researchInsights": "Research Insights ({count})", + "explore.universal": "Universal", + "explore.crossDomain": "Cross-domain", + "explore.alsoAppliesTo": "Also applies to:", + "explore.universalPatterns": "Universal Patterns ({count})", + "explore.leafDomain": "Leaf domain — see the detailed analysis below", + "label.discovery": "Discovery", + "label.researchInsight": "Research Insight", + "label.opportunity": "Opportunity", + "label.claim": "Claim", + "label.results": "Results", + "label.evidence": "Evidence:", + "label.hypothesis": "Hypothesis:", + "label.experiment": "Experiment:", + "label.impact": "Impact:", + "label.proposedExperiment": "Proposed Experiment", + "label.potentialImpact": "Potential Impact", + "label.previewProposal": "Preview Proposal", + "label.formalStructure": "Formal Structure:", + "label.transformation": "Transformation:", + "label.fields": "Fields:", + "label.predictions": "Predictions:", + "label.strongestChallenge": "Strongest Challenge:", + "label.problem": "Problem:", + "label.weakness": "Weakness:", + "label.method": "Method:", + "label.baselines": "Baselines:", + "label.datasets": "Datasets:", + "label.compute": "Compute:", + "label.mode": "Mode:", + "label.fixedAutomaticPipeline": "Fixed automatic pipeline", + "label.adversarial": "Adversarial: {score}/10", + "label.experimentStatus": "Experiment: {status}", + "label.verdict": "Verdict: {verdict}", + "label.internalStage": "Internal stage:", + "label.novelty": "Novelty:", + "label.cpuCheck": "CPU Check:", + "label.effect": "Effect: {effect}", + "label.tier": "Tier {tier}", + "label.baseline": "Baseline: {value}", + "label.best": "Best: {value}", + "label.iterations": "Iterations: {total} ({kept} kept)", + "label.repo": "Repo: {repo}", + "label.discoveryId": "Discovery {id}", + "label.experimentRun": "Experiment Run {id}", + "label.experimentRunStatus": "Experiment Run {id} [{status}]", + "label.manuscriptId": "Manuscript #{id}", + "label.submissionBundle": "Submission Bundle: {status}", + "label.latestFileStatus": "Latest file status:", + "label.latestError": "Latest error:", + "label.experimentRuns": "Experiment runs: {count}", + "label.latestExperimentRun": "Latest experiment run: {status}", + "label.experimentPath": "Experiment path: {path}", + "label.planPath": "Plan path: {path}", + "label.generatedManuscriptPath": "Generated manuscript path: {path}", + "label.canonicalRun": "Canonical Run: {run}", + "label.autoResearch": "Auto Research: {status}", + "label.submissionRunCount": "Submission: {status} | Run count: {count}", + "label.workspace": "Workspace: {path}", + "label.statusVerdict": "Status: {status} | Verdict: {verdict}", + "label.metrics": "Metrics", + "label.codebase": "Codebase:", + "label.phase": "Phase", + "label.metric": "Metric", + "label.status": "Status", + "label.description": "Description", + "label.experimentalClaims": "Experimental Claims", + "label.artifacts": "Artifacts: {artifacts}", + "label.iterationHistory": "Iteration History", + "label.experimentHistory": "Experiment History", + "label.planSnapshot": "Plan Snapshot", + "label.experimentArea": "Experiment Area", + "label.experimentPlanArea": "Experiment Plan Area", + "label.generatedManuscriptArea": "Generated Manuscript Area", + "label.openManuscriptPage": "Open manuscript page", + "label.openPaperPreview": "Open paper preview", + "label.openPaperPage": "Open paper page", + "label.usePaperPage": "Use the paper page to open only the assets that actually exist for this manuscript.", + "label.planFilesReady": "experiment spec, evidence plan, manuscript state, manuscript blockers", + "label.waitingPlanFiles": "waiting for plan files", + "label.planFile.experimentSpec": "experiment spec", + "label.planFile.evidencePlan": "evidence plan", + "label.planFile.manuscriptState": "manuscript state", + "label.planFile.manuscriptBlockers": "manuscript blockers", + "label.paperBlocked": "Paper blocked:", + "label.moreBlockers": "{count} more blocker(s)", + "label.canonical": "canonical", + "label.plot": "plot", + "label.submissionBundleBadge": "submission bundle", + "meta.confirmed": "Confirmed", + "meta.refuted": "Refuted", + "meta.hitRate": "Hit Rate", + "meta.signalTypePerformance": "Signal Type Performance", + "meta.signal": "Signal", + "meta.total": "Total", + "meta.learnedSignalWeights": "Learned Signal Weights", + "status.novelty.novel": "NOVEL", + "status.novelty.partial": "PARTIAL", + "status.novelty.exists": "EXISTS", + "status.novelty.unchecked": "UNCHECKED", + "service.state.missing": "missing", + "service.state.active": "active", + "service.state.ready": "ready", + "service.detail.paper": "Batch {batch}, {status}", + "service.detail.auto": "{jobs} jobs, {experiments} experiments, {blocked} blocked", + "service.detail.evo": "{count} active sessions", + "service.detail.paperOrchestra": "{bundles} bundles, {drafting} drafting", + "service.detail.gpu": "{running} running, {queued} queued, {workers} workers", + "status.auto.unavailable": "Auto Research status unavailable.", + "status.auto.running": "RUNNING", + "status.auto.stopped": "STOPPED", + "status.auto.interval": "Interval: {seconds}s", + "status.auto.jobs": "Jobs: {count}", + "status.auto.completed": "Completed: {count}", + "status.auto.blocked": "Blocked: {count}", + "providers.failed": "Failed to load provider data.", + "providers.unknown": "Unknown", + "search.failed": "Search failed.", + "agenda.dispatch.auto": "auto", + "agenda.dispatch.link": "link existing", + "agenda.dispatch.enqueue": "enqueue fresh", + "agenda.dispatch.none": "no dispatch", + "agenda.yamlPlaceholder": "version: v1\nname: ...", + "agenda.active": "active · #{id} {name}", + "agenda.focus": "focus", + "agenda.preferKeywords": "prefer.keywords", + "agenda.preferTiers": "prefer.tiers", + "agenda.reject": "reject", + "agenda.selection": "selection #{id}", + "agenda.status": "status", + "agenda.candidatesJson": "candidates_json", + "agenda.pasteYamlFirst": "Paste YAML first.", + "manuscript.venueCount": "{count} venues", + "manuscript.templatePlaceholder": "template_id (e.g. iclr2026)", + "manuscript.pageCountPlaceholder": "page_count (optional)", + "experiments.services": "Automation Services", + "experiments.readOnly": "Read-only automatic mode", + "experiments.autoResearch": "Auto Research", + "experiments.fixedMode": "Fixed automatic mode", + "experiments.ideaRuns": "Discovery Experiment Runs", + "experiments.all": "All", + "experiments.pending": "Pending", + "experiments.scaffolding": "Scaffolding", + "experiments.reproducing": "Reproducing", + "experiments.testing": "Testing", + "experiments.completed": "Completed", + "experiments.failed": "Failed", + "experiments.meta": "Meta-Learning Report", + "experiments.service.paperPipeline": "Paper Pipeline", + "experiments.service.autoResearch": "Auto Research", + "experiments.service.evoScientist": "EvoScientist", + "experiments.service.paperOrchestra": "PaperOrchestra", + "experiments.service.gpuScheduler": "GPU Scheduler", + "experiments.work.pipeline": "Pipeline activity", + "experiments.work.processingPapers": "Processing source papers", + "experiments.work.experimentPlans": "Generating experiment plans", + "experiments.work.running": "Running experiment runs", + "experiments.work.writing": "Writing generated manuscripts", + "experiments.advancedFields": "Advanced experiment fields", + "experiments.currentWork": "Current work:", + "experiments.planFiles": "Plan files:", + "experiments.viewHistory": "View automation history", + "experiments.viewRun": "View experiment run", + "experiments.openManuscript": "Open generated manuscript", + "experiments.discoveryProgress": "Discovery Progress", + "experiments.run": "Experiment Run", + "insights.title": "Research Insight", + "insights.allTypes": "All types", + "insights.paradigm": "Paradigm-Breaking", + "insights.score": "By N+F Score", + "insights.novelty": "By Novelty", + "insights.feasibility": "By Feasibility", + "insights.recent": "Most Recent", + "insights.type.contradiction": "Contradiction", + "insights.type.contradictionAnalysis": "Contradiction Analysis", + "insights.type.methodTransfer": "Method Transfer", + "insights.type.assumptionChallenge": "Assumption Challenge", + "insights.type.ignoredLimitation": "Ignored Limitation", + "insights.type.paradigmExhaustion": "Paradigm Exhaustion", + "insights.type.crossDomainBridge": "Cross-Domain Bridge", + "status.stage.checkingNovelty": "Checking novelty", + "status.stage.runningResearch": "Running EvoScientist research", + "status.stage.generatingPlan": "Generating experiment plan", + "status.stage.runningGpu": "Running on GPU", + "status.stage.runningExperiment": "Running experiment", + "status.stage.writingPaper": "Writing generated manuscript", + "status.stage.blocked": "Blocked", + "status.stage.failed": "Failed", + "status.stage.complete": "Complete", + "status.stage.queued": "Queued", + "status.cpu.unchecked": "CPU unchecked", + "status.cpu.eligible": "CPU eligible", + "status.cpu.blocked": "CPU blocked", + "status.evo.ready": "EvoScientist ready", + "status.evo.missing": "EvoScientist missing", + "feed.title": "Pipeline Event Feed", + "feed.initialCount": "0 events", + "providers.title": "LLM Providers", + "providers.refresh": "Auto-refresh 10s", + "providers.calls": "Calls", + "providers.tokens": "Tokens", + "providers.errors": "Errors", + "providers.avgLatency": "Avg Latency", + "search.researchAreas": "Research Areas", + "search.sourcePapers": "Source Papers", + "search.methods": "Methods", + "search.opportunities": "Opportunities", + "search.gaps": "Gaps", + "explore.happeningTitle": "What Is Happening In {name}?", + "explore.subareasTitle": "Sub-areas of {name} ({count})", + "agenda.title": "Research Agenda", + "agenda.noAgenda": "no agenda", + "agenda.refresh": "Refresh", + "agenda.select": "Run Selector + Dispatch", + "agenda.upload": "Upload agenda (YAML)", + "agenda.uploadButton": "Upload", + "agenda.latestSelection": "Latest Selection", + "agenda.review": "Run Review", + "agenda.plan": "Build Revision Plan", + "agenda.inspect": "Inspect Full Loop", + "agenda.loop": "Loop Inspection", + "agenda.noActive": "No active agenda. Upload one below.", + "agenda.noSelection": "No selection yet.", + "agenda.noSelectionAlert": "No selection.", + "agenda.uploadFailed": "Upload failed: {detail}", + "agenda.selectFailed": "Select failed: {detail}", + "agenda.reviewFailed": "Review failed: {detail}", + "agenda.planFailed": "Plan failed: {detail}", + "agenda.error": "Error: {detail}", + "agenda.reviewLabel": "REVIEW", + "agenda.revisionPlan": "REVISION PLAN", + "manuscript.routing": "Manuscript Routing & Format Lint", + "manuscript.refreshVenues": "Refresh Venues", + "manuscript.previewRoute": "Preview Route", + "manuscript.previewLint": "Preview Lint", + "manuscript.includeTiebreak": "include tiebreak", + "manuscript.stateJson": "State JSON (for route preview)", + "manuscript.lintPayload": "Lint payload (template_id + source)", + "manuscript.noResult": "No result yet.", + "manuscript.venueCountInitial": "— venues", + "manuscript.errorVenues": "ERROR (venues)", + "manuscript.venues": "VENUES", + "manuscript.stateParseError": "State JSON parse error: {message}", + "manuscript.errorRoute": "ERROR (route)", + "manuscript.routePreview": "ROUTE PREVIEW", + "manuscript.errorLint": "ERROR (lint)", + "manuscript.lintPreview": "LINT PREVIEW" + }, + zh: { + "app.live.idle": "空闲", + "app.live.live": "运行中", + "topbar.search.placeholder": "搜索文献、方法、研究洞见...", + "lang.en": "英", + "lang.zh": "中", + "nav.overview": "概览", + "nav.explore": "探索", + "nav.evidence": "证据", + "nav.generated": "生成", + "nav.insights": "洞见", + "nav.papers": "文献", + "nav.advanced": "高级", + "nav.paperProgress": "文献进度", + "nav.discoveries": "深度发现", + "nav.experiments": "实验", + "nav.feed": "事件流", + "nav.providers": "模型服务", + "nav.agenda": "研究议程", + "footer.fixedFlow": "固定流程模式:流水线、深度发现、实验运行和生成稿件会自动执行。", + "overview.sourcePapers": "文献", + "overview.results": "基准结果", + "overview.researchAreas": "研究领域", + "overview.contradictions": "矛盾", + "overview.researchInsights": "研究洞见", + "overview.tokens": "Token", + "overview.experimentRuns": "实验运行", + "overview.discoveries": "深度发现", + "overview.submissionBundles": "投稿包", + "overview.latest": "最新动态", + "overview.latestEmpty": "运行流水线以发现空白、矛盾、机会点和深度发现。", + "overview.processing": "当前处理", + "overview.idle": "空闲", + "overview.graph": "研究领域探索", + "overview.openExplore": "在探索中打开", + "overview.advanced": "高级过程指标", + "explore.title": "研究领域探索", + "explore.summary": "研究领域摘要", + "explore.children": "子领域", + "evidence.title": "基准矩阵", + "evidence.select": "选择研究领域:", + "evidence.option": "-- 选择叶子研究领域 --", + "evidence.hint": "选择叶子研究领域以查看基准矩阵。", + "evidence.gaps": "矩阵空白", + "papers.title": "文献", + "papers.filter": "筛选文献...", + "papers.allStatuses": "全部状态", + "papers.status.extracted": "已提取", + "papers.status.reasoned": "已推理", + "papers.status.processing": "处理中", + "papers.status.fetched": "已获取", + "papers.status.error": "错误", + "paperProgress.pipeline": "处理中(文献)", + "paperProgress.generation": "生成稿件进度", + "paperProgress.stat.sourcePapers": "处理中(文献)", + "paperProgress.stat.paperJobs": "稿件任务", + "paperProgress.stat.activeManuscripts": "活跃生成稿件", + "paperProgress.stat.blockedJobs": "阻塞任务", + "generated.title": "生成稿件", + "generated.stat.total": "生成稿件", + "generated.stat.ready": "就绪", + "generated.stat.stale": "过期", + "generated.stat.drafting": "草拟中", + "discoveries.title": "深度发现", + "discoveries.allTiers": "全部层级", + "discoveries.tier1": "层级 1:范式", + "discoveries.tier2": "层级 2:深度发现", + "empty.overviewLatest": "运行流水线以发现空白、矛盾、机会点和深度发现。", + "empty.processing": "空闲", + "empty.exploreSummary": "尚未生成摘要。", + "empty.workstreams": "暂无工作流。", + "empty.gaps": "暂无空白。", + "empty.entities": "暂无实体。", + "empty.relations": "暂无关系。", + "empty.evidenceStructured": "该研究领域暂无结构化基准数据。请选择有文献的叶子研究领域。", + "empty.evidenceBenchmark": "暂无基准数据。", + "empty.resultData": "暂无基准结果数据。", + "empty.papers": "未找到文献。", + "empty.paperProgress": "当前没有文献在流水线中处理。", + "empty.paperGeneration": "当前没有生成稿件任务。", + "empty.generated": "暂无生成稿件运行。", + "empty.claimsResults": "尚未提取论断或基准结果。", + "empty.researchInsights": "暂无研究洞见。运行流水线以分析文献。", + "empty.opportunities": "暂无研究洞见。运行流水线以发现真正的研究机会。", + "empty.discoveries": "暂无可展示的深度发现。自动发现仍在筛选候选项。", + "empty.autoResearch": "暂无自动研究任务。", + "empty.experiments": "暂无活跃实验运行。自动队列会在深度发现就绪后启动。", + "empty.experimentGroups": "暂无活跃深度发现。自动队列会在就绪后启动。", + "empty.providers": "暂无模型服务数据。", + "empty.search": "未找到结果。", + "empty.manuscriptAssets": "暂无生成稿件资产。", + "empty.experimentRuns": "该深度发现暂无实验运行。", + "empty.experimentRunCreated": "尚未创建实验运行", + "common.secondsAgo": "{count} 秒前", + "common.minutesAgo": "{count} 分钟前", + "common.hoursAgo": "{count} 小时前", + "common.daysAgo": "{count} 天前", + "common.eventsCount": "{count} 个事件", + "common.papersCount": "{count} 篇文献", + "common.papersUnit": "篇文献", + "common.methodsCount": "{count} 个方法", + "common.methodsUnit": "个方法", + "common.datasetsCount": "{count} 个数据集", + "common.gapsCount": "{count} 个空白", + "common.gapsUnit": "个空白", + "common.mentionsCount": "{count} 次提及", + "common.resultsCount": "{count} 条结果", + "common.claimsCount": "{count} 条论断", + "common.contradictionsCount": "{count} 个矛盾", + "common.paperShort": "{count} 篇", + "common.loading": "加载中...", + "common.loadingDetails": "正在加载详情...", + "common.failedToLoad": "加载失败:{message}", + "common.failedToLoadDetails": "详情加载失败。", + "common.failedToLoadProposal": "研究提案加载失败:{message}", + "common.failedToLoadIdeaHistory": "深度发现历史加载失败:{message}", + "common.errorLoadingData": "数据加载出错。", + "common.metric": "指标:", + "common.noData": "无数据", + "common.noneOption": "(无)", + "common.methodDataset": "方法 / 数据集", + "common.clickToExplore": "点击探索", + "common.emptyGraphNode": "空", + "common.untitled": "未命名", + "common.untitledPaper": "未命名文献", + "common.unknown": "未知", + "common.scoreValue": "分数 {score}/5", + "common.valueScore": "价值 {score}/5", + "common.onDataset": "{method} 在 {dataset}", + "common.latest": "最新:", + "common.error": "错误:", + "common.viewDetails": "查看详情", + "common.viewRunDetails": "查看运行详情", + "common.openPdf": "打开 PDF", + "common.openTex": "打开 TeX", + "common.generated": "生成时间:{time}", + "common.updated": "更新时间:{time}", + "common.noLatestStatus": "无 latest_status.json", + "common.noExperimentSpec": "无 experiment_spec.json", + "common.noManuscriptBlockers": "无稿件阻塞项", + "common.noManuscriptInputState": "无 manuscript_input_state.json", + "common.openArxiv": "在 arXiv 打开", + "explore.workstream": "工作流", + "explore.openGap": "开放空白", + "explore.whyNow": "为何现在:{text}", + "explore.whatPeopleWorking": "大家正在做什么", + "explore.whereGapsAre": "空白在哪里", + "explore.recurringThemes": "反复出现的主题", + "explore.methodsDatasets": "方法与数据集", + "explore.noneYet": "暂无", + "explore.paperCluster": "文献簇", + "explore.paperClusters": "文献簇", + "explore.sharedEntities": "共享实体:{entities}", + "explore.clusterWeakSignals": "该节点有 {count} 篇文献,但当前图谱信号还不足以形成稳定文献簇。", + "explore.coreEntities": "核心实体", + "explore.keyLinks": "关键关系", + "explore.researchInsights": "研究洞见({count})", + "explore.universal": "通用", + "explore.crossDomain": "跨领域", + "explore.alsoAppliesTo": "同样适用于:", + "explore.universalPatterns": "通用模式({count})", + "explore.leafDomain": "叶子领域 — 详见下方分析", + "label.discovery": "深度发现", + "label.researchInsight": "研究洞见", + "label.opportunity": "机会点", + "label.claim": "论断", + "label.results": "结果", + "label.evidence": "证据:", + "label.hypothesis": "假设:", + "label.experiment": "实验:", + "label.impact": "影响:", + "label.proposedExperiment": "拟议实验", + "label.potentialImpact": "潜在影响", + "label.previewProposal": "预览提案", + "label.formalStructure": "形式结构:", + "label.transformation": "转换:", + "label.fields": "领域:", + "label.predictions": "预测:", + "label.strongestChallenge": "最强挑战:", + "label.problem": "问题:", + "label.weakness": "弱点:", + "label.method": "方法:", + "label.baselines": "基线:", + "label.datasets": "数据集:", + "label.compute": "算力:", + "label.mode": "模式:", + "label.fixedAutomaticPipeline": "固定自动流水线", + "label.adversarial": "对抗评分:{score}/10", + "label.experimentStatus": "实验:{status}", + "label.verdict": "结论:{verdict}", + "label.internalStage": "内部阶段:", + "label.novelty": "新颖性:", + "label.cpuCheck": "CPU 检查:", + "label.effect": "效果:{effect}", + "label.tier": "层级 {tier}", + "label.baseline": "基线:{value}", + "label.best": "最佳:{value}", + "label.iterations": "迭代:{total}(保留 {kept})", + "label.repo": "仓库:{repo}", + "label.discoveryId": "深度发现 {id}", + "label.experimentRun": "实验运行 {id}", + "label.experimentRunStatus": "实验运行 {id} [{status}]", + "label.manuscriptId": "稿件 #{id}", + "label.submissionBundle": "投稿包:{status}", + "label.latestFileStatus": "最新文件状态:", + "label.latestError": "最新错误:", + "label.experimentRuns": "实验运行:{count}", + "label.latestExperimentRun": "最新实验运行:{status}", + "label.experimentPath": "实验路径:{path}", + "label.planPath": "计划路径:{path}", + "label.generatedManuscriptPath": "生成稿件路径:{path}", + "label.canonicalRun": "标准运行:{run}", + "label.autoResearch": "自动研究:{status}", + "label.submissionRunCount": "投稿:{status} | 运行数:{count}", + "label.workspace": "工作区:{path}", + "label.statusVerdict": "状态:{status} | 结论:{verdict}", + "label.metrics": "指标", + "label.codebase": "代码库:", + "label.phase": "阶段", + "label.metric": "指标", + "label.status": "状态", + "label.description": "描述", + "label.experimentalClaims": "实验论断", + "label.artifacts": "产物:{artifacts}", + "label.iterationHistory": "迭代历史", + "label.experimentHistory": "实验历史", + "label.planSnapshot": "计划快照", + "label.experimentArea": "实验区域", + "label.experimentPlanArea": "实验计划区域", + "label.generatedManuscriptArea": "生成稿件区域", + "label.openManuscriptPage": "打开稿件页面", + "label.openPaperPreview": "打开稿件预览", + "label.openPaperPage": "打开稿件页面", + "label.usePaperPage": "使用稿件页面只打开该稿件实际存在的资产。", + "label.planFilesReady": "实验规格、证据计划、稿件状态、稿件阻塞项", + "label.waitingPlanFiles": "等待计划文件", + "label.planFile.experimentSpec": "实验规格", + "label.planFile.evidencePlan": "证据计划", + "label.planFile.manuscriptState": "稿件状态", + "label.planFile.manuscriptBlockers": "稿件阻塞项", + "label.paperBlocked": "稿件阻塞:", + "label.moreBlockers": "还有 {count} 个阻塞项", + "label.canonical": "标准", + "label.plot": "图表", + "label.submissionBundleBadge": "投稿包", + "meta.confirmed": "已确认", + "meta.refuted": "已否定", + "meta.hitRate": "命中率", + "meta.signalTypePerformance": "信号类型表现", + "meta.signal": "信号", + "meta.total": "总数", + "meta.learnedSignalWeights": "学习到的信号权重", + "status.novelty.novel": "新颖", + "status.novelty.partial": "部分存在", + "status.novelty.exists": "已存在", + "status.novelty.unchecked": "未检查", + "service.state.missing": "缺失", + "service.state.active": "活跃", + "service.state.ready": "就绪", + "service.detail.paper": "批大小 {batch},{status}", + "service.detail.auto": "{jobs} 个任务,{experiments} 个实验,{blocked} 个阻塞", + "service.detail.evo": "{count} 个活跃会话", + "service.detail.paperOrchestra": "{bundles} 个投稿包,{drafting} 个草稿", + "service.detail.gpu": "{running} 个运行中,{queued} 个排队,{workers} 个工作器", + "status.auto.unavailable": "自动研究状态不可用。", + "status.auto.running": "运行中", + "status.auto.stopped": "已停止", + "status.auto.interval": "间隔:{seconds} 秒", + "status.auto.jobs": "任务:{count}", + "status.auto.completed": "完成:{count}", + "status.auto.blocked": "阻塞:{count}", + "providers.failed": "模型服务数据加载失败。", + "providers.unknown": "未知", + "search.failed": "搜索失败。", + "agenda.dispatch.auto": "自动", + "agenda.dispatch.link": "关联现有", + "agenda.dispatch.enqueue": "新建入队", + "agenda.dispatch.none": "不分发", + "agenda.yamlPlaceholder": "version: v1\nname: ...", + "agenda.active": "活跃 · #{id} {name}", + "agenda.focus": "重点", + "agenda.preferKeywords": "偏好关键词", + "agenda.preferTiers": "偏好层级", + "agenda.reject": "拒绝条件", + "agenda.selection": "选择 #{id}", + "agenda.status": "状态", + "agenda.candidatesJson": "候选 JSON", + "agenda.pasteYamlFirst": "请先粘贴 YAML。", + "manuscript.venueCount": "{count} 个会议模板", + "manuscript.templatePlaceholder": "template_id(例如 iclr2026)", + "manuscript.pageCountPlaceholder": "页数(可选)", + "experiments.services": "自动化服务", + "experiments.readOnly": "只读自动模式", + "experiments.autoResearch": "自动研究", + "experiments.fixedMode": "固定自动模式", + "experiments.ideaRuns": "深度发现实验运行", + "experiments.all": "全部", + "experiments.pending": "待处理", + "experiments.scaffolding": "脚手架", + "experiments.reproducing": "复现中", + "experiments.testing": "测试中", + "experiments.completed": "已完成", + "experiments.failed": "失败", + "experiments.meta": "元学习报告", + "experiments.service.paperPipeline": "文献流水线", + "experiments.service.autoResearch": "自动研究", + "experiments.service.evoScientist": "EvoScientist", + "experiments.service.paperOrchestra": "PaperOrchestra", + "experiments.service.gpuScheduler": "GPU 调度", + "experiments.work.pipeline": "流水线活动", + "experiments.work.processingPapers": "处理中(文献)", + "experiments.work.experimentPlans": "生成实验计划", + "experiments.work.running": "运行实验", + "experiments.work.writing": "撰写生成稿件", + "experiments.advancedFields": "高级实验字段", + "experiments.currentWork": "当前工作:", + "experiments.planFiles": "计划文件:", + "experiments.viewHistory": "查看自动化历史", + "experiments.viewRun": "查看实验运行", + "experiments.openManuscript": "打开生成稿件", + "experiments.discoveryProgress": "深度发现进展", + "experiments.run": "实验运行", + "insights.title": "研究洞见", + "insights.allTypes": "全部类型", + "insights.paradigm": "按范式突破", + "insights.score": "按 N+F 分数", + "insights.novelty": "按新颖性", + "insights.feasibility": "按可行性", + "insights.recent": "最近", + "insights.type.contradiction": "矛盾", + "insights.type.contradictionAnalysis": "矛盾分析", + "insights.type.methodTransfer": "方法迁移", + "insights.type.assumptionChallenge": "假设挑战", + "insights.type.ignoredLimitation": "被忽略的限制", + "insights.type.paradigmExhaustion": "范式耗尽", + "insights.type.crossDomainBridge": "跨领域桥接", + "status.stage.checkingNovelty": "检查新颖性", + "status.stage.runningResearch": "运行 EvoScientist 研究", + "status.stage.generatingPlan": "生成实验计划", + "status.stage.runningGpu": "GPU 运行中", + "status.stage.runningExperiment": "实验运行中", + "status.stage.writingPaper": "撰写生成稿件", + "status.stage.blocked": "已阻塞", + "status.stage.failed": "失败", + "status.stage.complete": "完成", + "status.stage.queued": "排队中", + "status.cpu.unchecked": "CPU 未检查", + "status.cpu.eligible": "CPU 可运行", + "status.cpu.blocked": "CPU 阻塞", + "status.evo.ready": "EvoScientist 就绪", + "status.evo.missing": "EvoScientist 缺失", + "feed.title": "流水线事件流", + "feed.initialCount": "0 个事件", + "providers.title": "模型服务商", + "providers.refresh": "10 秒自动刷新", + "providers.calls": "调用", + "providers.tokens": "Token", + "providers.errors": "错误", + "providers.avgLatency": "平均延迟", + "search.researchAreas": "研究领域", + "search.sourcePapers": "文献", + "search.methods": "方法", + "search.opportunities": "机会点", + "search.gaps": "空白", + "explore.happeningTitle": "{name} 中正在发生什么?", + "explore.subareasTitle": "{name} 的子领域({count})", + "agenda.title": "研究议程", + "agenda.noAgenda": "无议程", + "agenda.refresh": "刷新", + "agenda.select": "运行选择器并分发", + "agenda.upload": "上传议程(YAML)", + "agenda.uploadButton": "上传", + "agenda.latestSelection": "最新选择", + "agenda.review": "运行评审", + "agenda.plan": "生成修订计划", + "agenda.inspect": "检查完整循环", + "agenda.loop": "循环检查", + "agenda.noActive": "暂无活跃议程。请在下方上传。", + "agenda.noSelection": "暂无选择。", + "agenda.noSelectionAlert": "暂无选择。", + "agenda.uploadFailed": "上传失败:{detail}", + "agenda.selectFailed": "选择失败:{detail}", + "agenda.reviewFailed": "评审失败:{detail}", + "agenda.planFailed": "计划失败:{detail}", + "agenda.error": "错误:{detail}", + "agenda.reviewLabel": "评审", + "agenda.revisionPlan": "修订计划", + "manuscript.routing": "稿件路由与格式检查", + "manuscript.refreshVenues": "刷新会议模板", + "manuscript.previewRoute": "预览路由", + "manuscript.previewLint": "预览格式检查", + "manuscript.includeTiebreak": "包含平局规则", + "manuscript.stateJson": "状态 JSON(用于路由预览)", + "manuscript.lintPayload": "格式检查载荷(template_id + source)", + "manuscript.noResult": "暂无结果。", + "manuscript.venueCountInitial": "— 个会议模板", + "manuscript.errorVenues": "错误(会议模板)", + "manuscript.venues": "会议模板", + "manuscript.stateParseError": "状态 JSON 解析错误:{message}", + "manuscript.errorRoute": "错误(路由)", + "manuscript.routePreview": "路由预览", + "manuscript.errorLint": "错误(格式检查)", + "manuscript.lintPreview": "格式检查预览" + }, + }; + + function preferredLanguage() { + const saved = localStorage.getItem("deepgraph.lang"); + if (saved === "en" || saved === "zh") return saved; + return (navigator.language || "").toLowerCase().startsWith('zh') ? "zh" : "en"; + } + + let currentLanguage = preferredLanguage(); + + function t(key, vars) { + const table = I18N[currentLanguage] || I18N.en; + let text = table[key] || I18N.en[key] || key; + if (vars) { + text = text.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, name) => ( + vars[name] == null ? "" : String(vars[name]) + )); + } + return text; + } + + function applyI18n(root) { + const scope = root || document; + scope.querySelectorAll("[data-i18n]").forEach((node) => { + node.textContent = t(node.dataset.i18n); + }); + scope.querySelectorAll("[data-i18n-placeholder]").forEach((node) => { + node.setAttribute("placeholder", t(node.dataset.i18nPlaceholder)); + }); + scope.querySelectorAll("[data-i18n-title]").forEach((node) => { + node.setAttribute("title", t(node.dataset.i18nTitle)); + }); + document.documentElement.lang = currentLanguage === "zh" ? "zh-CN" : "en"; + document.querySelectorAll("[data-lang]").forEach((node) => { + node.classList.toggle("active", node.dataset.lang === currentLanguage); + }); + } + + function setLanguage(lang) { + currentLanguage = lang === "zh" ? "zh" : "en"; + localStorage.setItem("deepgraph.lang", currentLanguage); + applyI18n(document); + document.dispatchEvent(new CustomEvent("deepgraph:languagechange", { detail: { lang: currentLanguage } })); + } + + window.dgI18n = { + I18N, + t, + applyI18n, + setLanguage, + getLanguage: () => currentLanguage, + }; + window.t = t; +})(); diff --git a/web/static/js/manuscript_routing.js b/web/static/js/manuscript_routing.js index caae45a..80310d8 100644 --- a/web/static/js/manuscript_routing.js +++ b/web/static/js/manuscript_routing.js @@ -4,6 +4,10 @@ (function () { const $ = (id) => document.getElementById(id); + function tr(key, vars) { + return window.t ? window.t(key, vars) : key; + } + function fmtJSON(obj) { try { return JSON.stringify(obj, null, 2); } catch (e) { return String(obj); } } @@ -29,18 +33,18 @@ async function loadVenues() { const { status, body } = await jget("/api/manuscript/venues"); - if (status >= 300) { setResult("ERROR (venues)", body); return; } + if (status >= 300) { setResult(tr("manuscript.errorVenues"), body); return; } const venues = (body && body.venues) || []; const badge = $("venueCountBadge"); - if (badge) badge.textContent = `${venues.length} venues`; - setResult("VENUES", venues); + if (badge) badge.textContent = tr("manuscript.venueCount", { count: venues.length }); + setResult(tr("manuscript.venues"), venues); } function parseState() { const raw = ($("mrStateInput") && $("mrStateInput").value || "").trim(); if (!raw) return {}; try { return JSON.parse(raw); } - catch (e) { alert("State JSON parse error: " + e.message); return null; } + catch (e) { alert(tr("manuscript.stateParseError", { message: e.message })); return null; } } async function previewRoute() { @@ -49,7 +53,7 @@ const includeTiebreak = !!($("mrIncludeTiebreak") && $("mrIncludeTiebreak").checked); const payload = Object.assign({}, state, { include_tiebreak: includeTiebreak }); const { status, body } = await jpost("/api/manuscript/route", payload); - setResult(status >= 300 ? "ERROR (route)" : "ROUTE PREVIEW", body); + setResult(status >= 300 ? tr("manuscript.errorRoute") : tr("manuscript.routePreview"), body); } async function previewLint() { @@ -62,7 +66,7 @@ if (!Number.isNaN(n)) payload.page_count = n; } const { status, body } = await jpost("/api/manuscript/lint", payload); - setResult(status >= 300 ? "ERROR (lint)" : "LINT PREVIEW", body); + setResult(status >= 300 ? tr("manuscript.errorLint") : tr("manuscript.lintPreview"), body); } function init() { diff --git a/web/templates/index.html b/web/templates/index.html index dc6025b..d2d7f9d 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -20,32 +20,18 @@ DeepGraph - IDLE -
-
-
- 0 - Papers -
-
- 0 - Results -
-
- 0 - Insights -
-
- 0 - Tokens -
+ IDLE
- +
+
+ + +
@@ -57,58 +43,42 @@ @@ -126,97 +96,99 @@
0
-
Papers
+
Source Paper
0
-
Results
+
Result
0
-
Taxonomy Nodes
+
Research Area
0
-
Contradictions
+
Contradiction
0
-
Insights
+
Research Insight
0
-
Tokens
+
Token
-
+
0
-
Experiments
+
Experiment Run
-
+
0
-
Deep Discoveries
+
Discovery
-
+
0
-
Complete Papers
-
-
- - -
-
-

Currently Processing

- 0 -
-
-

Idle

+
Submission Bundle
-

Recently Discovered

+

Latest Activity

-

Run the pipeline to discover gaps, contradictions, and opportunities.

+

Run the pipeline to discover gaps, contradictions, opportunities, and discoveries.

-

Taxonomy Map

- +

Research Area Explorer

+
+ +
+ Advanced Process Metrics +
+
+

Current Processing

+ 0 +
+
+

+
+
+
@@ -226,19 +198,19 @@

Taxonomy Map

-

Research Area Explorer

+

Research Area Explorer

@@ -250,20 +222,20 @@

Sub-areas

-

Method x Dataset Matrix

+

Benchmark Matrix

- + - Select a leaf node to view the evidence matrix. + Select a leaf research area to view the benchmark matrix.
@@ -275,16 +247,16 @@

Matrix Gaps

-

Paper Database

+

Source Paper

- +
@@ -299,109 +271,108 @@

Paper Database

-

Pipeline Papers

+

Source Papers In Progress

0
-

No papers are moving through the pipeline right now.

+

No source papers are moving through the pipeline right now.

-

Research Paper Generation

+

Generated Manuscript Progress

0
-

No paper-generation jobs are active right now.

+

No generated manuscript jobs are active right now.

- +
-

Generated Papers

+

Generated Manuscript

0
-

No manuscript runs have been generated yet.

+

- +
-

Deep Discoveries

+

Discovery

-

No deep discoveries yet. Discovery runs in fixed automatic mode.

+

- +
-

Automation Services

- Read-only automatic mode +

Automation Services

+ Read-only automatic mode
-

Auto Research

+

Auto Research

- Fixed automatic mode + Fixed automatic mode
-
- Auto Research status unavailable. +
-

No auto research jobs yet.

+

-

Idea Experiments

+

Discovery Experiment Runs

-

No experiment ideas are active yet. The automatic queue will start them when ready.

+

@@ -413,22 +384,23 @@

Meta-Learning Report

-

Research Insights

+

Research Insight

@@ -442,8 +414,8 @@

Research Insights

-

Pipeline Event Feed

- 0 events +

Pipeline Event Feed

+ 0 events
@@ -455,8 +427,8 @@

Pipeline Event Feed

-

LLM Providers

- Auto-refresh 10s +

LLM Providers

+ Auto-refresh 10s
@@ -468,61 +440,61 @@

LLM Providers

-

Research Agenda

- no agenda +

Research Agenda

+ no agenda
- - + +
- Upload agenda (YAML) - - + Upload agenda (YAML) + +
-

Latest Selection

+

Latest Selection

- - - + + +
-

Loop Inspection

+

Loop Inspection


                     
-

Manuscript Routing & Format Lint

- — venues +

Manuscript Routing & Format Lint

+ — venues
- - - + + +
- State JSON (for route preview) + State JSON (for route preview)
- Lint payload (template_id + source) - - + Lint payload (template_id + source) + +
-
No result yet.
+

                         
@@ -554,6 +526,7 @@

Manuscript Routing & Format Lint

+
#${esc(tr('label.phase'))}${esc(tr('label.metric'))}${esc(tr('label.status'))}${esc(tr('label.description'))}
${it.iteration_number}${esc(it.phase)}${it.metric_value != null ? it.metric_value.toFixed(6) : '-'}${esc(it.status)}${esc(trunc(it.description || '', 60))}