Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate analysis with the dashboard #86

Merged
merged 2 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions go-server/handlers/youtube.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
26 changes: 22 additions & 4 deletions gptube/components/dashboard/button-new-analysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,32 @@ 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'

import { VideoPreview } from './video-preview'

export function ButtonNewAnalysis() {
const { user } = useAuth()
const { handleChange, email, showEmail, url } = useForm({
// eslint-disable-next-line prefer-const
let { handleChange, email, showEmail, url } = useForm({
url: '',
email: user?.email || '',
showEmail: true,
showEmail: false,
})

const { handleAnalysis, isLoading } = 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 (
<>
Expand Down Expand Up @@ -122,9 +129,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
</Button>
Expand Down
6 changes: 4 additions & 2 deletions gptube/components/dashboard/card-video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function CardVideo({ snippet, createdAt, videoId }: ModelsYoutubeVideoDas
const imgCard = thumbnails?._default?.url

return (
<article className="flex items-center gap-4 p-4 rounded-md shadow-sm">
<article className="flex items-center gap-4 p-4 rounded-md shadow-sm border">
<section>
{imgCard ? (
<img alt={title} className="object-cover object-center w-32 rounded" src={imgCard} />
Expand All @@ -29,7 +29,9 @@ export function CardVideo({ snippet, createdAt, videoId }: ModelsYoutubeVideoDas
{createdAt ? (
<>
<Dot />
<small>{formatDate(createdAt, 'MMM/DD')}</small>
<small title={formatDate(createdAt, 'DD MMM, YYYY (HH:mm A)')}>
{formatDate(createdAt, 'MMM/DD')}
</small>
</>
) : null}
</div>
Expand Down
35 changes: 22 additions & 13 deletions gptube/components/dashboard/dashboard-ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Card, CardBody } from '@nextui-org/card'
import { Pagination, Spinner } from '@nextui-org/react'

import { useVideosAnalyzed } from '@/hooks/use-videos-analyzed'
import { DEFAULT_PAGE_PAGINATION } from '@/constants/general.constants'

import { FilterVideoAnalysis } from './filter-video-analysis'
import { ButtonNewAnalysis } from './button-new-analysis'
Expand All @@ -10,50 +11,58 @@ import { CardVideo } from './card-video'

export function DashboardUI() {
const {
count,
videos,
isLoading: isLoadingVideos,
isError: isErrorVideos,
handleChangePage,
page,
totalPages,
isFetching,
} = useVideosAnalyzed()

return (
<main className="max-w-screen-lg px-6 py-6 mx-auto space-y-6">
<main className="max-w-screen-lg py-6 mx-auto space-y-6">
<header className="flex justify-end">
<ButtonNewAnalysis />
</header>
<section className="flex gap-10">
<aside>
<Card className="bg-white w-80" radius="sm" shadow="sm">
<Card className="bg-white w-72" radius="sm" shadow="sm">
<CardBody>
<FilterVideoAnalysis />
</CardBody>
</Card>
</aside>
<aside className="flex-1">
<Card className="min-h-[66.5dvh]" radius="sm">
<Card radius="sm">
<CardBody className="space-y-6">
<section>
{isLoadingVideos ? (
<div className="flex">
<Spinner className="mx-auto" color="default" />
</div>
) : (
{isLoadingVideos || isFetching ? (
<div className="flex justify-center">
<Spinner color="default" label="Loading videos..." />
</div>
) : (
<div className="px-4">
<p className="text-sm py-2 text-center">
Total videos analyzed are <span className="font-bold">{count || 0}</span>
</p>
<div className="space-y-2">
{!videos || videos.length === 0 ? <NotVideoFound /> : null}
<section className="space-y-4">
{videos?.map(video => <CardVideo key={video.videoId} {...video} />)}
</section>
</div>
)}
</section>
</div>
)}
{videos != null && videos.length > 0 ? (
<div className="flex justify-center">
<Pagination
isCompact
showControls
classNames={{
cursor: 'text-white',
}}
color="success"
initialPage={page}
initialPage={DEFAULT_PAGE_PAGINATION}
isDisabled={isFetching || isErrorVideos}
total={totalPages}
onChange={handleChangePage}
Expand Down
3 changes: 2 additions & 1 deletion gptube/components/dashboard/filter-video-analysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ export function FilterVideoAnalysis() {
return (
<div className="flex flex-col gap-4">
<Input
isDisabled
label="Filter videos analyzed"
labelPlacement="outside"
size="lg"
size="md"
startContent={<Search className="w-4 h-4" />}
variant="underlined"
/>
Expand Down
5 changes: 2 additions & 3 deletions gptube/components/videos/MainStatistics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ export function MainStatistics({ results, isLoading }: MainStatisticsProps) {
<p className="py-4 text-base">
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&apos;t have the truth maybe your channel treats controversial topics so don&apos;t
take it to seriously 😉.
views, or simply the presence of detractors. We don&apos;t have the truth maybe your
channel treats controversial topics so don&apos;t take it to seriously 😉.
</p>
<section>
<p className="h-56 py-2 pl-3 overflow-auto text-sm whitespace-pre-line rounded-lg bg-slate-50">
Expand Down
2 changes: 2 additions & 0 deletions gptube/constants/general.constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const DEFAULT_PAGE_PAGINATION = 1

export const DEFAULT_PAGE_SIZE_PAGINATION = 10

export const CONTACT_SUPPORT_X = '@node_srojas1'
77 changes: 77 additions & 0 deletions gptube/hooks/use-dashboard-analysis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { ModelsYoutubeAnalyzerReqBody } from '@/gptube-api'

import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useEffect } from 'react'
import toast from 'react-hot-toast'

import { apiClient } from '@/gptube-api'
import {
DEFAULT_PAGE_PAGINATION,
DEFAULT_PAGE_SIZE_PAGINATION,
CONTACT_SUPPORT_X,
} from '@/constants/general.constants'

import { videoQueryKeys } from './video-query-keys'

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,
}
}
3 changes: 2 additions & 1 deletion gptube/hooks/use-videos-analyzed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function useVideosAnalyzed() {
})

const totalVideos = query.data?.count ?? 0
const totalPages = totalVideos / pageSize
const totalPages = Math.ceil(totalVideos / pageSize)

useEffect(() => {
if (query.error instanceof Error) {
Expand All @@ -46,6 +46,7 @@ export function useVideosAnalyzed() {
}

return {
count: query.data?.count,
videos: query.data?.results,
handleChangePage,
totalPages,
Expand Down
1 change: 1 addition & 0 deletions gptube/hooks/video-query-keys.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const videoQueryKeys = {
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,
Expand Down
2 changes: 1 addition & 1 deletion gptube/utils/validations.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { YOUTUBE_URL_REGEX } from '@/constants/youtube.constants'
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
if (url.length === 0) return false

return url.match(YOUTUBE_URL_REGEX) !== null
}
Expand Down