forked from wishonia/wishonia
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
511 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
'use client' | ||
|
||
import { useState } from 'react' | ||
import { writeArticleAction } from '@/app/actions' | ||
import ArticleRenderer from '@/components/ArticleRenderer' | ||
import { Button } from "@/components/ui/button" | ||
import { Input } from "@/components/ui/input" | ||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" | ||
import { ReportOutput } from '@/lib/agents/researcher/researcher' | ||
import GlobalBrainNetwork from "@/components/landingPage/global-brain-network" | ||
|
||
export default function Home() { | ||
const [article, setArticle] = useState<ReportOutput | null>(null) | ||
const [error, setError] = useState('') | ||
const [isGenerating, setIsGenerating] = useState(false) | ||
|
||
async function handleSubmit(formData: FormData) { | ||
const topic = formData.get('topic') as string | ||
if (!topic) { | ||
setError('Please enter a topic') | ||
return | ||
} | ||
|
||
setIsGenerating(true) | ||
setError('') | ||
|
||
try { | ||
const generatedArticle = await writeArticleAction(topic) | ||
setArticle(generatedArticle) | ||
} catch (err) { | ||
setError('Failed to generate article. Please try again.') | ||
} finally { | ||
setIsGenerating(false) | ||
} | ||
} | ||
|
||
return ( | ||
<main className="container mx-auto p-4"> | ||
<h1 className="text-3xl font-bold mb-6">Article Generator</h1> | ||
<Card className="mb-8"> | ||
<CardHeader> | ||
<CardTitle>Generate an Article</CardTitle> | ||
<CardDescription>Enter a topic to generate an article</CardDescription> | ||
</CardHeader> | ||
<CardContent> | ||
<form onSubmit={(e) => { | ||
e.preventDefault() | ||
handleSubmit(new FormData(e.currentTarget)) | ||
}} className="flex gap-4"> | ||
<Input type="text" name="topic" placeholder="Enter article topic" className="flex-grow" /> | ||
<Button type="submit" disabled={isGenerating}> | ||
{isGenerating ? 'Generating...' : 'Generate Article'} | ||
</Button> | ||
</form> | ||
</CardContent> | ||
{error && ( | ||
<CardFooter> | ||
<p className="text-red-500">{error}</p> | ||
</CardFooter> | ||
)} | ||
</Card> | ||
|
||
{isGenerating && <GlobalBrainNetwork />} | ||
{!isGenerating && article && <ArticleRenderer {...article} />} | ||
</main> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import {useState} from 'react' | ||
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card" | ||
import {Badge} from "@/components/ui/badge" | ||
import {Separator} from "@/components/ui/separator" | ||
import {Clock, Tag, Folder, Link2} from 'lucide-react' | ||
import {ReportOutput} from '@/lib/agents/researcher/researcher' | ||
import {CustomReactMarkdown} from "@/components/CustomReactMarkdown"; | ||
|
||
export default function ArticleRenderer(props: ReportOutput) { | ||
const [expandedResult, setExpandedResult] = useState<string | null>(null) | ||
|
||
const { | ||
title, | ||
description, | ||
content, | ||
sources, | ||
tags, | ||
category, | ||
readingTime, | ||
searchResults | ||
} = props | ||
|
||
return ( | ||
<div className="container mx-auto p-4 max-w-6xl"> | ||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> | ||
<Card className="md:col-span-2"> | ||
<CardHeader> | ||
<CardTitle>{title}</CardTitle> | ||
<CardDescription>{description}</CardDescription> | ||
</CardHeader> | ||
<Separator className="my-4 mx-auto w-[90%]" /> | ||
<CardContent> | ||
<CustomReactMarkdown> | ||
{content} | ||
</CustomReactMarkdown> | ||
</CardContent> | ||
</Card> | ||
<div className="space-y-4"> | ||
<Card> | ||
<CardHeader> | ||
<CardTitle>Article Info</CardTitle> | ||
</CardHeader> | ||
<CardContent className="space-y-2"> | ||
<div className="flex items-center space-x-2"> | ||
<Folder className="w-4 h-4"/> | ||
<span>{category}</span> | ||
</div> | ||
<div className="flex items-center space-x-2"> | ||
<Clock className="w-4 h-4"/> | ||
<span>{readingTime} min read</span> | ||
</div> | ||
<div className="flex flex-wrap gap-2"> | ||
<Tag className="w-4 h-4"/> | ||
{tags?.map((tag, index) => ( | ||
<Badge key={index} variant="secondary">{tag}</Badge> | ||
))} | ||
</div> | ||
</CardContent> | ||
</Card> | ||
|
||
<Card> | ||
<CardHeader> | ||
<CardTitle>Sources</CardTitle> | ||
</CardHeader> | ||
<CardContent> | ||
<ul className="space-y-2"> | ||
{sources?.map((source, index) => ( | ||
<li key={index}> | ||
<a href={source.url} target="_blank" rel="noopener noreferrer" | ||
className="flex items-center space-x-2 text-blue-500 hover:underline"> | ||
<Link2 className="w-4 h-4"/> | ||
<span>{source.title}</span> | ||
</a> | ||
</li> | ||
))} | ||
</ul> | ||
</CardContent> | ||
</Card> | ||
</div> | ||
</div> | ||
|
||
<Card className="mt-8"> | ||
<CardHeader> | ||
<CardTitle>Search Results</CardTitle> | ||
</CardHeader> | ||
<CardContent> | ||
{searchResults?.map((result, index) => ( | ||
<div key={index} className="mb-4"> | ||
<h3 className="text-lg font-semibold"> | ||
<a href={result.url} target="_blank" rel="noopener noreferrer" | ||
className="text-blue-500 hover:underline"> | ||
{result.title} | ||
</a> | ||
</h3> | ||
{result.publishedDate && ( | ||
<p className="text-sm text-muted-foreground"> | ||
Published on: {new Date(result.publishedDate).toLocaleDateString()} | ||
</p> | ||
)} | ||
<p className="mt-1"> | ||
{expandedResult === result.id ? result.text : `${result.text.slice(0, 150)}...`} | ||
{result.text.length > 150 && ( | ||
<button | ||
onClick={() => setExpandedResult(expandedResult === result.id ? null : result.id)} | ||
className="ml-2 text-blue-500 hover:underline" | ||
> | ||
{expandedResult === result.id ? 'Show less' : 'Show more'} | ||
</button> | ||
)} | ||
</p> | ||
{index < (searchResults.length - 1) && <Separator className="my-4"/>} | ||
</div> | ||
))} | ||
</CardContent> | ||
</Card> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import React from "react" | ||
import { ReactMarkdown } from "react-markdown/lib/react-markdown" | ||
import rehypeRaw from "rehype-raw" | ||
import remarkGfm from "remark-gfm" | ||
import remarkMath from "remark-math" | ||
import { cn } from "@/lib/utils" | ||
import { CodeBlock } from "./ui/code-block" | ||
|
||
export const CustomReactMarkdown = React.memo(function CustomReactMarkdown({ | ||
children, | ||
className, | ||
...props | ||
}: React.ComponentPropsWithoutRef<typeof ReactMarkdown>) { | ||
return ( | ||
<ReactMarkdown | ||
className={cn( | ||
"prose dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 break-words text-sm [&_ul]:list-disc [&_ol]:list-decimal [&_ul]:pl-6 [&_ol]:pl-6 [&>*]:mb-4 [&_li]:mb-2", | ||
className | ||
)} | ||
rehypePlugins={[rehypeRaw as any, { allowDangerousHtml: true }]} | ||
remarkPlugins={[remarkGfm, remarkMath]} | ||
skipHtml={false} | ||
components={{ | ||
p({ children }) { | ||
return <p className="mb-0.5 last:mb-0">{children}</p> | ||
}, | ||
br() { | ||
return <></> | ||
}, | ||
h1({ children }) { | ||
return <h1 className="text-xl font-semibold">{children}</h1> | ||
}, | ||
h2({ children }) { | ||
return <h2 className="text-lg font-semibold">{children}</h2> | ||
}, | ||
h3({ children }) { | ||
return <h3 className="text-base font-semibold">{children}</h3> | ||
}, | ||
a({ href, children, ...props }) { | ||
let target = "" | ||
if (href?.startsWith("http")) { | ||
target = "_blank" | ||
} else if (href?.startsWith("#")) { | ||
target = "_self" | ||
} | ||
return ( | ||
<a | ||
href={href} | ||
target={target} | ||
rel="noreferrer" | ||
className="text-blue-600 hover:underline" | ||
{...props} | ||
> | ||
{children} | ||
</a> | ||
) | ||
}, | ||
code({ inline, className, children, ...props }) { | ||
if (children && children.length) { | ||
if (children[0] == "▍") { | ||
return ( | ||
<span className="mt-1 animate-pulse cursor-default">▍</span> | ||
) | ||
} | ||
children[0] = (children[0] as string).replace("`▍`", "▍") | ||
} | ||
const match = /language-(\w+)/.exec(className || "") | ||
if (inline) { | ||
return ( | ||
<code | ||
className={cn( | ||
"rounded-md bg-muted-foreground/30 px-1", | ||
className | ||
)} | ||
{...props} | ||
> | ||
{children} | ||
</code> | ||
) | ||
} | ||
return ( | ||
<CodeBlock | ||
key={Math.random()} | ||
language={(match && match[1]) || ""} | ||
value={String(children).replace(/\n$/, "")} | ||
{...props} | ||
/> | ||
) | ||
}, | ||
}} | ||
{...props} | ||
> | ||
{children} | ||
</ReactMarkdown> | ||
) | ||
}) |
Oops, something went wrong.