From ff3078e93411c3467d797258744a7f17a7dbdf0a Mon Sep 17 00:00:00 2001 From: polwex Date: Wed, 16 Jul 2025 10:07:06 +0700 Subject: m --- app/src/pages/_layout.tsx | 39 ++++++++++ app/src/pages/about.tsx | 35 +++++++++ app/src/pages/categorize.tsx | 171 +++++++++++++++++++++++++++++++++++++++++++ app/src/pages/index.tsx | 72 ++++++++++++++++++ 4 files changed, 317 insertions(+) create mode 100644 app/src/pages/_layout.tsx create mode 100644 app/src/pages/about.tsx create mode 100644 app/src/pages/categorize.tsx create mode 100644 app/src/pages/index.tsx (limited to 'app/src/pages') diff --git a/app/src/pages/_layout.tsx b/app/src/pages/_layout.tsx new file mode 100644 index 0000000..6d227c9 --- /dev/null +++ b/app/src/pages/_layout.tsx @@ -0,0 +1,39 @@ +import '../styles.css'; + +import type { ReactNode } from 'react'; + +import { Header } from '../components/header'; +import { Footer } from '../components/footer'; + +type RootLayoutProps = { children: ReactNode }; + +export default async function RootLayout({ children }: RootLayoutProps) { + const data = await getData(); + + return ( +
+ + +
+
+ {children} +
+
+
+ ); +} + +const getData = async () => { + const data = { + description: 'An internet website!', + icon: '/images/favicon.png', + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: 'static', + } as const; +}; diff --git a/app/src/pages/about.tsx b/app/src/pages/about.tsx new file mode 100644 index 0000000..c641af0 --- /dev/null +++ b/app/src/pages/about.tsx @@ -0,0 +1,35 @@ +import { Link } from "waku"; +import { listObsidian } from "../lib/categorization"; + +export default async function AboutPage() { + const data = await getData(); + + return ( +
+ {data.title} +

{data.headline}

+

{data.body}

+ + Return home + +
+ ); +} + +const getData = async () => { + const obsidian = await listObsidian(); + + const data = { + title: "About", + headline: "About Waku", + body: "The minimal React framework", + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: "static", + } as const; +}; 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>; + currentBookmarkIndex: number; + categorization?: CategorizationResponse; + error?: string; +} + +async function CategorizationFetcher({ + bookmarks, + currentIndex, +}: { + bookmarks: TwitterBookmark[]; + currentIndex: number; +}) { + if (currentIndex >= bookmarks.length) { + return ( +
+

+ All Bookmarks Categorized! +

+

+ You've successfully categorized all your bookmarks. +

+ + Back to Bookmarks + +
+ ); + } + + const bookmark = bookmarks[currentIndex]; + const llmRes = await callLLM(bookmark!); + if ("error" in llmRes) + return ( +
+ Error categorizing bookmark: {llmRes.error} +
+ ); + return ( +
+ +
+ ); +} + +function LoadingSpinner() { + return ( +
+
+ + Loading your Twitter bookmarks... + +
+ ); +} + +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 ( +
+ Missing Twitter cookie configuration +
+ ); + } + const twbookmarks = await getTwData(cookie); + if ("error" in twbookmarks) { + return ( +
+ Error fetching Twatter bookmarks +
+ ); + } + // const currentIndex = parseInt(searchParams.index || '0', 10); + const totalCount = twbookmarks.ok.length; + + return ( +
+
+ Categorize Bookmarks - SORMARK + +
+

+ Categorize Bookmarks +

+

+ Review and categorize your Twitter bookmarks one by one +

+
+ + }> + <> +
+

+ Bookmark {currentIndex + 1} of {totalCount} +

+
+
+
+
+ + +
+
+
+ ); +} + +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; +}; diff --git a/app/src/pages/index.tsx b/app/src/pages/index.tsx new file mode 100644 index 0000000..99202e5 --- /dev/null +++ b/app/src/pages/index.tsx @@ -0,0 +1,72 @@ +import * as Bun from "bun"; +import { Suspense } from "react"; +import { TwitterApiService } from "../lib/twitter-api"; +import { BookmarkList } from "../components/bookmark-list"; + +async function BookmarkFetcher() { + const cookie = Bun.env.TWATTER_COKI; + + if (!cookie) { + return ( +
Missing Twitter cookie configuration
+ ); + } + + try { + const twitterService = new TwitterApiService(cookie); + const bookmarks = await twitterService.fetchAllBookmarks(); + const file = Bun.file("testData.json"); + await file.write(JSON.stringify(bookmarks)); + + return ( +
+
+

Your Bookmarks

+ +
+
+ ); + } catch (error) { + return ( +
+ Error loading bookmarks:{" "} + {error instanceof Error ? error.message : "Unknown error"} +
+ ); + } +} + +function LoadingSpinner() { + return ( +
+
+ + Loading your Twitter bookmarks... + +
+ ); +} + +export default async function HomePage() { + return ( +
+ SORMARK - Twitter Bookmark Manager +
+

SORMARK

+

+ Your Twitter bookmark manager powered by AI +

+
+ + }> + + +
+ ); +} + +export const getConfig = async () => { + return { + render: "static", + } as const; +}; -- cgit v1.2.3