Skip to content

Commit e48a16c

Browse files
committed
feat: add download functionality for research findings
- Add DownloadIcon component to icons.tsx - Implement download function in ResultsDisplay component - Create markdown file with research content and sources - Add download button to research results UI - Generate timestamped filename for downloaded files
1 parent ee314bb commit e48a16c

2 files changed

Lines changed: 57 additions & 15 deletions

File tree

components/ResultsDisplay.tsx

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import type { Source } from '../types';
33
import { LoadingSpinner } from './LoadingSpinner';
4-
import { LinkIcon, ResearchDeeperIcon } from './icons';
4+
import { LinkIcon, ResearchDeeperIcon, DownloadIcon } from './icons';
55

66
interface ResultsDisplayProps {
77
text: string;
@@ -16,10 +16,10 @@ const SourceLink: React.FC<{ source: Source }> = ({ source }) => (
1616
href={source.web.uri}
1717
target="_blank"
1818
rel="noopener noreferrer"
19-
className="flex items-center gap-2 text-sm bg-slate-700/50 p-2 rounded-md hover:bg-slate-600/50 transition duration-200"
19+
className="flex items-center gap-2 p-2 text-sm transition duration-200 rounded-md bg-slate-700/50 hover:bg-slate-600/50"
2020
>
2121
<LinkIcon />
22-
<span className="truncate text-teal-300 hover:text-teal-200">{source.web.title || new URL(source.web.uri).hostname}</span>
22+
<span className="text-teal-300 truncate hover:text-teal-200">{source.web.title || new URL(source.web.uri).hostname}</span>
2323
</a>
2424
);
2525

@@ -49,6 +49,32 @@ const ResultItem: React.FC<{ line: string; onResearchDeeper: (query: string) =>
4949

5050

5151
export const ResultsDisplay: React.FC<ResultsDisplayProps> = ({ text, sources, isLoading, error, onResearchDeeper }) => {
52+
const handleDownload = () => {
53+
const timestamp = new Date().toISOString().split('T')[0];
54+
const filename = `aeternum-research-${timestamp}.md`;
55+
56+
let content = `# Aeternum Research Findings\n\n`;
57+
content += `Generated on: ${new Date().toLocaleString()}\n\n`;
58+
content += `## Research Results\n\n${text}\n\n`;
59+
60+
if (sources.length > 0) {
61+
content += `## Sources\n\n`;
62+
sources.forEach((source, index) => {
63+
content += `${index + 1}. [${source.web.title || new URL(source.web.uri).hostname}](${source.web.uri})\n`;
64+
});
65+
}
66+
67+
const blob = new Blob([content], { type: 'text/markdown' });
68+
const url = URL.createObjectURL(blob);
69+
const a = document.createElement('a');
70+
a.href = url;
71+
a.download = filename;
72+
document.body.appendChild(a);
73+
a.click();
74+
document.body.removeChild(a);
75+
URL.revokeObjectURL(url);
76+
};
77+
5278
if (isLoading) {
5379
return (
5480
<div className="mt-6 p-6 bg-slate-800/50 backdrop-blur-md rounded-lg shadow-lg border border-gray-600/50 flex flex-col items-center justify-center min-h-[20rem]">
@@ -61,9 +87,9 @@ export const ResultsDisplay: React.FC<ResultsDisplayProps> = ({ text, sources, i
6187

6288
if (error) {
6389
return (
64-
<div className="mt-6 p-6 bg-red-900/50 rounded-lg border border-red-500/50">
90+
<div className="p-6 mt-6 border rounded-lg bg-red-900/50 border-red-500/50">
6591
<h3 className="text-lg font-bold text-red-300">An Error Occurred</h3>
66-
<p className="text-red-200 mt-2">{error}</p>
92+
<p className="mt-2 text-red-200">{error}</p>
6793
</div>
6894
);
6995
}
@@ -75,22 +101,32 @@ export const ResultsDisplay: React.FC<ResultsDisplayProps> = ({ text, sources, i
75101
</div>
76102
);
77103
}
78-
104+
79105
const contentLines = text.split('\n');
80106

81107
return (
82-
<div className="mt-6 p-6 bg-slate-800/50 backdrop-blur-md rounded-lg shadow-lg border border-gray-600/50">
108+
<div className="p-6 mt-6 border rounded-lg shadow-lg bg-slate-800/50 backdrop-blur-md border-gray-600/50">
109+
<div className="flex items-center justify-between mb-4">
110+
<h2 className="text-2xl font-bold">Research Findings</h2>
111+
<button
112+
onClick={handleDownload}
113+
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white transition duration-200 bg-teal-600 rounded-md hover:bg-teal-500 focus:outline-none focus:ring-2 focus:ring-teal-500"
114+
title="Download research findings as Markdown"
115+
>
116+
<DownloadIcon />
117+
Download
118+
</button>
119+
</div>
83120
<div className="prose prose-invert prose-p:my-0 prose-headings:text-yellow-400 prose-a:text-teal-400 hover:prose-a:text-teal-300 prose-strong:text-gray-100 max-w-none">
84-
<h2 className="text-2xl font-bold mb-4">Research Findings</h2>
85121
{contentLines.map((line, index) => (
86122
<ResultItem key={index} line={line} onResearchDeeper={onResearchDeeper} />
87123
))}
88124
</div>
89125

90126
{sources.length > 0 && (
91-
<div className="mt-8 pt-4 border-t border-gray-600/50">
92-
<h3 className="text-xl font-bold mb-4 text-yellow-400">Sources</h3>
93-
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
127+
<div className="pt-4 mt-8 border-t border-gray-600/50">
128+
<h3 className="mb-4 text-xl font-bold text-yellow-400">Sources</h3>
129+
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
94130
{sources.map((source, index) => (
95131
<SourceLink key={`${source.web.uri}-${index}`} source={source} />
96132
))}

components/icons.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
export const SearchIcon: React.FC = () => (
44
<svg
55
xmlns="http://www.w3.org/2000/svg"
6-
className="h-5 w-5"
6+
className="w-5 h-5"
77
fill="none"
88
viewBox="0 0 24 24"
99
stroke="currentColor"
@@ -20,7 +20,7 @@ export const SearchIcon: React.FC = () => (
2020
export const LinkIcon: React.FC = () => (
2121
<svg
2222
xmlns="http://www.w3.org/2000/svg"
23-
className="h-4 w-4 flex-shrink-0 text-gray-400"
23+
className="flex-shrink-0 w-4 h-4 text-gray-400"
2424
fill="none"
2525
viewBox="0 0 24 24"
2626
stroke="currentColor"
@@ -35,13 +35,19 @@ export const LinkIcon: React.FC = () => (
3535
);
3636

3737
export const ResearchDeeperIcon: React.FC = () => (
38-
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
38+
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
3939
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" />
4040
</svg>
4141
);
4242

4343
export const HistoryIcon: React.FC = () => (
44-
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
44+
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
4545
<path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
4646
</svg>
4747
);
48+
49+
export const DownloadIcon: React.FC = () => (
50+
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
51+
<path strokeLinecap="round" strokeLinejoin="round" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
52+
</svg>
53+
);

0 commit comments

Comments
 (0)