From f1ee6c9a81d4e37e55a23f51f68882b4ef08b4ef Mon Sep 17 00:00:00 2001 From: saul Date: Sun, 26 Nov 2023 14:31:33 -0500 Subject: [PATCH] feat: added integration with analysis endpoint --- go-server/handlers/youtube.go | 20 +++-- .../dashboard/button-new-analysis.tsx | 25 +++++- gptube/components/videos/MainStatistics.tsx | 5 +- gptube/constants.ts | 2 +- gptube/constants/general.constants.ts | 2 + gptube/hooks/use-dashboard-analysis.ts | 77 +++++++++++++++++++ gptube/hooks/video-query-keys.ts | 21 ++--- gptube/utils/validations.utils.ts | 11 ++- 8 files changed, 128 insertions(+), 35 deletions(-) create mode 100644 gptube/hooks/use-dashboard-analysis.ts diff --git a/go-server/handlers/youtube.go b/go-server/handlers/youtube.go index 6ff84ec..01b6a70 100644 --- a/go-server/handlers/youtube.go +++ b/go-server/handlers/youtube.go @@ -263,16 +263,15 @@ func YoutubeAnalysisHandler(c *fiber.Ctx) error { } //////////////////////////////////////////////// - fmt.Printf("[YoutubeAnalysisHandler] Number of comments success Bert: %d\n", + log.Printf("[YoutubeAnalysisHandler] Number of comments success Bert: %d\n", analysis.Results.BertResults.SuccessCount) - fmt.Printf("[YoutubeAnalysisHandler] Number of comments failed Bert: %d\n", + log.Printf("[YoutubeAnalysisHandler] Number of comments failed Bert: %d\n", analysis.Results.BertResults.ErrorsCount) - fmt.Printf("[YoutubeAnalysisHandler] Number of comments success Roberta: %d\n", + log.Printf("[YoutubeAnalysisHandler] Number of comments success Roberta: %d\n", analysis.Results.RobertaResults.SuccessCount) - fmt.Printf("[YoutubeAnalysisHandler] Number of comments failed Roberta: %d\n", + log.Printf("[YoutubeAnalysisHandler] Number of comments failed Roberta: %d\n", analysis.Results.RobertaResults.ErrorsCount) - c.JSON(successResp) - return c.SendStatus(http.StatusOK) + return c.Status(http.StatusOK).JSON(successResp) } // This means we have received email hence this video is large so we do all @@ -350,7 +349,7 @@ func YoutubeAnalysisHandler(c *fiber.Ctx) error { analysis.Results.RobertaResults.ErrorsCount) }(videoData) - return c.SendStatus(http.StatusOK) + return c.Status(http.StatusOK).JSON(fiber.Map{}) } // @Summary Simple analysis with BERT model for the landing page @@ -409,9 +408,8 @@ func YoutubeAnalysisLandingHandler(c *fiber.Ctx) error { CreatedAt: time.Now().UTC(), } - fmt.Printf("[YoutubeAnalysisLandingHandler] Number of comments success Bert: %d\n", results.BertResults.SuccessCount) - fmt.Printf("[YoutubeAnalysisLandingHandler] Number of comments failed Bert: %d\n", results.BertResults.ErrorsCount) - c.JSON(successResp) + log.Printf("[YoutubeAnalysisLandingHandler] Number of comments success Bert: %d\n", results.BertResults.SuccessCount) + log.Printf("[YoutubeAnalysisLandingHandler] Number of comments failed Bert: %d\n", results.BertResults.ErrorsCount) - return c.SendStatus(http.StatusOK) + return c.Status(http.StatusOK).JSON(successResp) } diff --git a/gptube/components/dashboard/button-new-analysis.tsx b/gptube/components/dashboard/button-new-analysis.tsx index 2dcd6c4..5cc53b4 100644 --- a/gptube/components/dashboard/button-new-analysis.tsx +++ b/gptube/components/dashboard/button-new-analysis.tsx @@ -15,6 +15,8 @@ import { useVideoPreview } from '@/hooks/use-video-preview' import { isValidEmail, isValidYoutubeUrl } from '@/utils/validations.utils' import { useForm } from '@/hooks/use-form' import { useAuth } from '@/hooks/use-auth' +import { extractYTVideoID } from '@/utils' +import { useDashboardAnalysis } from '@/hooks/use-dashboard-analysis' import { Button } from '../Common/button' @@ -22,18 +24,22 @@ import { VideoPreview } from './video-preview' export function ButtonNewAnalysis() { const { user } = useAuth() - const { handleChange, email, showEmail, url } = useForm({ + let { handleChange, email, showEmail, url } = useForm({ url: '', email: user?.email || '', - showEmail: true, + showEmail: false, }) + const { handleAnalysis, isLoading, dataAnalysis } = useDashboardAnalysis() + const [debouncedUrl] = useDebounce(url, 500) const modalAnalysis = useDisclosure() const videoPreviewQuery = useVideoPreview(debouncedUrl) const isInvalidUrl = !isValidYoutubeUrl(url) const isInvalidEmail = email === '' || !isValidEmail(email) + const isInvalid = isInvalidUrl || (showEmail && isInvalidEmail) + const videoId = extractYTVideoID(debouncedUrl) || '' return ( <> @@ -122,9 +128,20 @@ export function ButtonNewAnalysis() { videoPreviewQuery.isSuccess ? 'hover:!bg-opacity-60' : '' } font-medium text-white disabled:cursor-not-allowed transition-opacity`} color="success" - disabled={isInvalidUrl || isInvalidEmail || url.length === 0} + isDisabled={isInvalid} + isLoading={isLoading} radius="sm" - onPress={onClose} + onPress={async () => { + if (!showEmail) { + email = '' + await handleAnalysis('1', videoId, email) + onClose() + + return + } + handleAnalysis('1', videoId, email) + onClose() + }} > Start analysis diff --git a/gptube/components/videos/MainStatistics.tsx b/gptube/components/videos/MainStatistics.tsx index 6f9fe9e..7fcad3a 100644 --- a/gptube/components/videos/MainStatistics.tsx +++ b/gptube/components/videos/MainStatistics.tsx @@ -20,9 +20,8 @@ export function MainStatistics({ results, isLoading }: MainStatisticsProps) {

This recommendation is based on the most negative comments you received in your video. Such comments may stem from discomfort with the discussed topic, disagreement with your - views, or simply the presence of detractors. Take it just as a recommendation, we - don't have the truth maybe your channel treats controversial topics so don't - take it to seriously 😉. + views, or simply the presence of detractors. We don't have the truth maybe your + channel treats controversial topics so don't take it to seriously 😉.

diff --git a/gptube/constants.ts b/gptube/constants.ts index db88947..7fe3811 100644 --- a/gptube/constants.ts +++ b/gptube/constants.ts @@ -1 +1 @@ -export const KEY_ANALYSIS_LANDING = 'gptube-analysis' +export const KEY_ANALYSIS_LANDING = 'gptube-analysis' \ No newline at end of file diff --git a/gptube/constants/general.constants.ts b/gptube/constants/general.constants.ts index 40f5221..0f537c6 100644 --- a/gptube/constants/general.constants.ts +++ b/gptube/constants/general.constants.ts @@ -1,3 +1,5 @@ export const DEFAULT_PAGE_PAGINATION = 1 export const DEFAULT_PAGE_SIZE_PAGINATION = 10 + +export const CONTACT_SUPPORT_X = '@node_srojas1' \ No newline at end of file diff --git a/gptube/hooks/use-dashboard-analysis.ts b/gptube/hooks/use-dashboard-analysis.ts new file mode 100644 index 0000000..0e546a4 --- /dev/null +++ b/gptube/hooks/use-dashboard-analysis.ts @@ -0,0 +1,77 @@ +import type { ModelsYoutubeAnalyzerReqBody } from "@/gptube-api"; +import type { FormEvent } from "react"; + +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useEffect } from "react"; +import toast from "react-hot-toast"; + +import { apiClient } from "@/gptube-api"; +import { getAnalysisLandingFromCache } from "@/services/get-analysis-landing-from-cache.service"; +import { extractYTVideoID } from "@/utils"; + +import { videoQueryKeys } from "./video-query-keys"; +import { + DEFAULT_PAGE_PAGINATION, + DEFAULT_PAGE_SIZE_PAGINATION, + CONTACT_SUPPORT_X, +} from "@/constants/general.constants"; + +export function useDashboardAnalysis() { + const queryClient = useQueryClient(); + const mutation = useMutation({ + mutationFn: (data: ModelsYoutubeAnalyzerReqBody) => { + if (!data.email || data.email === "") { + toast.success("Analysis started, please wait..."); + } else { + toast.success("Analysis started, the results will be sent to your email."); + } + return apiClient.apiYoutubeAnalysisPost({ + video: data, + }); + }, + onSuccess: (data) => { + queryClient.invalidateQueries( + videoQueryKeys.videosAnalyzed({ + // TODO: implement authentication + userId: "1", + page: DEFAULT_PAGE_PAGINATION, + pageSize: DEFAULT_PAGE_SIZE_PAGINATION, + }), + { exact: true }, + ); + queryClient.setQueryData(videoQueryKeys.videoAnalysis(data.videoId || ""), data); + }, + onError: (error) => { + if (error instanceof Error) { + toast.error(error.message); + return; + } + toast.error( + `Something went wrong. Please try again later or contact ${CONTACT_SUPPORT_X} on X.`, + ); + }, + }); + + const handleAnalysis = async (userId: string, videoId: string, email?: string) => { + const data: ModelsYoutubeAnalyzerReqBody = { + userId: userId, + videoId: videoId, + }; + if (email && email !== "") { + data["email"] = email; + } + await mutation.mutateAsync(data); + }; + + useEffect(() => { + if (mutation.error instanceof Error) { + toast.error(mutation.error.message); + } + }, [mutation.error, mutation.isError]); + + return { + handleAnalysis, + isLoading: mutation.isLoading, + dataAnalysis: mutation.data, + }; +} diff --git a/gptube/hooks/video-query-keys.ts b/gptube/hooks/video-query-keys.ts index cc0b65c..13cf1ad 100644 --- a/gptube/hooks/video-query-keys.ts +++ b/gptube/hooks/video-query-keys.ts @@ -1,13 +1,14 @@ export const videoQueryKeys = { - videoPreview: (videoURL: string) => ['video-preview', videoURL] as const, + videoPreview: (videoURL: string) => ["video-preview", videoURL] as const, + videoAnalysis: (videoId: string) => ["video-analysis", videoId] as const, videosAnalyzed: (args: { userId: string; page: number; pageSize: number }) => - ['videos-analyzed', { ...args }] as const, - videoStats: (args: { userId: string; videoId: string }) => ['video-stats', { ...args }] as const, + ["videos-analyzed", { ...args }] as const, + videoStats: (args: { userId: string; videoId: string }) => ["video-stats", { ...args }] as const, videoNegativeComments: (args: { - userId: string - videoId: string - page: number - pageSize: number - }) => ['video-negative-comments', { ...args }] as const, - videoLanding: () => ['video-landing'] as const, -} + userId: string; + videoId: string; + page: number; + pageSize: number; + }) => ["video-negative-comments", { ...args }] as const, + videoLanding: () => ["video-landing"] as const, +}; diff --git a/gptube/utils/validations.utils.ts b/gptube/utils/validations.utils.ts index 1bb65a5..b629080 100644 --- a/gptube/utils/validations.utils.ts +++ b/gptube/utils/validations.utils.ts @@ -1,13 +1,12 @@ -import { YOUTUBE_URL_REGEX } from '@/constants/youtube.constants' +import { YOUTUBE_URL_REGEX } from "@/constants/youtube.constants"; -const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i +const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i; export function isValidYoutubeUrl(url: string) { - if (url.length === 0) return true - - return url.match(YOUTUBE_URL_REGEX) !== null + if (url.length === 0) return false; + return url.match(YOUTUBE_URL_REGEX) !== null; } export function isValidEmail(email: string) { - return EMAIL_REGEX.exec(email) !== null + return EMAIL_REGEX.exec(email) !== null; }