deepresearch-flow 0.3.0__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. deepresearch_flow/paper/db.py +184 -0
  2. deepresearch_flow/paper/db_ops.py +1939 -0
  3. deepresearch_flow/paper/web/app.py +38 -3705
  4. deepresearch_flow/paper/web/constants.py +23 -0
  5. deepresearch_flow/paper/web/filters.py +255 -0
  6. deepresearch_flow/paper/web/handlers/__init__.py +14 -0
  7. deepresearch_flow/paper/web/handlers/api.py +217 -0
  8. deepresearch_flow/paper/web/handlers/pages.py +334 -0
  9. deepresearch_flow/paper/web/markdown.py +549 -0
  10. deepresearch_flow/paper/web/static/css/main.css +857 -0
  11. deepresearch_flow/paper/web/static/js/detail.js +406 -0
  12. deepresearch_flow/paper/web/static/js/index.js +266 -0
  13. deepresearch_flow/paper/web/static/js/outline.js +58 -0
  14. deepresearch_flow/paper/web/static/js/stats.js +39 -0
  15. deepresearch_flow/paper/web/templates/base.html +43 -0
  16. deepresearch_flow/paper/web/templates/detail.html +332 -0
  17. deepresearch_flow/paper/web/templates/index.html +114 -0
  18. deepresearch_flow/paper/web/templates/stats.html +29 -0
  19. deepresearch_flow/paper/web/templates.py +85 -0
  20. deepresearch_flow/paper/web/text.py +68 -0
  21. deepresearch_flow/recognize/cli.py +805 -26
  22. deepresearch_flow/recognize/katex_check.js +29 -0
  23. deepresearch_flow/recognize/math.py +719 -0
  24. deepresearch_flow/recognize/mermaid.py +690 -0
  25. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.1.dist-info}/METADATA +78 -4
  26. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.1.dist-info}/RECORD +30 -9
  27. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.1.dist-info}/WHEEL +0 -0
  28. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.1.dist-info}/entry_points.txt +0 -0
  29. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.1.dist-info}/licenses/LICENSE +0 -0
  30. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,58 @@
1
+ /* Outline functionality extracted from outline_assets */
2
+ (function() {
3
+ function initOutline() {
4
+ const content = document.getElementById('content');
5
+ if (!content) return;
6
+
7
+ const headings = content.querySelectorAll('h2, h3, h4');
8
+ if (headings.length === 0) return;
9
+
10
+ const outline = document.getElementById('outline');
11
+ const toggle = document.getElementById('outlineToggle');
12
+ const close = document.getElementById('outlineClose');
13
+ const outlineContent = document.getElementById('outlineContent');
14
+
15
+ if (!outline || !toggle || !close || !outlineContent) return;
16
+
17
+ for (let i = 0; i < headings.length; i++) {
18
+ const h = headings[i];
19
+ if (!h.id) h.id = 'heading-' + i;
20
+ const a = document.createElement('a');
21
+ a.href = '#' + h.id;
22
+ a.textContent = h.textContent.trim();
23
+ a.className = 'outline-' + h.tagName.toLowerCase();
24
+ outlineContent.appendChild(a);
25
+ }
26
+
27
+ toggle.addEventListener('click', function() {
28
+ outline.style.display = 'block';
29
+ toggle.style.display = 'none';
30
+ });
31
+
32
+ close.addEventListener('click', function() {
33
+ outline.style.display = 'none';
34
+ toggle.style.display = 'block';
35
+ });
36
+
37
+ const savedState = sessionStorage.getItem('outlineVisible');
38
+ if (savedState === 'true') {
39
+ outline.style.display = 'block';
40
+ toggle.style.display = 'none';
41
+ } else {
42
+ toggle.style.display = 'block';
43
+ }
44
+
45
+ const observer = new MutationObserver(function() {
46
+ const isVisible = outline.style.display === 'block';
47
+ sessionStorage.setItem('outlineVisible', String(isVisible));
48
+ });
49
+ observer.observe(outline, { attributes: true, attributeFilter: ['style'] });
50
+ }
51
+
52
+ // Run when DOM is ready
53
+ if (document.readyState === 'loading') {
54
+ document.addEventListener('DOMContentLoaded', initOutline);
55
+ } else {
56
+ initOutline();
57
+ }
58
+ })();
@@ -0,0 +1,39 @@
1
+ /* Stats page functionality */
2
+ (function() {
3
+ 'use strict';
4
+
5
+ function initStats() {
6
+ fetch('/api/stats')
7
+ .then(function(res) { return res.json(); })
8
+ .then(function(data) {
9
+ function bar(el, title, items) {
10
+ var chart = echarts.init(document.getElementById(el));
11
+ var labels = items.map(function(x) { return x.label; });
12
+ var counts = items.map(function(x) { return x.count; });
13
+ chart.setOption({
14
+ title: { text: title },
15
+ tooltip: { trigger: 'axis' },
16
+ xAxis: { type: 'category', data: labels },
17
+ yAxis: { type: 'value' },
18
+ series: [{ type: 'bar', data: counts }]
19
+ });
20
+ }
21
+
22
+ if (data.years) bar('year', 'Publication Year', data.years);
23
+ if (data.months) bar('month', 'Publication Month', data.months);
24
+ if (data.tags) bar('tags', 'Top Tags', data.tags.slice(0, 20));
25
+ if (data.keywords) bar('keywords', 'Top Keywords', data.keywords.slice(0, 20));
26
+ if (data.authors) bar('authors', 'Top Authors', data.authors.slice(0, 20));
27
+ if (data.venues) bar('venues', 'Top Venues', data.venues.slice(0, 20));
28
+ })
29
+ .catch(function(err) {
30
+ console.error('Error loading stats:', err);
31
+ });
32
+ }
33
+
34
+ if (document.readyState === 'loading') {
35
+ document.addEventListener('DOMContentLoaded', initStats);
36
+ } else {
37
+ initStats();
38
+ }
39
+ })();
@@ -0,0 +1,43 @@
1
+ <!doctype html>
2
+ <html lang="{{ html_lang|default('en') }}">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="robots" content="noindex, nofollow, noarchive, nosnippet" />
7
+ <meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet" />
8
+ <meta name="bingbot" content="noindex, nofollow, noarchive, nosnippet" />
9
+ <title>{{ title }}</title>
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <link rel="stylesheet" href="/static/css/main.css" />
12
+ {% block extra_head %}{% endblock %}
13
+ </head>
14
+ <body{% if pdf_url %} data-pdf-url="{{ pdf_url }}"{% endif %} class="{{ body_class|default('font-hei') }}">
15
+ {% if not embed %}
16
+ {# Normal mode: show header #}
17
+ {% if header_title %}
18
+ <header class="detail-header">
19
+ <div class="header-row">
20
+ <a class="header-back" href="/">← Papers</a>
21
+ <span class="header-title" title="{{ header_title }}">{{ header_title }}</span>
22
+ <div class="header-right">
23
+ <a class="header-link" href="/stats">Stats</a>
24
+ <a class="header-link" href="{{ repo_url }}" target="_blank" rel="noreferrer">GitHub</a>
25
+ <span class="header-version">v{{ app_version }}</span>
26
+ </div>
27
+ </div>
28
+ </header>
29
+ {% else %}
30
+ <header>
31
+ <a href="/">Papers</a>
32
+ <a href="/stats">Stats</a>
33
+ <a href="{{ repo_url }}" target="_blank" rel="noreferrer">GitHub</a>
34
+ <span class="header-version">v{{ app_version }}</span>
35
+ </header>
36
+ {% endif %}
37
+ {% endif %}
38
+ <div class="container{% if embed %} embed{% endif %}{% if container_class %} {{ container_class }}{% endif %}">
39
+ {% block content %}{% endblock %}
40
+ </div>
41
+ {% block extra_scripts %}{% endblock %}
42
+ </body>
43
+ </html>
@@ -0,0 +1,332 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block extra_head %}
4
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.min.css" />
5
+ <style>
6
+ #content img {
7
+ max-width: 100%;
8
+ height: auto;
9
+ }
10
+ .pdfjs-frame {
11
+ width: 100%;
12
+ height: 100%;
13
+ border: 1px solid #d0d7de;
14
+ border-radius: 10px;
15
+ flex: 1;
16
+ }
17
+ .split-layout {
18
+ display: flex;
19
+ gap: 12px;
20
+ width: 100%;
21
+ max-width: var(--split-max-width, 100%);
22
+ margin: 0 auto;
23
+ flex: 1;
24
+ min-height: 440px;
25
+ }
26
+ .split-pane {
27
+ flex: 1;
28
+ border: 1px solid #d0d7de;
29
+ border-radius: 10px;
30
+ overflow: hidden;
31
+ background: #fff;
32
+ }
33
+ .split-pane iframe {
34
+ width: 100%;
35
+ height: 100%;
36
+ border: 0;
37
+ }
38
+ @media (max-width: 900px) {
39
+ .split-layout {
40
+ flex-direction: column;
41
+ min-height: 0;
42
+ }
43
+ .split-pane {
44
+ height: 70vh;
45
+ }
46
+ }
47
+ </style>
48
+ {% endblock %}
49
+
50
+ {% block content %}
51
+ <div class="detail-shell">
52
+ {# Toolbar #}
53
+ {% if not embed %}
54
+ <div class="detail-toolbar">
55
+ <div class="tabs">
56
+ {% for label, view in tabs %}
57
+ <a class="tab inline-flex items-center rounded-full border px-3 py-1.5 text-sm shadow-sm{% if current_view == view %} border-slate-900 bg-slate-900 text-white{% else %} border-slate-200 bg-white text-slate-700 hover:bg-slate-50{% endif %}" href="{{ view_hrefs[view] }}">{{ label }}</a>
58
+ {% endfor %}
59
+ </div>
60
+ <div class="toolbar-actions">
61
+ {# Template select controls for summary view #}
62
+ {% if current_view == 'summary' and template_controls %}
63
+ {{ template_controls|safe }}
64
+ {% endif %}
65
+ {# Split view inline controls #}
66
+ {% if current_view == 'split' %}
67
+ <div class="split-controls">
68
+ <button id="splitControlsToggle" class="inline-flex h-9 items-center justify-center rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button" aria-expanded="true" title="Toggle split controls">Controls</button>
69
+ <input id="splitSearch" class="split-search h-9 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm" placeholder="Filter views..." aria-label="Filter split views" />
70
+ <div class="split-inline">
71
+ <span class="muted">Left</span>
72
+ <select id="splitLeft" class="h-9 rounded-md border border-slate-200 bg-white px-2 text-sm shadow-sm text-slate-900">
73
+ {% for value, label in split_options %}
74
+ <option value="{{ value }}"{% if value == left_view %} selected{% endif %}>{{ label }}</option>
75
+ {% endfor %}
76
+ </select>
77
+ <div class="split-actions">
78
+ <button id="splitTighten" class="inline-flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button" title="Tighten width">-</button>
79
+ <button id="splitSwap" class="inline-flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button" title="Swap panes">⇄</button>
80
+ <button id="splitWiden" class="inline-flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button" title="Widen width">+</button>
81
+ </div>
82
+ <span class="muted">Right</span>
83
+ <select id="splitRight" class="h-9 rounded-md border border-slate-200 bg-white px-2 text-sm shadow-sm text-slate-900">
84
+ {% for value, label in split_options %}
85
+ <option value="{{ value }}"{% if value == right_view %} selected{% endif %}>{{ label }}</option>
86
+ {% endfor %}
87
+ </select>
88
+ </div>
89
+ </div>
90
+ {% endif %}
91
+ {# Translation language select #}
92
+ {% if current_view == 'translated' and translation_langs %}
93
+ <div class="lang-select">
94
+ <label for="translationLang">Language</label>
95
+ <select id="translationLang" class="h-9 rounded-md border border-slate-200 bg-white px-2 text-sm shadow-sm text-slate-900">
96
+ {% for lang in translation_langs %}
97
+ <option value="{{ lang }}"{% if lang == selected_lang %} selected{% endif %}>{{ lang }}</option>
98
+ {% endfor %}
99
+ </select>
100
+ </div>
101
+ {% endif %}
102
+ {# Fullscreen controls #}
103
+ <div class="fullscreen-actions">
104
+ <button id="fullscreenEnter" class="fullscreen-enter inline-flex h-9 items-center justify-center rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button" title="Enter fullscreen">Fullscreen</button>
105
+ <button id="fullscreenExit" class="fullscreen-exit inline-flex h-9 items-center justify-center rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button" title="Exit Fullscreen">Exit Fullscreen</button>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ {% endif %}
110
+
111
+ <div class="detail-body">
112
+ {# PDF-only warning #}
113
+ {% if is_pdf_only and current_view in ['summary', 'source'] %}
114
+ <div class="warning">PDF-only entry: summary and source views are unavailable.</div>
115
+ {% endif %}
116
+
117
+ {# Main content based on view #}
118
+ {% if current_view == 'summary' %}
119
+ {# Summary view #}
120
+ {% if summary_template_name %}
121
+ <div class="muted">Template: {{ summary_template_name }}</div>
122
+ {% endif %}
123
+ {% if template_warning %}
124
+ {{ template_warning|safe }}
125
+ {% endif %}
126
+ {% if embed and template_controls %}
127
+ {{ template_controls|safe }}
128
+ {% endif %}
129
+ {# Outline #}
130
+ {% if show_outline %}
131
+ <div id="outline" style="position:fixed;top:var(--outline-top,96px);left:var(--outline-left,20px);max-width:260px;max-height:calc(100vh - var(--outline-top,96px) - 60px);overflow-y:auto;padding:12px;background:#ffffff;border:1px solid #d0d7de;border-radius:8px;box-shadow:0 2px 8px rgba(27,31,36,0.08);font-size:13px;z-index:5;display:none;">
132
+ <div style="position:sticky;top:-12px;background:#ffffff;z-index:2;padding:4px 0 6px;">
133
+ <button id="outlineClose" class="inline-flex h-7 items-center justify-center rounded-md border border-slate-200 bg-white px-2 text-xs text-slate-900 shadow-sm hover:bg-slate-50" style="float:right;">Hide</button>
134
+ <strong style="color:#1f2a37;">Outline</strong>
135
+ </div>
136
+ <div id="outlineContent" style="margin-top:8px;"></div>
137
+ </div>
138
+ <div id="outlineToggleContainer" style="position:fixed;top:var(--outline-top,96px);left:var(--outline-left,20px);z-index:4;">
139
+ <button id="outlineToggle" class="inline-flex h-9 items-center justify-center rounded-md bg-slate-900 px-3 text-sm text-white shadow hover:bg-slate-800">☰ Outline</button>
140
+ </div>
141
+ {% endif %}
142
+ <article id="content" class="content">{{ body_html|safe }}</article>
143
+
144
+ {% elif current_view == 'source' %}
145
+ {# Source view #}
146
+ {% if body_html.startswith('<div class="warning') %}
147
+ {# Error state - just show the warning #}
148
+ {{ body_html|safe }}
149
+ {% else %}
150
+ {# Normal state - show content with outline #}
151
+ <div class="muted">{{ source_path }}</div>
152
+ <div class="muted" style="margin-top:10px;">Rendered from source markdown:</div>
153
+ {% if show_outline %}
154
+ <div id="outline" style="position:fixed;top:var(--outline-top,96px);left:var(--outline-left,20px);max-width:260px;max-height:calc(100vh - var(--outline-top,96px) - 60px);overflow-y:auto;padding:12px;background:#ffffff;border:1px solid #d0d7de;border-radius:8px;box-shadow:0 2px 8px rgba(27,31,36,0.08);font-size:13px;z-index:5;display:none;">
155
+ <div style="position:sticky;top:-12px;background:#ffffff;z-index:2;padding:4px 0 6px;">
156
+ <button id="outlineClose" class="inline-flex h-7 items-center justify-center rounded-md border border-slate-200 bg-white px-2 text-xs text-slate-900 shadow-sm hover:bg-slate-50" style="float:right;">Hide</button>
157
+ <strong style="color:#1f2a37;">Outline</strong>
158
+ </div>
159
+ <div id="outlineContent" style="margin-top:8px;"></div>
160
+ </div>
161
+ <div id="outlineToggleContainer" style="position:fixed;top:var(--outline-top,96px);left:var(--outline-left,20px);z-index:4;">
162
+ <button id="outlineToggle" class="inline-flex h-9 items-center justify-center rounded-md bg-slate-900 px-3 text-sm text-white shadow hover:bg-slate-800">☰ Outline</button>
163
+ </div>
164
+ {% endif %}
165
+ <article id="content" class="content">{{ body_html|safe }}</article>
166
+ <details style="margin-top:12px;"><summary>Raw markdown</summary>
167
+ <pre><code>{{ raw_content|escape }}</code></pre>
168
+ </details>
169
+ {% endif %}
170
+
171
+ {% elif current_view == 'translated' %}
172
+ {# Translated view #}
173
+ {% if body_html.startswith('<div class="warning') %}
174
+ {# Error state - just show the warning #}
175
+ {{ body_html|safe }}
176
+ {% else %}
177
+ {# Normal state - show content with outline #}
178
+ <div class="muted">Language: {{ selected_lang }}</div>
179
+ <div class="muted">{{ translated_path }}</div>
180
+ <div class="muted" style="margin-top:10px;">Rendered from translated markdown:</div>
181
+ {% if embed and translation_langs %}
182
+ <div class="lang-select" style="margin: 8px 0;">
183
+ <label for="translationLang">Language</label>
184
+ <select id="translationLang" class="h-9 rounded-md border border-slate-200 bg-white px-2 text-sm text-slate-900 shadow-sm">
185
+ {% for lang in translation_langs %}
186
+ <option value="{{ lang }}"{% if lang == selected_lang %} selected{% endif %}>{{ lang }}</option>
187
+ {% endfor %}
188
+ </select>
189
+ </div>
190
+ {% endif %}
191
+ {% if show_outline %}
192
+ <div id="outline" style="position:fixed;top:var(--outline-top,96px);left:var(--outline-left,20px);max-width:260px;max-height:calc(100vh - var(--outline-top,96px) - 60px);overflow-y:auto;padding:12px;background:#ffffff;border:1px solid #d0d7de;border-radius:8px;box-shadow:0 2px 8px rgba(27,31,36,0.08);font-size:13px;z-index:5;display:none;">
193
+ <div style="position:sticky;top:-12px;background:#ffffff;z-index:2;padding:4px 0 6px;">
194
+ <button id="outlineClose" class="inline-flex h-7 items-center justify-center rounded-md border border-slate-200 bg-white px-2 text-xs text-slate-900 shadow-sm hover:bg-slate-50" style="float:right;">Hide</button>
195
+ <strong style="color:#1f2a37;">Outline</strong>
196
+ </div>
197
+ <div id="outlineContent" style="margin-top:8px;"></div>
198
+ </div>
199
+ <div id="outlineToggleContainer" style="position:fixed;top:var(--outline-top,96px);left:var(--outline-left,20px);z-index:4;">
200
+ <button id="outlineToggle" class="inline-flex h-9 items-center justify-center rounded-md bg-slate-900 px-3 text-sm text-white shadow hover:bg-slate-800">☰ Outline</button>
201
+ </div>
202
+ {% endif %}
203
+ <article id="content" class="content">{{ body_html|safe }}</article>
204
+ <details style="margin-top:12px;"><summary>Raw markdown</summary>
205
+ <pre><code>{{ raw_content|escape }}</code></pre>
206
+ </details>
207
+ {% endif %}
208
+
209
+ {% elif current_view == 'pdf' %}
210
+ {# PDF view (canvas) #}
211
+ {% if body_html.startswith('<div class="warning') %}
212
+ {{ body_html|safe }}
213
+ {% else %}
214
+ <div class="muted">{{ pdf_filename }}</div>
215
+ <div class="flex flex-wrap items-center gap-2 py-2">
216
+ <button id="prev" class="inline-flex h-9 items-center justify-center rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button">Prev</button>
217
+ <button id="next" class="inline-flex h-9 items-center justify-center rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button">Next</button>
218
+ <span class="muted">Page <span id="page_num">1</span> / <span id="page_count">?</span></span>
219
+ <span class="flex-1"></span>
220
+ <button id="zoomOut" class="inline-flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button">-</button>
221
+ <button id="zoomIn" class="inline-flex h-9 w-9 items-center justify-center rounded-md border border-slate-200 bg-white text-sm text-slate-900 shadow-sm hover:bg-slate-50" type="button">+</button>
222
+ </div>
223
+ <canvas id="the-canvas" class="w-full rounded-lg border border-slate-200"></canvas>
224
+ {% endif %}
225
+
226
+ {% elif current_view == 'pdfjs' %}
227
+ {# PDF.js viewer view #}
228
+ {% if body_html.startswith('<div class="warning') %}
229
+ {{ body_html|safe }}
230
+ {% else %}
231
+ <div class="muted">{{ pdf_filename }}</div>
232
+ <iframe class="pdfjs-frame" src="{{ pdfjs_url }}" title="PDF.js Viewer"></iframe>
233
+ {% endif %}
234
+
235
+ {% elif current_view == 'split' %}
236
+ {# Split view #}
237
+ <div class="split-layout">
238
+ <div class="split-pane">
239
+ <iframe id="leftPane" src="{{ left_src }}" title="Left pane"></iframe>
240
+ </div>
241
+ <div class="split-pane">
242
+ <iframe id="rightPane" src="{{ right_src }}" title="Right pane"></iframe>
243
+ </div>
244
+ </div>
245
+
246
+ {% endif %}
247
+ </div>
248
+ <button id="backToTop" class="back-to-top" type="button" title="Back to top">↑</button>
249
+ </div>
250
+ {% endblock %}
251
+
252
+ {% block extra_scripts %}
253
+ {# CDN scripts for markdown rendering #}
254
+ <script src="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.min.js"></script>
255
+ <script src="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/contrib/auto-render.min.js"></script>
256
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
257
+ <script src="https://cdn.jsdelivr.net/npm/markmap-lib@0.15.4/dist/browser/index.min.js"></script>
258
+ <script src="https://cdn.jsdelivr.net/npm/markmap-view@0.15.4/dist/index.min.js"></script>
259
+
260
+ {# PDF.js for PDF view #}
261
+ {% if current_view == 'pdf' %}
262
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.min.js"></script>
263
+ {% endif %}
264
+
265
+ {# Main detail JavaScript #}
266
+ <script src="/static/js/detail.js"></script>
267
+
268
+ {# Outline functionality #}
269
+ {% if show_outline %}
270
+ <script>
271
+ (function() {
272
+ function initOutline() {
273
+ var content = document.getElementById('content');
274
+ if (!content) return;
275
+ var headings = content.querySelectorAll('h2, h3, h4');
276
+ if (headings.length === 0) return;
277
+
278
+ var outline = document.getElementById('outline');
279
+ var toggle = document.getElementById('outlineToggle');
280
+ var close = document.getElementById('outlineClose');
281
+ var outlineContent = document.getElementById('outlineContent');
282
+
283
+ if (!outline || !toggle || !close || !outlineContent) return;
284
+
285
+ for (var i = 0; i < headings.length; i++) {
286
+ var h = headings[i];
287
+ if (!h.id) h.id = 'heading-' + i;
288
+ var a = document.createElement('a');
289
+ a.href = '#' + h.id;
290
+ a.textContent = h.textContent.trim();
291
+ a.className = 'outline-' + h.tagName.toLowerCase();
292
+ outlineContent.appendChild(a);
293
+ }
294
+
295
+ toggle.addEventListener('click', function() {
296
+ outline.style.display = 'block';
297
+ toggle.style.display = 'none';
298
+ document.body.classList.add('outline-open');
299
+ });
300
+
301
+ close.addEventListener('click', function() {
302
+ outline.style.display = 'none';
303
+ toggle.style.display = 'block';
304
+ document.body.classList.remove('outline-open');
305
+ });
306
+
307
+ var savedState = sessionStorage.getItem('outlineVisible');
308
+ if (savedState === 'true') {
309
+ outline.style.display = 'block';
310
+ toggle.style.display = 'none';
311
+ document.body.classList.add('outline-open');
312
+ } else {
313
+ toggle.style.display = 'block';
314
+ document.body.classList.remove('outline-open');
315
+ }
316
+
317
+ var observer = new MutationObserver(function() {
318
+ var isVisible = outline.style.display === 'block';
319
+ sessionStorage.setItem('outlineVisible', String(isVisible));
320
+ });
321
+ observer.observe(outline, { attributes: true, attributeFilter: ['style'] });
322
+ }
323
+
324
+ if (document.readyState === 'loading') {
325
+ document.addEventListener('DOMContentLoaded', initOutline);
326
+ } else {
327
+ initOutline();
328
+ }
329
+ })();
330
+ </script>
331
+ {% endif %}
332
+ {% endblock %}
@@ -0,0 +1,114 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block extra_head %}
4
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.min.css" />
5
+ <style>
6
+ #content img {
7
+ max-width: 100%;
8
+ height: auto;
9
+ }
10
+ </style>
11
+ {% endblock %}
12
+
13
+ {% block content %}
14
+ <h2>Paper Database</h2>
15
+ <div class="card rounded-2xl border border-slate-200 bg-white/80 shadow-sm backdrop-blur">
16
+ <div class="muted">Search (Scholar-style): <code>tag:fpga year:2023..2025 -survey</code> · Use quotes for phrases and <code>OR</code> for alternatives.</div>
17
+ <div class="search-row">
18
+ <input id="query" class="h-11 w-full rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950/20" placeholder='Search... e.g. title:"nearest neighbor" tag:fpga year:2023..2025' />
19
+ <select id="openView" class="h-11 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm">
20
+ <option value="summary" selected>Open: Summary</option>
21
+ <option value="source">Open: Source</option>
22
+ <option value="translated">Open: Translated</option>
23
+ <option value="pdf">Open: PDF</option>
24
+ <option value="pdfjs">Open: PDF Viewer</option>
25
+ <option value="split">Open: Split</option>
26
+ </select>
27
+ </div>
28
+ <div class="filters" style="margin-top:10px;">
29
+ <div class="filter-group">
30
+ <label>PDF</label>
31
+ <select id="filterPdf" multiple size="2" class="h-20 rounded-md border border-slate-200 bg-white px-2 text-sm text-slate-900 shadow-sm">
32
+ <option value="with">With</option>
33
+ <option value="without">Without</option>
34
+ </select>
35
+ </div>
36
+ <div class="filter-group">
37
+ <label>Source</label>
38
+ <select id="filterSource" multiple size="2" class="h-20 rounded-md border border-slate-200 bg-white px-2 text-sm text-slate-900 shadow-sm">
39
+ <option value="with">With</option>
40
+ <option value="without">Without</option>
41
+ </select>
42
+ </div>
43
+ <div class="filter-group">
44
+ <label>Translated</label>
45
+ <select id="filterTranslated" multiple size="2" class="h-20 rounded-md border border-slate-200 bg-white px-2 text-sm text-slate-900 shadow-sm">
46
+ <option value="with">With</option>
47
+ <option value="without">Without</option>
48
+ </select>
49
+ </div>
50
+ <div class="filter-group">
51
+ <label>Summary</label>
52
+ <select id="filterSummary" multiple size="2" class="h-20 rounded-md border border-slate-200 bg-white px-2 text-sm text-slate-900 shadow-sm">
53
+ <option value="with">With</option>
54
+ <option value="without">Without</option>
55
+ </select>
56
+ </div>
57
+ <div class="filter-group">
58
+ <label>Template</label>
59
+ <select id="filterTemplate" multiple size="4" class="h-28 rounded-md border border-slate-200 bg-white px-2 text-sm text-slate-900 shadow-sm">
60
+ {% if template_tags %}
61
+ {% for tag in template_tags %}
62
+ <option value="{{ tag }}">{{ tag }}</option>
63
+ {% endfor %}
64
+ {% else %}
65
+ <option value="" disabled>(no templates)</option>
66
+ {% endif %}
67
+ </select>
68
+ </div>
69
+ </div>
70
+ <div class="filter-row">
71
+ <input id="filterQuery" class="h-10 w-full rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm" placeholder='Filters... e.g. pdf:yes tmpl:simple' />
72
+ <select id="sortBy" class="h-10 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm">
73
+ <option value="" selected>Sort: Default</option>
74
+ <option value="year">Sort: Year</option>
75
+ <option value="title">Sort: Title</option>
76
+ <option value="venue">Sort: Venue</option>
77
+ <option value="author">Sort: First Author</option>
78
+ </select>
79
+ <select id="sortDir" class="h-10 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm">
80
+ <option value="desc" selected>Order: Desc</option>
81
+ <option value="asc">Order: Asc</option>
82
+ </select>
83
+ <span class="help-icon" data-tip="{{ filter_help | replace('\n', '&#10;') | safe }}">?</span>
84
+ </div>
85
+ <details style="margin-top:10px;">
86
+ <summary>Advanced search</summary>
87
+ <div style="margin-top:10px;" class="muted">Build a query:</div>
88
+ <div class="filters">
89
+ <input id="advTitle" class="h-10 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm" placeholder="title contains..." />
90
+ <input id="advAuthor" class="h-10 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm" placeholder="author contains..." />
91
+ <input id="advTag" class="h-10 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm" placeholder="tag (comma separated)" />
92
+ <input id="advYear" class="h-10 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm" placeholder="year (e.g. 2020..2024)" />
93
+ <input id="advMonth" class="h-10 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm" placeholder="month (01-12)" />
94
+ <input id="advVenue" class="h-10 rounded-md border border-slate-200 bg-white px-3 text-sm text-slate-900 shadow-sm" placeholder="venue contains..." />
95
+ </div>
96
+ <div class="adv-actions">
97
+ <button id="buildQuery" class="inline-flex h-10 items-center justify-center rounded-md bg-slate-900 px-4 text-sm font-medium text-white shadow hover:bg-slate-800">Build</button>
98
+ <div class="muted">Generated: <code id="generated"></code></div>
99
+ </div>
100
+ </details>
101
+ </div>
102
+ <div id="stats" class="stats mt-3 grid gap-2">
103
+ <div id="statsTotal" class="stats-row flex flex-wrap items-center gap-2"></div>
104
+ <div id="statsFiltered" class="stats-row flex flex-wrap items-center gap-2"></div>
105
+ </div>
106
+ <div id="results"></div>
107
+ <div id="loading" class="muted">Loading...</div>
108
+ {% endblock %}
109
+
110
+ {% block extra_scripts %}
111
+ <script src="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.min.js"></script>
112
+ <script src="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/contrib/auto-render.min.js"></script>
113
+ <script src="/static/js/index.js"></script>
114
+ {% endblock %}
@@ -0,0 +1,29 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block extra_head %}
4
+ <style>
5
+ #year, #month, #tags, #keywords, #authors, #venues {
6
+ width: 100%;
7
+ height: 360px;
8
+ }
9
+ #tags, #keywords, #authors, #venues {
10
+ height: 420px;
11
+ }
12
+ </style>
13
+ {% endblock %}
14
+
15
+ {% block content %}
16
+ <h2>Stats</h2>
17
+ <div class="muted">Charts are rendered with ECharts (CDN).</div>
18
+ <div id="year"></div>
19
+ <div id="month"></div>
20
+ <div id="tags"></div>
21
+ <div id="keywords"></div>
22
+ <div id="authors"></div>
23
+ <div id="venues"></div>
24
+ {% endblock %}
25
+
26
+ {% block extra_scripts %}
27
+ <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
28
+ <script src="/static/js/stats.js"></script>
29
+ {% endblock %}