pvanand commited on
Commit
dba5081
·
verified ·
1 Parent(s): 9e628ae

Update templates/apps-hub/article-writer.html

Browse files
Files changed (1) hide show
  1. templates/apps-hub/article-writer.html +352 -352
templates/apps-hub/article-writer.html CHANGED
@@ -1,353 +1,353 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Report Generator</title>
7
- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
8
- <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
9
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
10
- <script src="https://cdn.jsdelivr.net/npm/appwrite@15.0.0"></script>
11
- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
12
- <style>
13
- /* Main styles */
14
- /* body {
15
- margin: 0;
16
- padding: 0;
17
- text-rendering: optimizeLegibility;
18
- -webkit-font-smoothing: antialiased;
19
- color: rgba(0,0,0,0.8);
20
- position: relative;
21
- min-height: 100vh;
22
- font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif;
23
- font-size: 20px;
24
- line-height: 1.58;
25
- color: rgba(0, 0, 0, 0.8);
26
- background-color: #fff;
27
- } */
28
- .container {
29
- text-rendering: optimizeLegibility;
30
- -webkit-font-smoothing: antialiased;
31
- color: rgba(0,0,0,0.8);
32
- position: relative;
33
- min-height: 100vh;
34
- font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif;
35
- font-size: 20px;
36
- line-height: 1.58;
37
- color: rgba(0, 0, 0, 0.8);
38
- background-color: #fff;
39
- max-width: 800px;
40
- margin: 0 auto;
41
- padding: 0 20px;
42
- }
43
- /* Typography */
44
- h1, h2, h3, h4, h5, h6 {
45
- font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif;
46
- font-weight: 700;
47
- color: rgba(0, 0, 0, 0.84);
48
- letter-spacing: -0.022em;
49
- line-height: 1.2;
50
- }
51
- h1 {
52
- font-size: 40px;
53
- margin-bottom: 0.5em;
54
- }
55
- h2 {
56
- font-size: 32px;
57
- margin-top: 1.5em;
58
- margin-bottom: 0.5em;
59
- }
60
- h3 {
61
- font-size: 26px;
62
- margin-top: 1.5em;
63
- margin-bottom: 0.5em;
64
- }
65
- p {
66
- margin-bottom: 32px;
67
- }
68
- /* Links */
69
- a {
70
- color: #1a8917;
71
- text-decoration: none;
72
- }
73
- a:hover {
74
- text-decoration: underline;
75
- }
76
- /* Input container */
77
- #input-container {
78
- margin-bottom: 40px;
79
- background-color: #f9f9f9;
80
- padding: 32px;
81
- border-radius: 5px;
82
- }
83
- textarea {
84
- width: 100%;
85
- padding: 12px;
86
- margin-bottom: 20px;
87
- border: 1px solid rgba(0, 0, 0, 0.15);
88
- border-radius: 4px;
89
- font-size: 18px;
90
- resize: vertical;
91
- box-sizing: border-box;
92
- font-family: inherit;
93
- }
94
- button {
95
- padding: 12px 24px;
96
- background-color: #1a8917;
97
- color: white;
98
- border: none;
99
- border-radius: 4px;
100
- cursor: pointer;
101
- font-size: 18px;
102
- transition: background-color 0.3s;
103
- }
104
- button:hover {
105
- background-color: #0f6b0f;
106
- }
107
- /* Output container */
108
- #output-container {
109
- display: flex;
110
- flex-direction: column;
111
- gap: 40px;
112
- }
113
- #report-container, #sources-container {
114
- background-color: #fff;
115
- padding: 32px;
116
- border-radius: 5px;
117
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
118
- overflow-wrap: break-word;
119
- word-wrap: break-word;
120
- word-break: break-word;
121
- }
122
- /* Sources */
123
- .source-item {
124
- margin-bottom: 32px;
125
- padding: 24px;
126
- background-color: #f9f9f9;
127
- border: 1px solid #e0e0e0;
128
- border-radius: 5px;
129
- position: relative;
130
- cursor: pointer;
131
- transition: box-shadow 0.3s;
132
- }
133
- .source-item:hover {
134
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
135
- }
136
- .source-url {
137
- color: #1a8917;
138
- text-decoration: none;
139
- word-break: break-all;
140
- font-weight: bold;
141
- display: block;
142
- margin-bottom: 16px;
143
- font-size: 18px;
144
- }
145
- .source-content {
146
- margin-top: 16px;
147
- position: relative;
148
- overflow: hidden;
149
- }
150
- .source-snippet {
151
- max-height: 150px;
152
- overflow: hidden;
153
- }
154
- .source-full {
155
- display: none;
156
- }
157
- .expand-indicator {
158
- position: absolute;
159
- bottom: 0;
160
- left: 0;
161
- right: 0;
162
- height: 40px;
163
- background: linear-gradient(to bottom, rgba(249,249,249,0), rgba(249,249,249,1));
164
- display: flex;
165
- align-items: center;
166
- justify-content: center;
167
- }
168
- .expand-indicator::after {
169
- content: '▼';
170
- font-size: 14px;
171
- color: #666;
172
- }
173
- .expanded .expand-indicator::after {
174
- content: '▲';
175
- }
176
- /* Responsive adjustments */
177
- @media (max-width: 768px) {
178
- .container {
179
- padding: 0 16px;
180
- }
181
- h1 {
182
- font-size: 32px;
183
- }
184
- h2 {
185
- font-size: 24px;
186
- }
187
- #input-container, #report-container, #sources-container {
188
- padding: 24px;
189
- }
190
- }
191
- </style>
192
- <link rel="stylesheet" href="/css/ai-sidebar.css">
193
- </head>
194
- <body>
195
- <div id="app" class="ai-sidebar__container">
196
- <sidebar-component></sidebar-component>
197
- <main class="ai-sidebar__content">
198
- <div class="container">
199
- <div id="input-container">
200
- <h1>Blog Post Generator</h1>
201
- <textarea id="description" rows="4" placeholder="Enter description">write a medium article on nvidia stock performance</textarea>
202
- <button onclick="generateReport()">Generate Report</button>
203
- </div>
204
- <div id="output-container">
205
- <div id="report-container"></div>
206
- <div id="sources-container"></div>
207
- </div>
208
- </div>
209
- </main>
210
- </div>
211
- <script src="/js/auth.js"></script>
212
- <script src="/js/sidebar-component.js"></script>
213
- <script src="/js/main-app.js"></script>
214
- <script>
215
- async function generateReport() {
216
- const description = document.getElementById('description').value;
217
- const reportContainer = document.getElementById('report-container');
218
- const sourcesContainer = document.getElementById('sources-container');
219
- reportContainer.innerHTML = 'Generating report...';
220
- sourcesContainer.innerHTML = '';
221
- try {
222
- const response = await fetch('https://pvanand-search-generate-prod.hf.space/generate_report', {
223
- method: 'POST',
224
- headers: {
225
- 'Content-Type': 'application/json',
226
- 'Accept': 'text/plain'
227
- },
228
- body: JSON.stringify({
229
- description: description,
230
- user_id: "",
231
- user_name: "multi-agent-research",
232
- internet: true,
233
- output_format: "report_table",
234
- data_format: "Structured data",
235
- generate_charts: true,
236
- output_as_md: true
237
- })
238
- });
239
- if (!response.ok) {
240
- throw new Error(`HTTP error! status: ${response.status}`);
241
- }
242
- const reader = response.body.getReader();
243
- const decoder = new TextDecoder();
244
- let markdown = '';
245
- let metadata = '';
246
- let isReadingMetadata = false;
247
- while (true) {
248
- const { value, done } = await reader.read();
249
- if (done) break;
250
-
251
- const chunk = decoder.decode(value, { stream: true });
252
-
253
- if (chunk.includes('<report-metadata>')) {
254
- isReadingMetadata = true;
255
- metadata = '';
256
- }
257
-
258
- if (isReadingMetadata) {
259
- metadata += chunk;
260
- if (chunk.includes('</report-metadata>')) {
261
- isReadingMetadata = false;
262
- processMetadata(metadata);
263
- }
264
- } else {
265
- markdown += chunk;
266
- renderMarkdown(markdown);
267
- }
268
- }
269
- } catch (error) {
270
- reportContainer.innerHTML = `Error generating report: ${error.message}`;
271
- }
272
- }
273
- function renderMarkdown(markdown) {
274
- const reportContainer = document.getElementById('report-container');
275
- const reportContent = markdown.match(/<report>([\s\S]*)<\/report>/);
276
-
277
- if (reportContent) {
278
- reportContainer.innerHTML = marked.parse(reportContent[1]);
279
- } else {
280
- reportContainer.innerHTML = marked.parse(markdown);
281
- }
282
-
283
- const scripts = reportContainer.getElementsByTagName('script');
284
- Array.from(scripts).forEach(script => {
285
- const newScript = document.createElement('script');
286
- newScript.textContent = script.textContent;
287
- script.parentNode.replaceChild(newScript, script);
288
- });
289
- // Make Plotly charts responsive
290
- const plots = reportContainer.querySelectorAll('.js-plotly-plot');
291
- plots.forEach(plot => {
292
- Plotly.Plots.resize(plot);
293
- });
294
- }
295
- function processMetadata(metadata) {
296
- const sourcesContainer = document.getElementById('sources-container');
297
- const metadataMatch = metadata.match(/all-text-with-urls: (.+)/);
298
-
299
- if (metadataMatch) {
300
- const metadataObj = JSON.parse(metadataMatch[1]);
301
-
302
- sourcesContainer.innerHTML = '<h2>Sources</h2>';
303
- metadataObj.forEach(([content, url]) => {
304
- if (content.trim() !== "") {
305
- const sourceItem = document.createElement('div');
306
- sourceItem.className = 'source-item';
307
- const snippet = content.length > 400 ? content.substring(0, 400) + '...' : content;
308
- sourceItem.innerHTML = `
309
- <a href="${url}" target="_blank" class="source-url">${url}</a>
310
- <div class="source-content">
311
- <div class="source-snippet">${marked.parse(snippet)}</div>
312
- <div class="source-full">${marked.parse(content)}</div>
313
- <div class="expand-indicator"></div>
314
- </div>
315
- `;
316
- sourcesContainer.appendChild(sourceItem);
317
-
318
- const sourceUrl = sourceItem.querySelector('.source-url');
319
- const sourceContent = sourceItem.querySelector('.source-content');
320
- const snippetDiv = sourceItem.querySelector('.source-snippet');
321
- const fullDiv = sourceItem.querySelector('.source-full');
322
-
323
- sourceContent.addEventListener('click', function(e) {
324
- if (!sourceItem.classList.contains('expanded')) {
325
- sourceItem.classList.add('expanded');
326
- snippetDiv.style.display = 'none';
327
- fullDiv.style.display = 'block';
328
- } else if (e.clientY > sourceContent.getBoundingClientRect().bottom - 30) {
329
- sourceItem.classList.remove('expanded');
330
- snippetDiv.style.display = 'block';
331
- fullDiv.style.display = 'none';
332
- }
333
- });
334
-
335
- sourceUrl.addEventListener('click', function(e) {
336
- e.stopPropagation();
337
- });
338
- }
339
- });
340
- } else {
341
- sourcesContainer.innerHTML = '<h2>Sources</h2><p>No source information available.</p>';
342
- }
343
- }
344
- // Make Plotly charts responsive on window resize
345
- window.addEventListener('resize', function() {
346
- const plots = document.querySelectorAll('.js-plotly-plot');
347
- plots.forEach(plot => {
348
- Plotly.Plots.resize(plot);
349
- });
350
- });
351
- </script>
352
- </body>
353
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Report Generator</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
8
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
10
+ <script src="https://cdn.jsdelivr.net/npm/appwrite@15.0.0"></script>
11
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
12
+ <style>
13
+ /* Main styles */
14
+ /* body {
15
+ margin: 0;
16
+ padding: 0;
17
+ text-rendering: optimizeLegibility;
18
+ -webkit-font-smoothing: antialiased;
19
+ color: rgba(0,0,0,0.8);
20
+ position: relative;
21
+ min-height: 100vh;
22
+ font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif;
23
+ font-size: 20px;
24
+ line-height: 1.58;
25
+ color: rgba(0, 0, 0, 0.8);
26
+ background-color: #fff;
27
+ } */
28
+ .container {
29
+ text-rendering: optimizeLegibility;
30
+ -webkit-font-smoothing: antialiased;
31
+ color: rgba(0,0,0,0.8);
32
+ position: relative;
33
+ min-height: 100vh;
34
+ font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif;
35
+ font-size: 20px;
36
+ line-height: 1.58;
37
+ color: rgba(0, 0, 0, 0.8);
38
+ background-color: #fff;
39
+ max-width: 800px;
40
+ margin: 0 auto;
41
+ padding: 0 20px;
42
+ }
43
+ /* Typography */
44
+ h1, h2, h3, h4, h5, h6 {
45
+ font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif;
46
+ font-weight: 700;
47
+ color: rgba(0, 0, 0, 0.84);
48
+ letter-spacing: -0.022em;
49
+ line-height: 1.2;
50
+ }
51
+ h1 {
52
+ font-size: 40px;
53
+ margin-bottom: 0.5em;
54
+ }
55
+ h2 {
56
+ font-size: 32px;
57
+ margin-top: 1.5em;
58
+ margin-bottom: 0.5em;
59
+ }
60
+ h3 {
61
+ font-size: 26px;
62
+ margin-top: 1.5em;
63
+ margin-bottom: 0.5em;
64
+ }
65
+ p {
66
+ margin-bottom: 32px;
67
+ }
68
+ /* Links */
69
+ a {
70
+ color: #1a8917;
71
+ text-decoration: none;
72
+ }
73
+ a:hover {
74
+ text-decoration: underline;
75
+ }
76
+ /* Input container */
77
+ #input-container {
78
+ margin-bottom: 40px;
79
+ background-color: #f9f9f9;
80
+ padding: 32px;
81
+ border-radius: 5px;
82
+ }
83
+ textarea {
84
+ width: 100%;
85
+ padding: 12px;
86
+ margin-bottom: 20px;
87
+ border: 1px solid rgba(0, 0, 0, 0.15);
88
+ border-radius: 4px;
89
+ font-size: 18px;
90
+ resize: vertical;
91
+ box-sizing: border-box;
92
+ font-family: inherit;
93
+ }
94
+ button {
95
+ padding: 12px 24px;
96
+ background-color: #1a8917;
97
+ color: white;
98
+ border: none;
99
+ border-radius: 4px;
100
+ cursor: pointer;
101
+ font-size: 18px;
102
+ transition: background-color 0.3s;
103
+ }
104
+ button:hover {
105
+ background-color: #0f6b0f;
106
+ }
107
+ /* Output container */
108
+ #output-container {
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: 40px;
112
+ }
113
+ #report-container, #sources-container {
114
+ background-color: #fff;
115
+ padding: 32px;
116
+ border-radius: 5px;
117
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
118
+ overflow-wrap: break-word;
119
+ word-wrap: break-word;
120
+ word-break: break-word;
121
+ }
122
+ /* Sources */
123
+ .source-item {
124
+ margin-bottom: 32px;
125
+ padding: 24px;
126
+ background-color: #f9f9f9;
127
+ border: 1px solid #e0e0e0;
128
+ border-radius: 5px;
129
+ position: relative;
130
+ cursor: pointer;
131
+ transition: box-shadow 0.3s;
132
+ }
133
+ .source-item:hover {
134
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
135
+ }
136
+ .source-url {
137
+ color: #1a8917;
138
+ text-decoration: none;
139
+ word-break: break-all;
140
+ font-weight: bold;
141
+ display: block;
142
+ margin-bottom: 16px;
143
+ font-size: 18px;
144
+ }
145
+ .source-content {
146
+ margin-top: 16px;
147
+ position: relative;
148
+ overflow: hidden;
149
+ }
150
+ .source-snippet {
151
+ max-height: 150px;
152
+ overflow: hidden;
153
+ }
154
+ .source-full {
155
+ display: none;
156
+ }
157
+ .expand-indicator {
158
+ position: absolute;
159
+ bottom: 0;
160
+ left: 0;
161
+ right: 0;
162
+ height: 40px;
163
+ background: linear-gradient(to bottom, rgba(249,249,249,0), rgba(249,249,249,1));
164
+ display: flex;
165
+ align-items: center;
166
+ justify-content: center;
167
+ }
168
+ .expand-indicator::after {
169
+ content: '▼';
170
+ font-size: 14px;
171
+ color: #666;
172
+ }
173
+ .expanded .expand-indicator::after {
174
+ content: '▲';
175
+ }
176
+ /* Responsive adjustments */
177
+ @media (max-width: 768px) {
178
+ .container {
179
+ padding: 0 16px;
180
+ }
181
+ h1 {
182
+ font-size: 32px;
183
+ }
184
+ h2 {
185
+ font-size: 24px;
186
+ }
187
+ #input-container, #report-container, #sources-container {
188
+ padding: 24px;
189
+ }
190
+ }
191
+ </style>
192
+ <link rel="stylesheet" href="/css/ai-sidebar.css">
193
+ </head>
194
+ <body>
195
+ <div id="app" class="ai-sidebar__container">
196
+ <sidebar-component></sidebar-component>
197
+ <main class="ai-sidebar__content">
198
+ <div class="container">
199
+ <div id="input-container">
200
+ <h1>Blog Post Generator</h1>
201
+ <textarea id="description" rows="4" placeholder="Enter description">write a medium article on nvidia stock performance</textarea>
202
+ <button onclick="generateReport()">Generate Report</button>
203
+ </div>
204
+ <div id="output-container">
205
+ <div id="report-container"></div>
206
+ <div id="sources-container"></div>
207
+ </div>
208
+ </div>
209
+ </main>
210
+ </div>
211
+ <script src="/js/auth.js"></script>
212
+ <script src="/js/sidebar-component.js"></script>
213
+ <script src="/js/main-app.js"></script>
214
+ <script>
215
+ async function generateReport() {
216
+ const description = document.getElementById('description').value;
217
+ const reportContainer = document.getElementById('report-container');
218
+ const sourcesContainer = document.getElementById('sources-container');
219
+ reportContainer.innerHTML = ' Searching results...';
220
+ sourcesContainer.innerHTML = '';
221
+ try {
222
+ const response = await fetch('https://pvanand-search-generate-prod.hf.space/generate_report', {
223
+ method: 'POST',
224
+ headers: {
225
+ 'Content-Type': 'application/json',
226
+ 'Accept': 'text/plain'
227
+ },
228
+ body: JSON.stringify({
229
+ description: description,
230
+ user_id: "",
231
+ user_name: "multi-agent-research",
232
+ internet: true,
233
+ output_format: "report_table",
234
+ data_format: "Structured data",
235
+ generate_charts: true,
236
+ output_as_md: true
237
+ })
238
+ });
239
+ if (!response.ok) {
240
+ throw new Error(`HTTP error! status: ${response.status}`);
241
+ }
242
+ const reader = response.body.getReader();
243
+ const decoder = new TextDecoder();
244
+ let markdown = '';
245
+ let metadata = '';
246
+ let isReadingMetadata = false;
247
+ while (true) {
248
+ const { value, done } = await reader.read();
249
+ if (done) break;
250
+
251
+ const chunk = decoder.decode(value, { stream: true });
252
+
253
+ if (chunk.includes('<report-metadata>')) {
254
+ isReadingMetadata = true;
255
+ metadata = '';
256
+ }
257
+
258
+ if (isReadingMetadata) {
259
+ metadata += chunk;
260
+ if (chunk.includes('</report-metadata>')) {
261
+ isReadingMetadata = false;
262
+ processMetadata(metadata);
263
+ }
264
+ } else {
265
+ markdown += chunk;
266
+ renderMarkdown(markdown);
267
+ }
268
+ }
269
+ } catch (error) {
270
+ reportContainer.innerHTML = `Error generating report: ${error.message}`;
271
+ }
272
+ }
273
+ function renderMarkdown(markdown) {
274
+ const reportContainer = document.getElementById('report-container');
275
+ const reportContent = markdown.match(/<report>([\s\S]*)<\/report>/);
276
+
277
+ if (reportContent) {
278
+ reportContainer.innerHTML = marked.parse(reportContent[1]);
279
+ } else {
280
+ reportContainer.innerHTML = marked.parse(markdown);
281
+ }
282
+
283
+ const scripts = reportContainer.getElementsByTagName('script');
284
+ Array.from(scripts).forEach(script => {
285
+ const newScript = document.createElement('script');
286
+ newScript.textContent = script.textContent;
287
+ script.parentNode.replaceChild(newScript, script);
288
+ });
289
+ // Make Plotly charts responsive
290
+ const plots = reportContainer.querySelectorAll('.js-plotly-plot');
291
+ plots.forEach(plot => {
292
+ Plotly.Plots.resize(plot);
293
+ });
294
+ }
295
+ function processMetadata(metadata) {
296
+ const sourcesContainer = document.getElementById('sources-container');
297
+ const metadataMatch = metadata.match(/all-text-with-urls: (.+)/);
298
+
299
+ if (metadataMatch) {
300
+ const metadataObj = JSON.parse(metadataMatch[1]);
301
+
302
+ sourcesContainer.innerHTML = '<h2>Sources</h2>';
303
+ metadataObj.forEach(([content, url]) => {
304
+ if (content.trim() !== "") {
305
+ const sourceItem = document.createElement('div');
306
+ sourceItem.className = 'source-item';
307
+ const snippet = content.length > 400 ? content.substring(0, 400) + '...' : content;
308
+ sourceItem.innerHTML = `
309
+ <a href="${url}" target="_blank" class="source-url">${url}</a>
310
+ <div class="source-content">
311
+ <div class="source-snippet">${marked.parse(snippet)}</div>
312
+ <div class="source-full">${marked.parse(content)}</div>
313
+ <div class="expand-indicator"></div>
314
+ </div>
315
+ `;
316
+ sourcesContainer.appendChild(sourceItem);
317
+
318
+ const sourceUrl = sourceItem.querySelector('.source-url');
319
+ const sourceContent = sourceItem.querySelector('.source-content');
320
+ const snippetDiv = sourceItem.querySelector('.source-snippet');
321
+ const fullDiv = sourceItem.querySelector('.source-full');
322
+
323
+ sourceContent.addEventListener('click', function(e) {
324
+ if (!sourceItem.classList.contains('expanded')) {
325
+ sourceItem.classList.add('expanded');
326
+ snippetDiv.style.display = 'none';
327
+ fullDiv.style.display = 'block';
328
+ } else if (e.clientY > sourceContent.getBoundingClientRect().bottom - 30) {
329
+ sourceItem.classList.remove('expanded');
330
+ snippetDiv.style.display = 'block';
331
+ fullDiv.style.display = 'none';
332
+ }
333
+ });
334
+
335
+ sourceUrl.addEventListener('click', function(e) {
336
+ e.stopPropagation();
337
+ });
338
+ }
339
+ });
340
+ } else {
341
+ sourcesContainer.innerHTML = '<h2>Sources</h2><p>No source information available.</p>';
342
+ }
343
+ }
344
+ // Make Plotly charts responsive on window resize
345
+ window.addEventListener('resize', function() {
346
+ const plots = document.querySelectorAll('.js-plotly-plot');
347
+ plots.forEach(plot => {
348
+ Plotly.Plots.resize(plot);
349
+ });
350
+ });
351
+ </script>
352
+ </body>
353
  </html>