11"use client" ;
22
33import type React from "react" ;
4- import { useState , useEffect , useRef } from "react" ;
4+ import { useState , useEffect , useRef , useMemo } from "react" ;
55import { useRouter } from "next/navigation" ;
66import Link from "next/link" ;
77import { Send , Link as LinkIcon } from "lucide-react" ;
@@ -118,6 +118,73 @@ export default function AnalyzePage() {
118118 setIsChatLoading ( false ) ;
119119 }
120120 }
121+
122+ const cleanedTextForView = analysisData ?. cleaned_text ?? "" ;
123+
124+ const articleLayout = useMemo ( ( ) => {
125+ if ( ! cleanedTextForView ) {
126+ return {
127+ highlights : [ ] as string [ ] ,
128+ sections : [ ] as { title : string | null ; paragraphs : string [ ] } [ ] ,
129+ } ;
130+ }
131+
132+ const blocks = cleanedTextForView
133+ . split ( / \n { 2 , } / )
134+ . map ( ( block : string ) => block . trim ( ) )
135+ . filter ( Boolean ) ;
136+
137+ const isTitleLike = ( block : string ) => {
138+ const wordCount = block . split ( / \s + / ) . length ;
139+ return (
140+ block . length <= 90 &&
141+ wordCount <= 12 &&
142+ ! / [ . ] $ / . test ( block ) &&
143+ ! / ^ \d + [ . ) ] / . test ( block )
144+ ) ;
145+ } ;
146+
147+ const highlights : string [ ] = [ ] ;
148+ const sections : { title : string | null ; paragraphs : string [ ] } [ ] = [ ] ;
149+
150+ for ( let i = 0 ; i < blocks . length ; i += 1 ) {
151+ const block = blocks [ i ] ;
152+ const nextBlock = blocks [ i + 1 ] ;
153+
154+ if ( isTitleLike ( block ) ) {
155+ if ( nextBlock && ! isTitleLike ( nextBlock ) ) {
156+ sections . push ( { title : block , paragraphs : [ nextBlock ] } ) ;
157+ i += 1 ;
158+ } else {
159+ highlights . push ( block ) ;
160+ }
161+ continue ;
162+ }
163+
164+ const lastSection = sections [ sections . length - 1 ] ;
165+ if ( lastSection && lastSection . paragraphs . length < 2 ) {
166+ lastSection . paragraphs . push ( block ) ;
167+ } else {
168+ sections . push ( { title : null , paragraphs : [ block ] } ) ;
169+ }
170+ }
171+
172+ return { highlights, sections } ;
173+ } , [ cleanedTextForView ] ) ;
174+
175+ const articleStats = useMemo ( ( ) => {
176+ const words = cleanedTextForView . trim ( )
177+ ? cleanedTextForView . trim ( ) . split ( / \s + / ) . length
178+ : 0 ;
179+ const readTimeMin = words > 0 ? Math . max ( 1 , Math . round ( words / 220 ) ) : 0 ;
180+
181+ return {
182+ words,
183+ readTimeMin,
184+ sectionCount : articleLayout . sections . length ,
185+ } ;
186+ } , [ cleanedTextForView , articleLayout . sections . length ] ) ;
187+
121188 if ( isLoading ) {
122189 return (
123190 < div className = "flex items-center justify-center h-screen" >
@@ -128,11 +195,9 @@ export default function AnalyzePage() {
128195
129196
130197 const {
131- cleaned_text,
132198 facts = [ ] ,
133199 sentiment,
134200 perspective,
135- score,
136201 } = analysisData ;
137202
138203
@@ -173,12 +238,70 @@ export default function AnalyzePage() {
173238 </ TabsList >
174239
175240 < TabsContent value = "summary" >
176- < div className = "prose max-w-none" >
177- { cleaned_text
178- . split ( "\n\n" )
179- . map ( ( para : string , idx : number ) => (
180- < p key = { idx } > { para } </ p >
181- ) ) }
241+ < div className = "relative overflow-hidden rounded-2xl border bg-gradient-to-b from-card via-card to-card/70 p-5 sm:p-7" >
242+ < div className = "pointer-events-none absolute inset-x-0 top-0 h-20 bg-gradient-to-b from-primary/10 to-transparent" />
243+
244+ < div className = "relative mb-6 flex flex-wrap gap-2" >
245+ < Badge variant = "outline" > { articleStats . sectionCount } sections</ Badge >
246+ < Badge variant = "outline" > { articleStats . words } words</ Badge >
247+ < Badge variant = "outline" > ~{ articleStats . readTimeMin } min read</ Badge >
248+ </ div >
249+
250+ { articleLayout . highlights . length > 0 && (
251+ < section className = "relative mb-5 rounded-xl border border-border/70 bg-background/40 p-4 sm:p-5" >
252+ < p className = "mb-3 text-xs font-semibold uppercase tracking-[0.12em] text-primary/90" >
253+ Article Highlights
254+ </ p >
255+ < div className = "grid grid-cols-1 gap-2 sm:grid-cols-2" >
256+ { articleLayout . highlights . map ( ( headline : string , idx : number ) => (
257+ < div
258+ key = { `${ headline } -${ idx } ` }
259+ className = "rounded-lg border border-border/60 bg-card/60 px-3 py-2 text-sm font-medium text-foreground/90"
260+ >
261+ { headline }
262+ </ div >
263+ ) ) }
264+ </ div >
265+ </ section >
266+ ) }
267+
268+ < div className = "relative space-y-4 sm:space-y-5" >
269+ { articleLayout . sections . map (
270+ (
271+ section : { title : string | null ; paragraphs : string [ ] } ,
272+ idx : number
273+ ) => (
274+ < article
275+ key = { idx }
276+ className = "rounded-xl border border-border/70 bg-background/40 p-4 sm:p-5"
277+ >
278+ < div className = "mb-3 flex items-center gap-3" >
279+ < span className = "inline-flex h-7 min-w-7 items-center justify-center rounded-full bg-primary/15 px-2 text-xs font-semibold text-primary" >
280+ { ( idx + 1 ) . toString ( ) . padStart ( 2 , "0" ) }
281+ </ span >
282+ < h3 className = "text-lg font-semibold leading-snug text-foreground" >
283+ { section . title ?? `Key Point ${ idx + 1 } ` }
284+ </ h3 >
285+ </ div >
286+
287+ < div className = "space-y-3" >
288+ { section . paragraphs . map ( ( paragraph : string , pIdx : number ) => (
289+ < p
290+ key = { pIdx }
291+ className = { `text-base leading-8 text-foreground/90 ${
292+ idx === 0 && pIdx === 0
293+ ? "first-letter:float-left first-letter:mr-2 first-letter:text-3xl first-letter:font-semibold first-letter:leading-none"
294+ : ""
295+ } `}
296+ >
297+ { paragraph }
298+ </ p >
299+ ) ) }
300+ </ div >
301+ </ article >
302+ )
303+ ) }
304+ </ div >
182305 </ div >
183306 </ TabsContent >
184307
0 commit comments