diff --git a/lib/ranking.ts b/lib/ranking.ts index 64fcffea..8fd22391 100644 --- a/lib/ranking.ts +++ b/lib/ranking.ts @@ -56,13 +56,15 @@ function computeTrendScore( } /** Shared: fetch storyline candidates + batch ratings */ -async function fetchCandidatesAndRatings(supabase: SupabaseClient) { +async function fetchCandidatesAndRatings(supabase: SupabaseClient, writerType?: number) { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { data } = await (supabase.from("storylines") as any) + let q = (supabase.from("storylines") as any) .select("*") .eq("hidden", false) .eq("sunset", false) - .neq("token_address", "") + .neq("token_address", ""); + if (writerType !== undefined) q = q.eq("writer_type", writerType); + const { data } = await q .order("block_timestamp", { ascending: false }) .limit(50); @@ -115,8 +117,9 @@ async function enrichWithOnChain( export async function getTrendingStorylines( supabase: SupabaseClient, limit = 20, + writerType?: number, ): Promise { - const { storylines, ratingMap } = await fetchCandidatesAndRatings(supabase); + const { storylines, ratingMap } = await fetchCandidatesAndRatings(supabase, writerType); if (storylines.length === 0) return []; const enriched = await Promise.all( @@ -153,8 +156,9 @@ export async function getTrendingStorylines( export async function getRisingStorylines( supabase: SupabaseClient, limit = 20, + writerType?: number, ): Promise { - const { storylines } = await fetchCandidatesAndRatings(supabase); + const { storylines } = await fetchCandidatesAndRatings(supabase, writerType); if (storylines.length === 0) return []; const now = new Date(); diff --git a/src/app/discover/page.tsx b/src/app/discover/page.tsx index aa7696fb..04f38c3d 100644 --- a/src/app/discover/page.tsx +++ b/src/app/discover/page.tsx @@ -2,21 +2,32 @@ import { createServerClient, type Storyline } from "../../../lib/supabase"; import { getTrendingStorylines, getRisingStorylines } from "../../../lib/ranking"; import { StoryCard } from "../../components/StoryCard"; import { TabNav } from "../../components/TabNav"; +import { + WriterFilter, + type WriterFilterValue, +} from "../../components/WriterFilter"; export const revalidate = 120; // ISR: regenerate at most every 2 minutes -type SearchParams = Promise<{ tab?: string }>; +type SearchParams = Promise<{ tab?: string; writer?: string }>; const TABS = ["new", "trending", "rising", "completed"] as const; type Tab = (typeof TABS)[number]; +const WRITER_VALUES: WriterFilterValue[] = ["all", "human", "agent"]; + export default async function DiscoverPage({ searchParams, }: { searchParams: SearchParams; }) { - const { tab: rawTab } = await searchParams; + const { tab: rawTab, writer: rawWriter } = await searchParams; const tab: Tab = TABS.includes(rawTab as Tab) ? (rawTab as Tab) : "new"; + const writer: WriterFilterValue = WRITER_VALUES.includes( + rawWriter as WriterFilterValue, + ) + ? (rawWriter as WriterFilterValue) + : "all"; const supabase = createServerClient(); if (!supabase) { @@ -27,7 +38,10 @@ export default async function DiscoverPage({ ); } - const storylines = await queryTab(supabase, tab); + const storylines = await queryTab(supabase, tab, writer); + + const extraParams = + writer !== "all" ? { writer } : undefined; return (
@@ -36,7 +50,14 @@ export default async function DiscoverPage({

Browse stories on PlotLink

- + + +
{storylines.map((s) => ( @@ -55,14 +76,18 @@ export default async function DiscoverPage({ async function queryTab( supabase: ReturnType & object, tab: Tab, + writer: WriterFilterValue, ): Promise { switch (tab) { case "new": { - const { data } = await supabase + let q = supabase .from("storylines") .select("*") .eq("hidden", false) - .eq("sunset", false) + .eq("sunset", false); + if (writer === "human") q = q.eq("writer_type", 0); + if (writer === "agent") q = q.eq("writer_type", 1); + const { data } = await q .order("block_timestamp", { ascending: false }) .limit(50) .returns(); @@ -70,11 +95,14 @@ async function queryTab( } case "completed": { - const { data } = await supabase + let q = supabase .from("storylines") .select("*") .eq("hidden", false) - .eq("sunset", true) + .eq("sunset", true); + if (writer === "human") q = q.eq("writer_type", 0); + if (writer === "agent") q = q.eq("writer_type", 1); + const { data } = await q .order("plot_count", { ascending: false }) .limit(50) .returns(); @@ -82,11 +110,13 @@ async function queryTab( } case "trending": { - return getTrendingStorylines(supabase, 20); + const wt = writer === "human" ? 0 : writer === "agent" ? 1 : undefined; + return getTrendingStorylines(supabase, 20, wt); } case "rising": { - return getRisingStorylines(supabase, 20); + const wt = writer === "human" ? 0 : writer === "agent" ? 1 : undefined; + return getRisingStorylines(supabase, 20, wt); } } } diff --git a/src/app/story/[storylineId]/page.tsx b/src/app/story/[storylineId]/page.tsx index af0165cf..18ae918f 100644 --- a/src/app/story/[storylineId]/page.tsx +++ b/src/app/story/[storylineId]/page.tsx @@ -9,6 +9,7 @@ import { getTokenPrice, type TokenPriceInfo } from "../../../../lib/price"; import { IS_TESTNET } from "../../../../lib/contracts/constants"; import { type Address } from "viem"; import { truncateAddress } from "../../../../lib/utils"; +import { AgentBadge } from "../../../components/AgentBadge"; type Params = Promise<{ storylineId: string }>; @@ -115,11 +116,7 @@ function StoryHeader({ {storyline.plot_count} {storyline.plot_count === 1 ? "plot" : "plots"} - {storyline.writer_type === 1 && ( - - agent - - )} + {storyline.writer_type === 1 && }
diff --git a/src/components/AgentBadge.tsx b/src/components/AgentBadge.tsx new file mode 100644 index 00000000..3f8135b0 --- /dev/null +++ b/src/components/AgentBadge.tsx @@ -0,0 +1,9 @@ +export function AgentBadge({ className }: { className?: string }) { + return ( + + agent + + ); +} diff --git a/src/components/StoryCard.tsx b/src/components/StoryCard.tsx index 3566e354..0c6857c4 100644 --- a/src/components/StoryCard.tsx +++ b/src/components/StoryCard.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; import type { Storyline } from "../../lib/supabase"; import { truncateAddress } from "../../lib/utils"; +import { AgentBadge } from "./AgentBadge"; export function StoryCard({ storyline, @@ -33,11 +34,7 @@ export function StoryCard({ {genre} )} - {storyline.writer_type === 1 && ( - - agent - - )} + {storyline.writer_type === 1 && }
); diff --git a/src/components/TabNav.tsx b/src/components/TabNav.tsx index caf3f55b..e9883577 100644 --- a/src/components/TabNav.tsx +++ b/src/components/TabNav.tsx @@ -4,17 +4,29 @@ export function TabNav({ tabs, active, className, + extraParams, }: { tabs: readonly string[]; active: string; className?: string; + extraParams?: Record; }) { + function buildHref(tab: string) { + const params = new URLSearchParams({ tab }); + if (extraParams) { + for (const [k, v] of Object.entries(extraParams)) { + params.set(k, v); + } + } + return `/discover?${params.toString()}`; + } + return (