diff options
Diffstat (limited to 'app/src/components/cat/Entry.tsx')
-rw-r--r-- | app/src/components/cat/Entry.tsx | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/app/src/components/cat/Entry.tsx b/app/src/components/cat/Entry.tsx new file mode 100644 index 0000000..1ded145 --- /dev/null +++ b/app/src/components/cat/Entry.tsx @@ -0,0 +1,214 @@ +import { + CategorizationResponse, + userCategories, +} from "../../lib/categorization"; +import { TwitterApiService, TwitterBookmark } from "../../lib/twitter-api"; + +export default function ({ + bookmark, + categorization, + currentIndex, + totalCount, +}: { + bookmark: TwitterBookmark; + categorization: CategorizationResponse; + currentIndex: number; + totalCount: number; +}) { + async function processEntry() { + "use server"; + const cookie = Bun.env.TWATTER_COKI; + const api = new TwitterApiService(cookie!); + await api.removeBookmark(bookmark.id); + } + + return ( + <div className="bg-white rounded-lg shadow-lg overflow-hidden"> + {/* Bookmark Content */} + <div className="p-6 border-b"> + <div className="flex items-start space-x-3 mb-4"> + <img + src={bookmark.author.avatar} + alt={bookmark.author.name} + className="w-12 h-12 rounded-full" + /> + <div> + <h3 className="font-semibold text-gray-900"> + {bookmark.author.name} + </h3> + <p className="text-gray-600">@{bookmark.author.username}</p> + <p className="text-sm text-gray-500"> + {new Date(bookmark.createdAt).toLocaleDateString()} + </p> + </div> + </div> + + <div className="prose max-w-none mb-4"> + <p className="whitespace-pre-wrap">{bookmark.text}</p> + </div> + + {bookmark.media.pics.length > 0 && ( + <div className="mb-4"> + <h4 className="text-sm font-semibold text-gray-700 mb-2">Images</h4> + <div className="grid grid-cols-3 gap-2"> + {bookmark.media.pics.map((url, index) => ( + <img + key={index} + src={url} + alt={`Bookmark image ${index + 1}`} + className="rounded-lg border" + /> + ))} + </div> + </div> + )} + + <div className="flex flex-wrap gap-2"> + {bookmark.hashtags.map((tag) => ( + <span + key={tag} + className="px-2 py-1 bg-gray-100 text-gray-700 rounded-full text-sm" + > + #{tag} + </span> + ))} + </div> + </div> + + {/* LLM Analysis */} + <div className="p-6 border-b bg-gray-50"> + <h3 className="text-lg font-semibold mb-3">AI Analysis</h3> + <p className="text-sm text-gray-600 mb-3">{categorization.summary}</p> + + <div className="mb-4"> + <h4 className="text-sm font-semibold mb-2">Key Topics</h4> + <div className="flex flex-wrap gap-2"> + {categorization.keyTopics.map((topic) => ( + <span + key={topic} + className="px-2 py-1 bg-blue-100 text-blue-800 rounded text-sm" + > + {topic} + </span> + ))} + </div> + </div> + + <div> + <h4 className="text-sm font-semibold mb-2">Suggested Categories</h4> + {categorization.suggestedCategories.map((suggestion, index) => ( + <div key={index} className="mb-2 p-2 bg-white rounded border"> + <div className="flex items-center justify-between mb-1"> + <span className="font-medium"> + {suggestion.categories.join(", ")} + </span> + <span className="text-sm text-gray-600"> + {(suggestion.confidence * 100).toFixed(0)}% + </span> + </div> + <p className="text-sm text-gray-600">{suggestion.reasoning}</p> + </div> + ))} + </div> + </div> + + {/* Category Selection */} + <form method="POST" action="/api/save-categorization"> + <div className="p-6"> + <h3 className="text-lg font-semibold mb-3">Select Categories</h3> + + <div className="mb-4"> + <h4 className="text-sm font-semibold mb-2">User Categories</h4> + <div className="flex flex-wrap gap-2"> + {userCategories.map((category) => ( + <label + key={category.name} + className="flex items-center cursor-pointer" + > + <input + type="checkbox" + name="categories" + value={category.name} + defaultChecked={categorization.suggestedCategories[0]?.categories.includes( + category.name, + )} + className="mr-2" + /> + <span className="px-3 py-1 bg-gray-200 text-gray-700 rounded-full text-sm"> + {category.name} + </span> + </label> + ))} + </div> + </div> + + {categorization.newCategories.length > 0 && ( + <div className="mb-4"> + <h4 className="text-sm font-semibold mb-2"> + New Category Suggestions + </h4> + <div className="flex flex-wrap gap-2"> + {categorization.newCategories.map((category) => ( + <label + key={category} + className="flex items-center cursor-pointer" + > + <input + type="checkbox" + name="categories" + value={category} + className="mr-2" + /> + <span className="px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm"> + {category} + </span> + </label> + ))} + </div> + </div> + )} + + <div className="mb-4"> + <label className="block text-sm font-semibold mb-2"> + Custom Categories + </label> + <input + type="text" + name="customCategories" + placeholder="Add custom categories, separated by commas" + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + </div> + + <input type="hidden" name="bookmarkId" value={bookmark.id} /> + <input type="hidden" name="nextIndex" value={currentIndex + 1} /> + + <div className="flex space-x-4"> + {currentIndex > 0 && ( + <a + href={`/categorize?idx=${currentIndex - 1}`} + className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50" + > + Previous + </a> + )} + <button + type="submit" + className="flex-1 bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700" + > + Save & Next + </button> + {currentIndex + 1 < totalCount && ( + <a + href={`/categorize?idx=${currentIndex + 1}`} + className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50" + > + Skip + </a> + )} + </div> + </div> + </form> + </div> + ); +} |