diff options
Diffstat (limited to 'app/src/pages/categorize.tsx')
-rw-r--r-- | app/src/pages/categorize.tsx | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/app/src/pages/categorize.tsx b/app/src/pages/categorize.tsx new file mode 100644 index 0000000..d91d8c1 --- /dev/null +++ b/app/src/pages/categorize.tsx @@ -0,0 +1,171 @@ +import * as Bun from "bun"; +import { Suspense } from "react"; +import { TwitterApiService, TwitterBookmark } from "../lib/twitter-api"; +import { + userCategories, + type CategorizationResponse, +} from "../lib/categorization"; +import { LLMService } from "../lib/llm-service"; +import ProcessedBookmark from "../components/cat/Entry"; + +interface CategorizePageProps { + bookmarks: Awaited<ReturnType<TwitterApiService["fetchAllBookmarks"]>>; + currentBookmarkIndex: number; + categorization?: CategorizationResponse; + error?: string; +} + +async function CategorizationFetcher({ + bookmarks, + currentIndex, +}: { + bookmarks: TwitterBookmark[]; + currentIndex: number; +}) { + if (currentIndex >= bookmarks.length) { + return ( + <div className="text-center py-12"> + <h2 className="text-2xl font-bold text-gray-900 mb-4"> + All Bookmarks Categorized! + </h2> + <p className="text-gray-600 mb-6"> + You've successfully categorized all your bookmarks. + </p> + <a + href="/" + className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700" + > + Back to Bookmarks + </a> + </div> + ); + } + + const bookmark = bookmarks[currentIndex]; + const llmRes = await callLLM(bookmark!); + if ("error" in llmRes) + return ( + <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg"> + Error categorizing bookmark: {llmRes.error} + </div> + ); + return ( + <div className=""> + <ProcessedBookmark + bookmark={bookmark!} + categorization={llmRes.ok} + currentIndex={currentIndex} + totalCount={bookmarks.length} + /> + </div> + ); +} + +function LoadingSpinner() { + return ( + <div className="flex items-center justify-center py-12"> + <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div> + <span className="ml-3 text-gray-600"> + Loading your Twitter bookmarks... + </span> + </div> + ); +} + +export default async function CategorizePage(props: any) { + // console.log({ props }); + const params = new URLSearchParams(props.query); + const currentIndex = Number(params.get("idx") || "0"); + const cookie = Bun.env.TWATTER_COKI; + + if (!cookie) { + return ( + <div className="text-red-600 text-center py-12"> + Missing Twitter cookie configuration + </div> + ); + } + const twbookmarks = await getTwData(cookie); + if ("error" in twbookmarks) { + return ( + <div className="text-red-600 text-center py-12"> + Error fetching Twatter bookmarks + </div> + ); + } + // const currentIndex = parseInt(searchParams.index || '0', 10); + const totalCount = twbookmarks.ok.length; + + return ( + <div className="min-h-screen bg-gray-50 py-8"> + <div className="max-w-4xl mx-auto px-4"> + <title>Categorize Bookmarks - SORMARK</title> + + <div className="mb-8"> + <h1 className="text-4xl font-bold tracking-tight mb-4"> + Categorize Bookmarks + </h1> + <p className="text-lg text-gray-600"> + Review and categorize your Twitter bookmarks one by one + </p> + </div> + + <Suspense fallback={<LoadingSpinner />}> + <> + <div className="bg-blue-600 text-white p-4"> + <p className="text-blue-100 mt-1"> + Bookmark {currentIndex + 1} of {totalCount} + </p> + <div className="w-full bg-blue-800 rounded-full h-2 mt-2"> + <div + className="bg-white h-2 rounded-full transition-all duration-300" + style={{ + width: `${((currentIndex + 1) / totalCount) * 100}%`, + }} + ></div> + </div> + </div> + <CategorizationFetcher + bookmarks={twbookmarks.ok} + currentIndex={currentIndex} + /> + </> + </Suspense> + </div> + </div> + ); +} + +import bmarks from "../lib/testData.json"; +async function getTwData(cookie: string) { + try { + // const twitterService = new TwitterApiService(cookie); + // const bookmarks = await twitterService.fetchAllBookmarks(); + // return { ok: bookmarks }; + return { ok: bmarks } as any; + } catch (error) { + return { error: `${error}` }; + } +} + +async function callLLM(bookmark: TwitterBookmark) { + const apiKey = Bun.env.GEMINI_API_KEY!; + try { + const llmService = new LLMService(apiKey); + + const categorization = await llmService.categorizeBookmark({ + bookmark, + userCategories, + }); + + return { ok: categorization }; + } catch (error) { + return { error: `${error}` }; + } +} + +export const getConfig = async () => { + return { + render: "dynamic", + } as const; +}; |