Skip to content

Commit 60faef0

Browse files
committed
Refactor researcher module and add content enhancement feature
Removed tag-based article listing and introduced a server-side content enhancement feature. Enhanced the search functionality with additional filters and sorting options. Added a new UI component for content enhancement with version history and copy features. Took 19 seconds
1 parent ed19504 commit 60faef0

File tree

5 files changed

+436
-64
lines changed

5 files changed

+436
-64
lines changed

app/researcher/articles/tags/[tagSlug]/page.tsx

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
'use client'
2+
3+
import { useState, useEffect } from 'react'
4+
import { enhanceContent } from '../enhanceActions'
5+
import { Button } from '@/components/ui/button'
6+
import { Textarea } from '@/components/ui/textarea'
7+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
8+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
9+
import { Loader2, Copy, Sun, Moon, History, ArrowLeft, Trash2 } from 'lucide-react'
10+
import { useTheme } from 'next-themes'
11+
import { CustomReactMarkdown } from '@/components/CustomReactMarkdown'
12+
import {
13+
DropdownMenu,
14+
DropdownMenuContent,
15+
DropdownMenuItem,
16+
DropdownMenuTrigger,
17+
} from "@/components/ui/dropdown-menu"
18+
import { formatDistanceToNow } from 'date-fns'
19+
20+
interface ContentVersion {
21+
content: string;
22+
timestamp: number;
23+
isEnhanced: boolean;
24+
}
25+
26+
export default function ContentEnhancer() {
27+
const [input, setInput] = useState('')
28+
const [enhancedContent, setEnhancedContent] = useState('')
29+
const [isEnhancing, setIsEnhancing] = useState(false)
30+
const [showHistory, setShowHistory] = useState(false)
31+
const [versions, setVersions] = useState<ContentVersion[]>([])
32+
const { theme, setTheme } = useTheme()
33+
34+
// Load versions from localStorage on component mount
35+
useEffect(() => {
36+
const savedVersions = localStorage.getItem('contentVersions')
37+
if (savedVersions) {
38+
setVersions(JSON.parse(savedVersions))
39+
}
40+
}, [])
41+
42+
const saveVersion = (content: string, isEnhanced: boolean = false) => {
43+
const newVersion: ContentVersion = {
44+
content,
45+
timestamp: Date.now(),
46+
isEnhanced
47+
}
48+
const updatedVersions = [...versions, newVersion]
49+
setVersions(updatedVersions)
50+
localStorage.setItem('contentVersions', JSON.stringify(updatedVersions))
51+
}
52+
53+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
54+
e.preventDefault()
55+
setIsEnhancing(true)
56+
const formData = new FormData(e.currentTarget)
57+
const content = formData.get('content') as string
58+
59+
try {
60+
// Always save the current version before enhancement
61+
if (content.trim()) {
62+
// Check if this exact content isn't already the latest version
63+
const latestVersion = versions[versions.length - 1]
64+
if (!latestVersion || latestVersion.content !== content) {
65+
saveVersion(content, false)
66+
}
67+
}
68+
69+
const response = await enhanceContent(content)
70+
71+
// Save enhanced version if it's different from the input
72+
if (response.enhancedContent.trim() && response.enhancedContent !== content) {
73+
saveVersion(response.enhancedContent, true)
74+
}
75+
76+
setInput(response.enhancedContent)
77+
setEnhancedContent(response.enhancedContent)
78+
} catch (error) {
79+
console.error('Enhancement failed:', error)
80+
} finally {
81+
setIsEnhancing(false)
82+
}
83+
}
84+
85+
const deleteVersion = (timestamp: number) => {
86+
const updatedVersions = versions.filter(v => v.timestamp !== timestamp)
87+
setVersions(updatedVersions)
88+
localStorage.setItem('contentVersions', JSON.stringify(updatedVersions))
89+
}
90+
91+
const copyToClipboard = async (text: string, format: 'markdown' | 'rich') => {
92+
if (format === 'markdown') {
93+
await navigator.clipboard.writeText(text)
94+
} else {
95+
// Convert markdown to HTML for rich text copying
96+
const tempDiv = document.createElement('div')
97+
tempDiv.innerHTML = text
98+
99+
try {
100+
await navigator.clipboard.write([
101+
new ClipboardItem({
102+
'text/plain': new Blob([text], { type: 'text/plain' }),
103+
'text/html': new Blob([tempDiv.innerHTML], { type: 'text/html' })
104+
})
105+
])
106+
} catch (err) {
107+
// Fallback to plain text if rich copy fails
108+
await navigator.clipboard.writeText(text)
109+
}
110+
}
111+
}
112+
113+
const restoreVersion = (version: ContentVersion) => {
114+
setInput(version.content)
115+
setShowHistory(false)
116+
}
117+
118+
const formatDate = (timestamp: number) => {
119+
const date = new Date(timestamp)
120+
const timeAgo = formatDistanceToNow(date, { addSuffix: true })
121+
return {
122+
timeAgo,
123+
fullDate: date.toLocaleString()
124+
}
125+
}
126+
127+
if (showHistory) {
128+
return (
129+
<div className="container mx-auto p-4 max-w-4xl">
130+
<Card>
131+
<CardHeader>
132+
<CardTitle className="flex justify-between items-center">
133+
Version History
134+
<Button variant="ghost" size="icon" onClick={() => setShowHistory(false)}>
135+
<ArrowLeft className="h-6 w-6" />
136+
</Button>
137+
</CardTitle>
138+
</CardHeader>
139+
<CardContent>
140+
<div className="space-y-4">
141+
{[...versions]
142+
.sort((a, b) => b.timestamp - a.timestamp) // Sort by newest first
143+
.map((version) => {
144+
const { timeAgo, fullDate } = formatDate(version.timestamp)
145+
return (
146+
<Card key={version.timestamp} className="p-4">
147+
<div className="flex justify-between items-center mb-2">
148+
<div className="flex items-center gap-2">
149+
<span
150+
className="text-sm text-muted-foreground"
151+
title={fullDate} // Show full date on hover
152+
>
153+
{timeAgo}
154+
</span>
155+
{version.isEnhanced && (
156+
<span className="text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100 px-2 py-0.5 rounded">
157+
Enhanced
158+
</span>
159+
)}
160+
</div>
161+
<div className="flex gap-2">
162+
<Button
163+
variant="outline"
164+
size="sm"
165+
onClick={() => restoreVersion(version)}
166+
>
167+
Restore
168+
</Button>
169+
<Button
170+
variant="outline"
171+
size="icon"
172+
className="h-8 w-8"
173+
onClick={() => deleteVersion(version.timestamp)}
174+
>
175+
<Trash2 className="h-4 w-4 text-red-500" />
176+
</Button>
177+
</div>
178+
</div>
179+
<div className="max-h-40 overflow-y-auto">
180+
<pre className="text-sm whitespace-pre-wrap">{version.content.substring(0, 200)}...</pre>
181+
</div>
182+
</Card>
183+
)
184+
})}
185+
</div>
186+
</CardContent>
187+
</Card>
188+
</div>
189+
)
190+
}
191+
192+
return (
193+
<div className="container mx-auto p-4 max-w-4xl">
194+
<Card className="mb-4">
195+
<CardHeader>
196+
<CardTitle className="flex justify-between items-center">
197+
Content Enhancer
198+
<div className="flex gap-2">
199+
<Button
200+
variant="ghost"
201+
size="icon"
202+
onClick={() => setShowHistory(true)}
203+
disabled={versions.length === 0}
204+
>
205+
<History className="h-6 w-6" />
206+
</Button>
207+
<Button
208+
variant="ghost"
209+
size="icon"
210+
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
211+
>
212+
{theme === 'dark' ? <Sun className="h-6 w-6" /> : <Moon className="h-6 w-6" />}
213+
</Button>
214+
</div>
215+
</CardTitle>
216+
</CardHeader>
217+
<CardContent>
218+
<form onSubmit={handleSubmit}>
219+
<Textarea
220+
name="content"
221+
placeholder="Paste your content here..."
222+
value={input}
223+
onChange={(e) => setInput(e.target.value)}
224+
className="min-h-[200px] mb-4"
225+
/>
226+
<div className="flex justify-between items-center mb-4">
227+
<Button type="submit" disabled={isEnhancing || input.trim() === ''}>
228+
{isEnhancing ? (
229+
<>
230+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
231+
Enhancing...
232+
</>
233+
) : (
234+
'Enhance Content'
235+
)}
236+
</Button>
237+
</div>
238+
</form>
239+
</CardContent>
240+
</Card>
241+
242+
{enhancedContent && (
243+
<Card>
244+
<CardHeader>
245+
<CardTitle className="flex justify-between items-center">
246+
Enhanced Content
247+
<DropdownMenu>
248+
<DropdownMenuTrigger asChild>
249+
<Button variant="outline" size="icon">
250+
<Copy className="h-4 w-4" />
251+
</Button>
252+
</DropdownMenuTrigger>
253+
<DropdownMenuContent align="end">
254+
<DropdownMenuItem
255+
onClick={() => copyToClipboard(enhancedContent, 'markdown')}
256+
>
257+
Copy as Markdown
258+
</DropdownMenuItem>
259+
<DropdownMenuItem
260+
onClick={() => copyToClipboard(enhancedContent, 'rich')}
261+
>
262+
Copy as Rich Text
263+
</DropdownMenuItem>
264+
</DropdownMenuContent>
265+
</DropdownMenu>
266+
</CardTitle>
267+
</CardHeader>
268+
<CardContent>
269+
<Tabs defaultValue="preview">
270+
<TabsList className="mb-4">
271+
<TabsTrigger value="preview">Preview</TabsTrigger>
272+
<TabsTrigger value="markdown">Markdown</TabsTrigger>
273+
</TabsList>
274+
<TabsContent value="preview">
275+
<div className="relative rounded-md border bg-background p-4">
276+
<CustomReactMarkdown>{enhancedContent}</CustomReactMarkdown>
277+
</div>
278+
</TabsContent>
279+
<TabsContent value="markdown">
280+
<div className="relative">
281+
<pre className="p-4 bg-muted rounded-md overflow-x-auto">
282+
<code>{enhancedContent}</code>
283+
</pre>
284+
</div>
285+
</TabsContent>
286+
</Tabs>
287+
</CardContent>
288+
</Card>
289+
)}
290+
</div>
291+
)
292+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use server'
2+
3+
import { MarkdownEnhancer, MarkdownEnhancementResult } from '@/lib/content/markdownEnhancer'
4+
5+
interface EnhanceResponse {
6+
enhancedContent: string;
7+
}
8+
9+
export async function enhanceContent(content: string): Promise<EnhanceResponse> {
10+
const enhancer = new MarkdownEnhancer(process.env.TAVILY_API_KEY || '')
11+
12+
13+
try {
14+
const result = await enhancer.enhance(content)
15+
// Map the result.content to enhancedContent for the frontend
16+
return {
17+
enhancedContent: result.content
18+
}
19+
} catch (error) {
20+
console.error('Enhancement error:', error)
21+
throw new Error('Failed to enhance content')
22+
}
23+
}

app/researcher/enhance/page.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import EnhanceForm from './components/EnhanceForm';
2+
3+
export default function ContentEnhancerPage() {
4+
return (
5+
<div className="max-w-4xl mx-auto">
6+
<EnhanceForm />
7+
</div>
8+
);
9+
}

0 commit comments

Comments
 (0)