Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,35 @@ const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = {
title: "Perspective - AI-Powered Bias Detection",
description: "Combat bias and one-sided narratives with AI-generated alternative perspectives.",
description:
"Combat bias and one-sided narratives with AI-generated alternative perspectives. Perspective analyzes text and provides balanced viewpoints using AI.",
keywords: [
"AI bias detection",
"AI perspective analysis",
"bias detection tool",
"AI content evaluation",
"alternative narrative generation",
],
openGraph: {
title: "Perspective - AI-Powered Bias Detection",
description:
"Analyze content for bias and generate alternative AI-driven perspectives.",
url: "https://perspective-aossie.vercel.app/",
siteName: "Perspective",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Perspective - AI-Powered Bias Detection",
description:
"AI-powered tool to analyze bias and generate alternative perspectives.",
},
Comment on lines +28 to +33
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

card: "summary_large_image" with no image URL will not render a large image card.

Twitter/X (and most Open Graph consumers) require an explicit image URL to display the summary_large_image card format. Without it, the card silently falls back to a plain text summary, defeating the purpose. The same applies to the openGraph block (lines 20–27) which also omits images.

♻️ Add image fields to both OG and Twitter metadata
   openGraph: {
     title: "Perspective - AI-Powered Bias Detection",
     description: "Analyze content for bias and generate alternative AI-driven perspectives.",
     url: "https://perspective-aossie.vercel.app/",
     siteName: "Perspective",
     type: "website",
+    images: [
+      {
+        url: "https://perspective-aossie.vercel.app/og-image.png",
+        width: 1200,
+        height: 630,
+        alt: "Perspective - AI-Powered Bias Detection",
+      },
+    ],
   },
   twitter: {
     card: "summary_large_image",
     title: "Perspective - AI-Powered Bias Detection",
     description: "AI-powered tool to analyze bias and generate alternative perspectives.",
+    images: ["https://perspective-aossie.vercel.app/og-image.png"],
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/layout.tsx` around lines 28 - 33, The twitter metadata block
(twitter) and the openGraph metadata block (openGraph) are missing image URLs so
the card type "summary_large_image" won't render a large image; update both the
twitter object and the openGraph object to include an images field (or image
property) with a fully-qualified image URL (and optional alt/type entries)
pointing to your social preview image so the summary_large_image card and OG
previews render correctly (e.g., add twitter.images = [{ url:
"https://.../social-preview.png", alt: "Perspective preview" }] and
openGraph.images = [{ url: "...", alt: "..." }]).

}

/**
* Root layout component that sets up global HTML structure, font, and theming for the application.
*
* Wraps all page content with the Inter font and a theme provider supporting system-based theming.
*
* @param children - The content to be rendered within the layout.
*/
export default function RootLayout({
children,
Expand All @@ -26,10 +46,31 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange={false}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange={false}
>
{/* Structured Data for AI & Search Engines */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "Perspective",
applicationCategory: "AIApplication",
description:
"Perspective is an AI-powered tool that detects bias in text and generates alternative perspectives.",
operatingSystem: "Web",
url: "https://perspective-aossie.vercel.app/",
}),
}}
/>
{children}
</ThemeProvider>
</body>
</html>
)
}
}
91 changes: 91 additions & 0 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import Script from "next/script";
import {
Card,
CardContent,
Expand Down Expand Up @@ -91,6 +92,29 @@ export default function Home() {
{ label: "Fact Accuracy", value: "98%", color: "text-purple-600" },
{ label: "User Satisfaction", value: "4.9/5", color: "text-orange-600" },
];
const faqData = [
{
question: "What is Perspective?",
answer:
"Perspective is an AI-powered research tool that analyzes online articles to uncover bias and generate balanced counter-perspectives using advanced NLP models.",
},
{
question: "How does Perspective detect bias?",
answer:
"Perspective uses natural language processing techniques and structured evaluation pipelines to detect narrative patterns and highlight potential bias in content.",
},
{
question: "Is Perspective free to use?",
answer:
"Yes, Perspective is completely free to use and does not require sign-in.",
},
{
question: "What technology powers Perspective?",
answer:
"Perspective is built using Next.js, FastAPI, LangChain, LangGraph, and advanced NLP models for research-focused analysis.",
},
];


return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50/30 to-indigo-100/50 dark:from-slate-900 dark:via-slate-900/80 dark:to-indigo-950/50 overflow-hidden transition-colors duration-300">
Expand Down Expand Up @@ -274,6 +298,36 @@ export default function Home() {
</div>
</section>

{/* FAQ Section */}
<section className="container mx-auto px-4 py-12 md:py-20">
<div className="max-w-4xl mx-auto">
<h2 className="text-3xl md:text-4xl font-bold mb-8 text-center text-slate-900 dark:text-slate-100">
Frequently Asked Questions
</h2>

<div className="space-y-6">
{faqData.map((faq, index) => (
<Card
key={index}
className="p-6 border-0 shadow-lg bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm"
>
<CardHeader>
<CardTitle className="text-lg text-slate-900 dark:text-slate-100">
{faq.question}
</CardTitle>
</CardHeader>
<CardContent>
<CardDescription className="text-slate-600 dark:text-slate-300">
{faq.answer}
</CardDescription>
</CardContent>
</Card>
))}
</div>
</div>

</section>

{/* Footer */}
<footer className="border-t border-white/20 dark:border-white/10 bg-white/90 dark:bg-slate-900/90 backdrop-blur-xl">
<div className="container mx-auto px-4 py-6 md:py-8">
Expand All @@ -296,6 +350,43 @@ export default function Home() {
</div>
</div>
</footer>

{/* FAQ Structured Data */}
<Script
id="faq-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqData.map((faq) => ({
"@type": "Question",
name: faq.question,
acceptedAnswer: {
"@type": "Answer",
text: faq.answer,
},
})),
}),
}}
/>

<Script
id="software-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "Perspective",
applicationCategory: "ResearchApplication",
operatingSystem: "Web",
description:
"Perspective is an AI-powered research tool that analyzes online articles to detect bias and generate structured counter-perspectives.",
url: "https://perspective-aossie.vercel.app/",
}),
}}
/>
Comment on lines +374 to +389
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Duplicate SoftwareApplication JSON-LD block with a conflicting applicationCategory.

layout.tsx already injects a SoftwareApplication JSON-LD block (applicationCategory: "AIApplication"). This second block uses applicationCategory: "ResearchApplication" and a different description. Two SoftwareApplication schemas on the same page with conflicting properties is an SEO anti-pattern—search engines may ignore both or report a validation error.

Remove the SoftwareApplication Script block from page.tsx entirely and consolidate all non-page-specific structured data in layout.tsx.

🐛 Proposed fix
-      <Script
-        id="software-schema"
-        type="application/ld+json"
-        dangerouslySetInnerHTML={{
-          __html: JSON.stringify({
-            "@context": "https://schema.org",
-            "@type": "SoftwareApplication",
-            name: "Perspective",
-            applicationCategory: "ResearchApplication",
-            operatingSystem: "Web",
-            description:
-              "Perspective is an AI-powered research tool that analyzes online articles to detect bias and generate structured counter-perspectives.",
-            url: "https://perspective-aossie.vercel.app/",
-          }),
-        }}
-      />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Script
id="software-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "Perspective",
applicationCategory: "ResearchApplication",
operatingSystem: "Web",
description:
"Perspective is an AI-powered research tool that analyzes online articles to detect bias and generate structured counter-perspectives.",
url: "https://perspective-aossie.vercel.app/",
}),
}}
/>
🧰 Tools
🪛 ast-grep (0.40.5)

[warning] 376-376: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/page.tsx` around lines 374 - 389, Remove the duplicate
SoftwareApplication JSON-LD Script block in page.tsx (the <Script
id="software-schema" ...> block) so you don't emit two conflicting
SoftwareApplication schemas; delete that Script element entirely and ensure any
non-page-specific fields (name, url, applicationCategory, description,
operatingSystem) remain defined only in layout.tsx's JSON-LD so all canonical
app metadata is consolidated there.

</div>
);
}