Skip to content

Commit b48d433

Browse files
committed
Improve Article tab layout and fix hook order bug
1 parent 06df29c commit b48d433

1 file changed

Lines changed: 132 additions & 9 deletions

File tree

frontend/app/analyze/results/page.tsx

Lines changed: 132 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import type React from "react";
4-
import { useState, useEffect, useRef } from "react";
4+
import { useState, useEffect, useRef, useMemo } from "react";
55
import { useRouter } from "next/navigation";
66
import Link from "next/link";
77
import { 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

Comments
 (0)