From 615d9bc9978f571fce49d446363213951f388412 Mon Sep 17 00:00:00 2001 From: Jamesb Date: Sun, 25 Feb 2024 09:27:39 +0000 Subject: [PATCH] feat: enable requesting recommendations --- .../client/src/components/ProfilePage.tsx | 140 +++++++++++++++--- .../server/src/lib/getNumRunningPipelines.ts | 26 ++++ .../server/src/routers/authenticatedRouter.ts | 60 ++++++++ .../server/src/tasks/twitterPipeline.saga.ts | 1 - 4 files changed, 202 insertions(+), 25 deletions(-) create mode 100644 packages/server/src/lib/getNumRunningPipelines.ts diff --git a/packages/client/src/components/ProfilePage.tsx b/packages/client/src/components/ProfilePage.tsx index b2da705..960390a 100644 --- a/packages/client/src/components/ProfilePage.tsx +++ b/packages/client/src/components/ProfilePage.tsx @@ -8,6 +8,7 @@ import { Button, Collapse, Paper, + Snackbar, Table, TableBody, TableCell, @@ -27,7 +28,8 @@ interface ProfilePageProps { auth: AuthInfo | undefined; } -interface TableRow { +interface SummaryRowData { + id: number; createdAt: string; type: string; content: string; @@ -48,6 +50,8 @@ export function ProfilePage(props: ProfilePageProps) { React.useState(); const [isFollowing, setIsFollowing] = React.useState(false); + const [requestingRecommendations, setRequestingRecommendations] = + React.useState(false); React.useEffect(() => { if (!props.auth?.authenticated) return; @@ -98,9 +102,10 @@ export function ProfilePage(props: ProfilePageProps) { ); } - const rows: TableRow[] = []; + const rows: SummaryRowData[] = []; summaries?.forEach((summary) => { rows.push({ + id: summary.id, createdAt: summary.createdAt, type: "Twitter Summary", content: summary.content, @@ -108,18 +113,26 @@ export function ProfilePage(props: ProfilePageProps) { }); }); + const [customQuery, setCustomQuery] = React.useState(""); + const [customQueryMessage, setCustomQueryMessage] = + React.useState(""); + const handleClose = () => { + setCustomQueryMessage(""); + }; + if (!profileForUser) { return
Loading...
; } - profileForUser.following.forEach((follow) => { - rows.push({ - createdAt: follow.createdAt, - type: "Following", - content: follow.user.username, - useForRecommendations: true, - }); - }); + // profileForUser.following.forEach((follow) => { + // rows.push({ + // id: follow.id, + // createdAt: follow.createdAt, + // type: "Following", + // content: follow.user.username, + // useForRecommendations: true, + // }); + // }); return (
@@ -153,14 +166,43 @@ export function ProfilePage(props: ProfilePageProps) {


-
- - -
+ {viewingOwnProfile && ( +
+ setCustomQuery(e.target.value)} + /> + + +
+ )}

Recommendation Inputs



@@ -177,9 +219,12 @@ export function ProfilePage(props: ProfilePageProps) { {_.sortBy(rows, (x) => x.createdAt).map((row, idx) => ( ))} @@ -190,12 +235,15 @@ export function ProfilePage(props: ProfilePageProps) { } interface SummaryRowProps { - row: TableRow; + row: SummaryRowData; viewingOwnProfile: boolean; + auth: AuthInfo | undefined; + requestingRecommendations: boolean; + setRequestingRecommendations: (value: boolean) => void; } function SummaryRow(props: SummaryRowProps) { - const { row, viewingOwnProfile } = props; + const { row, viewingOwnProfile, auth } = props; const [expanded, setExpanded] = React.useState(false); return ( @@ -218,16 +266,60 @@ function SummaryRow(props: SummaryRowProps) { - {viewingOwnProfile && ( + {viewingOwnProfile && auth?.authenticated && (
{/* */}
- +
)}
); } + +interface GetRecommendationsButtonProps { + row: SummaryRowData; + auth: AuthInfo | undefined; + disabled?: boolean; + setRequestingRecommendations: (value: boolean) => void; +} + +function GetRecommendationsButton(props: GetRecommendationsButtonProps) { + const [message, setMessage] = React.useState(""); + const handleClose = () => { + setMessage(""); + }; + return ( + <> + + + + ); +} diff --git a/packages/server/src/lib/getNumRunningPipelines.ts b/packages/server/src/lib/getNumRunningPipelines.ts new file mode 100644 index 0000000..5894b41 --- /dev/null +++ b/packages/server/src/lib/getNumRunningPipelines.ts @@ -0,0 +1,26 @@ +import { UserModel } from "shared/src/schemas"; +import { z } from "zod"; +import { prisma } from "../db"; + +export async function getNumRunningPipelines( + authenticatedUser: z.infer +): Promise { + if (!authenticatedUser) { + return; + } + const pipelines = ( + await prisma.pipelineRun.findMany({ + where: { + username: authenticatedUser.username, + }, + include: { + tasks: true, + }, + }) + ) + // TODO: check + // slice(1) because the first task is the pipeline itself, doesn't get updated properly + .filter((p) => p.tasks.slice(1).some((t) => t.status === "running")); + + return pipelines.length; +} diff --git a/packages/server/src/routers/authenticatedRouter.ts b/packages/server/src/routers/authenticatedRouter.ts index fe4045b..4989aea 100644 --- a/packages/server/src/routers/authenticatedRouter.ts +++ b/packages/server/src/routers/authenticatedRouter.ts @@ -3,6 +3,9 @@ import { router, publicProcedure } from "../trpc"; import { z } from "zod"; import { PublicUserModel } from "shared/src/manual/PublicUser"; import { generateAPIKey } from "../generateAPIKey"; +import { addPipeline } from "../tasks/worker"; +import { randomUUID } from "crypto"; +import { getNumRunningPipelines } from "../lib/getNumRunningPipelines"; export const authenticatedRouter = router({ voteOnRecommendation: publicProcedure @@ -326,4 +329,61 @@ export const authenticatedRouter = router({ }); return apiKey; }), + + getNumRunningPipelines: publicProcedure.query(async ({ ctx }) => { + const user = await prisma.user.findUnique({ + where: { + id: ctx.user?.id, + }, + }); + if (!user) { + return; + } + return await getNumRunningPipelines(user); + }), + + requestRecommendations: publicProcedure + .input( + z.object({ + summaryId: z.number().optional(), + customQuery: z.string().optional(), + }) + ) + .mutation(async ({ ctx, input }) => { + const authenticatedUser = await prisma.user.findUnique({ + where: { + id: ctx.user?.id, + }, + }); + if (!authenticatedUser) { + return { type: "error" as const, error: "User not found" }; + } + + const numRunning = await getNumRunningPipelines(authenticatedUser); + if (numRunning && numRunning >= 2) { + return { + type: "error" as const, + error: "You can only have max 2 pipelines running at a time", + }; + } + + const summary = input.summaryId + ? await prisma.summary.findFirst({ + where: { + userId: authenticatedUser.id, + id: input.summaryId, + }, + }) + : undefined; + + const job = await addPipeline("twitter-pipeline-v1", { + username: authenticatedUser.username, + summary: summary?.content, + queries: input.customQuery ? [input.customQuery] : undefined, + runId: randomUUID(), + emailResults: true, + }); + + return { type: "success" as const }; + }), }); diff --git a/packages/server/src/tasks/twitterPipeline.saga.ts b/packages/server/src/tasks/twitterPipeline.saga.ts index 8484d76..45d9e41 100644 --- a/packages/server/src/tasks/twitterPipeline.saga.ts +++ b/packages/server/src/tasks/twitterPipeline.saga.ts @@ -35,7 +35,6 @@ import { sendEmail } from "../lib/sendEmail"; import { TranscriptClipWithScore } from "shared/src/manual/TranscriptClip"; import { ArticleSnippetWithScore } from "shared/src/manual/ArticleSnippet"; import { chunksToClips } from "cli/src/recommender/chunksToClips"; -import { format } from "path"; type QueryWithSearchResultWithTranscript = { searchResults: (VideoResultWithTranscript | MetaphorArticleResult)[];