From ff3078e93411c3467d797258744a7f17a7dbdf0a Mon Sep 17 00:00:00 2001
From: polwex <polwex@sortug.com>
Date: Wed, 16 Jul 2025 10:07:06 +0700
Subject: m

---
 app/src/pages/categorize.tsx | 171 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 171 insertions(+)
 create mode 100644 app/src/pages/categorize.tsx

(limited to 'app/src/pages/categorize.tsx')

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;
+};
-- 
cgit v1.2.3

