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+ }
0 commit comments