From c90285c3cbd19965cebd3bdc9e773911c8ed48bd Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Fri, 17 Apr 2026 18:23:03 +0530 Subject: [PATCH] chore: remove dead segments surface Deletes the remaining segment list/create routes, the Segments UI components, segment-fans renderers, segment hooks, lib/segments, chat-segment lookup, segment MCP/AI tool wrappers, sidebar segment nav entries, and segment-related supabase helpers. The Segments feature is being removed entirely; the earlier migration plan is abandoned. Follows #1678 (segment report removal). Part of SEGMENTS_SURFACE_REMOVAL_PLAN.md. Co-Authored-By: Claude Opus 4.6 --- app/api/segments/create/route.ts | 24 --- app/api/segments/route.ts | 25 ---- app/segments/page.tsx | 21 --- components/Segments/FanProfileHover.tsx | 99 ------------- components/Segments/NoSegmentsFound.tsx | 53 ------- components/Segments/SegmentButton.tsx | 43 ------ components/Segments/SegmentFanCircles.tsx | 95 ------------ components/Segments/Segments.tsx | 20 --- components/Segments/SegmentsSkeleton.tsx | 28 ---- components/Segments/SegmentsWrapper.tsx | 33 ----- components/SideMenu/SideMenu.tsx | 6 - components/Sidebar/FanGroupNavItem.tsx | 31 ---- components/Sidebar/Menu.tsx | 2 - components/Sidebar/MiniMenu.tsx | 17 +-- components/Sidebar/SecondaryNav.tsx | 4 - components/VercelChat/ToolComponents.tsx | 15 -- .../tools/segment-fans/EmptyState.tsx | 19 --- .../tools/segment-fans/ErrorState.tsx | 23 --- .../VercelChat/tools/segment-fans/FanCard.tsx | 59 -------- .../segment-fans/GetSegmentFansResult.tsx | 47 ------ .../GetSegmentFansResultSkeleton.tsx | 138 ------------------ .../tools/segment-fans/SegmentHeader.tsx | 31 ---- .../tools/segment-fans/animations.ts | 28 ---- hooks/useArtistSegments.ts | 21 --- hooks/useChatSegment.ts | 27 ---- hooks/useCreateSegments.ts | 39 ----- hooks/useFansCSVExport.tsx | 21 --- lib/agent/getSegments.tsx | 23 --- lib/chats/getChatSegment.ts | 31 ---- lib/getFanSegments.tsx | 26 ---- lib/getFunnelReport.tsx | 11 -- lib/segments/createSegmentResponses.ts | 29 ---- lib/segments/createSegments.ts | 134 ----------------- lib/segments/generateSegments.ts | 29 ---- lib/segments/getAnalysisPrompt.ts | 38 ----- lib/segments/getFanSegmentsToInsert.ts | 25 ---- .../artist_segments/insertArtistSegments.ts | 21 --- .../fan_segments/insertFanSegments.ts | 21 --- .../fan_segments/selectFanSegments.ts | 36 ----- lib/supabase/getArtistSegmentNames.ts | 35 ----- lib/supabase/getArtistSegments.ts | 70 --------- lib/supabase/getSegmentCounts.ts | 32 ---- lib/supabase/segments/deleteSegments.ts | 46 ------ lib/supabase/segments/insertSegments.ts | 21 --- lib/tools/createSegments.ts | 29 ---- lib/tools/getArtistSegments.ts | 84 ----------- lib/tools/getMcpTools.ts | 6 - lib/tools/getSegmentFans.ts | 64 -------- lib/utils/getToolsInfo.ts | 8 - public/segment.svg | 3 - types/fans.ts | 40 ----- 51 files changed, 1 insertion(+), 1830 deletions(-) delete mode 100644 app/api/segments/create/route.ts delete mode 100644 app/api/segments/route.ts delete mode 100644 app/segments/page.tsx delete mode 100644 components/Segments/FanProfileHover.tsx delete mode 100644 components/Segments/NoSegmentsFound.tsx delete mode 100644 components/Segments/SegmentButton.tsx delete mode 100644 components/Segments/SegmentFanCircles.tsx delete mode 100644 components/Segments/Segments.tsx delete mode 100644 components/Segments/SegmentsSkeleton.tsx delete mode 100644 components/Segments/SegmentsWrapper.tsx delete mode 100644 components/Sidebar/FanGroupNavItem.tsx delete mode 100644 components/VercelChat/tools/segment-fans/EmptyState.tsx delete mode 100644 components/VercelChat/tools/segment-fans/ErrorState.tsx delete mode 100644 components/VercelChat/tools/segment-fans/FanCard.tsx delete mode 100644 components/VercelChat/tools/segment-fans/GetSegmentFansResult.tsx delete mode 100644 components/VercelChat/tools/segment-fans/GetSegmentFansResultSkeleton.tsx delete mode 100644 components/VercelChat/tools/segment-fans/SegmentHeader.tsx delete mode 100644 components/VercelChat/tools/segment-fans/animations.ts delete mode 100644 hooks/useArtistSegments.ts delete mode 100644 hooks/useChatSegment.ts delete mode 100644 hooks/useCreateSegments.ts delete mode 100644 hooks/useFansCSVExport.tsx delete mode 100644 lib/agent/getSegments.tsx delete mode 100644 lib/chats/getChatSegment.ts delete mode 100644 lib/getFanSegments.tsx delete mode 100644 lib/getFunnelReport.tsx delete mode 100644 lib/segments/createSegmentResponses.ts delete mode 100644 lib/segments/createSegments.ts delete mode 100644 lib/segments/generateSegments.ts delete mode 100644 lib/segments/getAnalysisPrompt.ts delete mode 100644 lib/segments/getFanSegmentsToInsert.ts delete mode 100644 lib/supabase/artist_segments/insertArtistSegments.ts delete mode 100644 lib/supabase/fan_segments/insertFanSegments.ts delete mode 100644 lib/supabase/fan_segments/selectFanSegments.ts delete mode 100644 lib/supabase/getArtistSegmentNames.ts delete mode 100644 lib/supabase/getArtistSegments.ts delete mode 100644 lib/supabase/getSegmentCounts.ts delete mode 100644 lib/supabase/segments/deleteSegments.ts delete mode 100644 lib/supabase/segments/insertSegments.ts delete mode 100644 lib/tools/createSegments.ts delete mode 100644 lib/tools/getArtistSegments.ts delete mode 100644 lib/tools/getSegmentFans.ts delete mode 100644 public/segment.svg diff --git a/app/api/segments/create/route.ts b/app/api/segments/create/route.ts deleted file mode 100644 index 7540fae5c..000000000 --- a/app/api/segments/create/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { createSegments } from "@/lib/segments/createSegments"; - -export async function POST(req: NextRequest) { - try { - const { artist_account_id, prompt } = await req.json(); - if (!artist_account_id || !prompt) { - return NextResponse.json( - { error: "artist_account_id and prompt are required" }, - { status: 400 } - ); - } - const result = await createSegments({ artist_account_id, prompt }); - return NextResponse.json(result); - } catch (error) { - return NextResponse.json( - { - error: - error instanceof Error ? error.message : "Failed to create segments", - }, - { status: 500 } - ); - } -} diff --git a/app/api/segments/route.ts b/app/api/segments/route.ts deleted file mode 100644 index deb3db5a6..000000000 --- a/app/api/segments/route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { getArtistSegments } from "@/lib/supabase/getArtistSegments"; -import { NextRequest } from "next/server"; - -export async function GET(req: NextRequest) { - const artistId = req.nextUrl.searchParams.get("artistId"); - - if (!artistId) { - return Response.json({ error: "artistId is required" }, { status: 400 }); - } - - try { - const segments = await getArtistSegments(artistId); - return Response.json(segments, { status: 200 }); - } catch (error) { - console.error("Error fetching segments:", error); - return Response.json( - { error: "Failed to fetch segments" }, - { status: 500 } - ); - } -} - -export const dynamic = "force-dynamic"; -export const fetchCache = "force-no-store"; -export const revalidate = 0; diff --git a/app/segments/page.tsx b/app/segments/page.tsx deleted file mode 100644 index 17e2c8c24..000000000 --- a/app/segments/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import { type NextPage } from "next"; -import SegmentsWrapper from "@/components/Segments/SegmentsWrapper"; - -const SegmentsPage: NextPage = () => { - return ( -
-

- Fans -

-

- View fan groups and insights. - View your fan groups and get automated insights. -

- -
- ); -}; - -export default SegmentsPage; diff --git a/components/Segments/FanProfileHover.tsx b/components/Segments/FanProfileHover.tsx deleted file mode 100644 index 849af721b..000000000 --- a/components/Segments/FanProfileHover.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Tables } from "@/types/database.types"; -import { Card, CardContent } from "@/components/ui/card"; -import { User } from "lucide-react"; -import { useState } from "react"; - -type Social = Tables<"socials">; - -interface FanProfileHoverProps { - fan: Social; - children: React.ReactNode; -} - -const FanProfileHover = ({ fan, children }: FanProfileHoverProps) => { - const [imageError, setImageError] = useState(false); - const [isVisible, setIsVisible] = useState(false); - - const handleImageError = () => setImageError(true); - - const handleMouseEnter = () => setIsVisible(true); - - const handleMouseLeave = () => setIsVisible(false); - - return ( -
- {children} - - {isVisible && ( -
- - -
-
- {!imageError && fan.avatar ? ( - // eslint-disable-next-line @next/next/no-img-element - {fan.username - ) : ( -
- -
- )} -
-
-
-

- {fan.username || "Unknown User"} -

-
- - {fan.bio && ( -

- {fan.bio} -

- )} - - {/* Follower Stats */} -
- {fan.followerCount !== null && ( - - - {fan.followerCount.toLocaleString()} - {" "} - followers - - )} - {fan.followingCount !== null && ( - - - {fan.followingCount.toLocaleString()} - {" "} - following - - )} -
- - {fan.region && ( -

- 📍 {fan.region} -

- )} -
-
-
-
-
- )} -
- ); -}; - -export default FanProfileHover; diff --git a/components/Segments/NoSegmentsFound.tsx b/components/Segments/NoSegmentsFound.tsx deleted file mode 100644 index bd3398ea5..000000000 --- a/components/Segments/NoSegmentsFound.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from "react"; -import { Button } from "@/components/ui/button"; -import { SpinnerIcon } from "@/components/VercelChat/icons"; -import { useCreateSegments } from "@/hooks/useCreateSegments"; -import { Icons } from "@/components/Icon/resolver"; -import { useArtistSocials } from "@/hooks/useArtistSocials"; - -interface NoSegmentsFoundProps { - refetch?: () => void; -} - -const NoSegmentsFound = ({ refetch }: NoSegmentsFoundProps) => { - const { hasInstagram } = useArtistSocials(); - const { loading, createSegments } = useCreateSegments(); - - return ( -
-
No fan groups found for this artist.
- -
    -
  • - {hasInstagram ? : } - - {hasInstagram ? "Instagram Connected" : "Missing Instagram"} - -
  • - {[ - "Missing posts", - "Missing post comments", - "Missing fans", - "Missing segments", - ].map((item) => ( -
  • - - {item} -
  • - ))} -
-
- ); -}; - -export default NoSegmentsFound; diff --git a/components/Segments/SegmentButton.tsx b/components/Segments/SegmentButton.tsx deleted file mode 100644 index 0f78a4446..000000000 --- a/components/Segments/SegmentButton.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Card } from "@/components/ui/card"; -import { Users } from "lucide-react"; -import { Segment } from "@/lib/supabase/getArtistSegments"; -import SegmentFanCircles from "./SegmentFanCircles"; - -interface SegmentButtonProps { - segment: Segment; -} - -const SegmentButton = ({ segment }: SegmentButtonProps) => { - const fansWithAvatars = segment.fans?.filter((fan) => fan.avatar) || []; - - return ( - -
- {/* Title */} -

- {segment.name} -

- - {/* Fan Avatars and Count */} -
- {fansWithAvatars.length > 0 ? ( - - ) : ( -
-
- -
-
- )} - {segment.size} Fans -
-
-
- ); -}; - -export default SegmentButton; diff --git a/components/Segments/SegmentFanCircles.tsx b/components/Segments/SegmentFanCircles.tsx deleted file mode 100644 index 84bd65ea0..000000000 --- a/components/Segments/SegmentFanCircles.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Tables } from "@/types/database.types"; -import { User } from "lucide-react"; -import { useState } from "react"; -import Image from "next/image"; -import FanProfileHover from "./FanProfileHover"; -import Link from "next/link"; - -type Social = Tables<"socials">; - -interface SegmentFanCirclesProps { - fans: Social[]; - maxVisible?: number; - totalCount?: number; -} - -const SegmentFanCircles = ({ - fans, - maxVisible = 4, - totalCount -}: SegmentFanCirclesProps) => { - const displayedFans = fans.slice(0, maxVisible); - const remainingCount = totalCount ? totalCount - maxVisible : fans.length - maxVisible; - const showCounter = remainingCount > 0; - const [imageErrors, setImageErrors] = useState>(new Set()); - - const handleImageError = (fanId: string) => { - setImageErrors(prev => new Set(prev).add(fanId)); - }; - - return ( -
- {displayedFans.map((fan, index) => ( - -
- e.stopPropagation()} - className="block" - > - {!imageErrors.has(fan.id) && fan.avatar ? ( - // eslint-disable-next-line @next/next/no-img-element - {fan.username handleImageError(fan.id)} - /> - ) : ( -
- -
- )} - - {fan.profile_url.includes("tiktok.com") && ( -
- TikTok -
- )} - {fan.profile_url.includes("instagram.com") && ( -
- Instagram -
- )} -
-
- ))} - {showCounter && ( -
- +{remainingCount > 99 ? '99' : remainingCount} -
- )} -
- ); -}; - -export default SegmentFanCircles; diff --git a/components/Segments/Segments.tsx b/components/Segments/Segments.tsx deleted file mode 100644 index 661f164f4..000000000 --- a/components/Segments/Segments.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { type Segment } from "@/lib/supabase/getArtistSegments"; -import SegmentButton from "./SegmentButton"; - -interface SegmentsProps { - segments: Segment[]; -} - -const Segments = ({ segments }: SegmentsProps) => { - const sortedSegments = [...segments].sort((a, b) => b.size - a.size); - - return ( -
- {sortedSegments.map((segment) => ( - - ))} -
- ); -}; - -export default Segments; diff --git a/components/Segments/SegmentsSkeleton.tsx b/components/Segments/SegmentsSkeleton.tsx deleted file mode 100644 index 6402f326b..000000000 --- a/components/Segments/SegmentsSkeleton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Skeleton } from "../ui/skeleton"; -import { Card, CardContent, CardHeader } from "@/components/ui/card"; - -const SegmentsSkeleton = () => { - return ( -
- {Array.from({ length: 8 }).map((_, index) => ( - - - - - -
- - - -
-
- -
-
-
- ))} -
- ); -}; - -export default SegmentsSkeleton; diff --git a/components/Segments/SegmentsWrapper.tsx b/components/Segments/SegmentsWrapper.tsx deleted file mode 100644 index c4d563550..000000000 --- a/components/Segments/SegmentsWrapper.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Segments from "./Segments"; -import SegmentsSkeleton from "./SegmentsSkeleton"; -import NoSegmentsFound from "./NoSegmentsFound"; -import { useArtistProvider } from "@/providers/ArtistProvider"; -import { useArtistSegments } from "@/hooks/useArtistSegments"; - -const SegmentsWrapper = () => { - const { selectedArtist } = useArtistProvider(); - const { - data: segments, - isLoading, - error, - refetch, - } = useArtistSegments(selectedArtist?.account_id); - - if (!selectedArtist || isLoading) { - return ; - } - - if (error) { - return ( -
- Failed to load segments -
- ); - } - - if (!segments?.length) return ; - - return ; -}; - -export default SegmentsWrapper; diff --git a/components/SideMenu/SideMenu.tsx b/components/SideMenu/SideMenu.tsx index 9b4956ab8..5a1adbf14 100644 --- a/components/SideMenu/SideMenu.tsx +++ b/components/SideMenu/SideMenu.tsx @@ -10,7 +10,6 @@ import { v4 as uuidV4 } from "uuid"; import { useArtistProvider } from "@/providers/ArtistProvider"; import { PointerIcon } from "lucide-react"; import { Button } from "../ui/button"; -import FanGroupNavItem from "../Sidebar/FanGroupNavItem"; import AgentsNavItem from "../Sidebar/AgentsNavItem"; import { usePathname } from "next/navigation"; import TasksNavItem from "../Sidebar/TasksNavItem"; @@ -32,7 +31,6 @@ const SideMenu = ({ const hasArtists = sorted.length > 0; const isArtistSelected = !!selectedArtist; const isAgents = pathname.includes("/agents"); - const isSegments = pathname.includes("/segments"); const isTasks = pathname.includes("/tasks"); const isFiles = pathname.includes("/files"); @@ -95,10 +93,6 @@ const SideMenu = ({ onClick={() => goToItem("agents")} /> goToItem("tasks")} /> - goToItem("segments")} - /> goToItem("files")} /> diff --git a/components/Sidebar/FanGroupNavItem.tsx b/components/Sidebar/FanGroupNavItem.tsx deleted file mode 100644 index 5101c0277..000000000 --- a/components/Sidebar/FanGroupNavItem.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useArtistProvider } from "@/providers/ArtistProvider"; -import { useArtistSegments } from "@/hooks/useArtistSegments"; -import NavButton from "./NavButton"; - -const FanGroupNavItem = ({ - isActive, - isExpanded, - onClick, -}: { - isActive: boolean; - isExpanded?: boolean; - onClick: () => void; -}) => { - const { selectedArtist } = useArtistProvider(); - const { data: segments, isLoading } = useArtistSegments(selectedArtist?.account_id); - - const shouldRender = !!(selectedArtist && !isLoading && segments && segments.length > 0); - - return ( - - ); -}; - -export default FanGroupNavItem; diff --git a/components/Sidebar/Menu.tsx b/components/Sidebar/Menu.tsx index a5d6cca39..a1e6f9354 100644 --- a/components/Sidebar/Menu.tsx +++ b/components/Sidebar/Menu.tsx @@ -22,7 +22,6 @@ const Menu = ({ isExpanded, isPinned = false, onTogglePin }: MenuProps) => { const pathname = usePathname(); const { email, isPrepared } = useUserProvider(); const isAgents = pathname.includes("/agents"); - const isSegments = pathname.includes("/segments"); const isTasks = pathname.includes("/tasks"); const isFiles = pathname.includes("/files"); @@ -47,7 +46,6 @@ const Menu = ({ isExpanded, isPinned = false, onTogglePin }: MenuProps) => { isExpanded={isExpanded} isAgents={isAgents} isTasks={isTasks} - isSegments={isSegments} isFiles={isFiles} onNavigate={goToItem} /> diff --git a/components/Sidebar/MiniMenu.tsx b/components/Sidebar/MiniMenu.tsx index ce551d7be..9610b8f77 100644 --- a/components/Sidebar/MiniMenu.tsx +++ b/components/Sidebar/MiniMenu.tsx @@ -2,8 +2,6 @@ import { useUserProvider } from "@/providers/UserProvder"; import { useRouter } from "next/navigation"; import { ArrowRightFromLine } from "lucide-react"; import Icon from "../Icon"; -import { useArtistProvider } from "@/providers/ArtistProvider"; -import { useArtistSegments } from "@/hooks/useArtistSegments"; const MiniMenu = ({ toggleMenuExpanded, @@ -12,15 +10,11 @@ const MiniMenu = ({ }) => { const { push } = useRouter(); const { isPrepared } = useUserProvider(); - const { selectedArtist } = useArtistProvider(); - const { data: segments, isLoading } = useArtistSegments(selectedArtist?.account_id); - + const goToItem = (link?: string) => { if (isPrepared()) push(`/${link || ""}`); }; - const showSegments = selectedArtist && !isLoading && segments && segments.length > 0; - return (
@@ -41,15 +35,6 @@ const MiniMenu = ({ > - {showSegments && ( - - )}
); - } else if (toolName === "get_segment_fans") { - return ( -
- -
- ); } else if (toolName === "get_youtube_channels") { return (
@@ -418,12 +409,6 @@ export function getToolResultComponent(part: ToolUIPart | DynamicToolUIPart) {
); - } else if (toolName === "get_segment_fans") { - return ( -
- -
- ); } else if (toolName === "youtube_login") { return (
diff --git a/components/VercelChat/tools/segment-fans/EmptyState.tsx b/components/VercelChat/tools/segment-fans/EmptyState.tsx deleted file mode 100644 index 7204c6b78..000000000 --- a/components/VercelChat/tools/segment-fans/EmptyState.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import { motion } from "framer-motion"; -import { fadeInVariants } from "./animations"; - -/** - * Component to display empty state when no fans are found - */ -const EmptyState: React.FC = () => { - return ( - - No fans found in this segment. - - ); -}; - -export default EmptyState; \ No newline at end of file diff --git a/components/VercelChat/tools/segment-fans/ErrorState.tsx b/components/VercelChat/tools/segment-fans/ErrorState.tsx deleted file mode 100644 index 37a2aa3af..000000000 --- a/components/VercelChat/tools/segment-fans/ErrorState.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import { motion } from "framer-motion"; -import { fadeInVariants } from "./animations"; - -interface ErrorStateProps { - message?: string; -} - -/** - * Component to display error state for segment fans - */ -const ErrorState: React.FC = ({ message }) => { - return ( - - Error: {message || "Unknown error"} - - ); -}; - -export default ErrorState; \ No newline at end of file diff --git a/components/VercelChat/tools/segment-fans/FanCard.tsx b/components/VercelChat/tools/segment-fans/FanCard.tsx deleted file mode 100644 index 7419e826c..000000000 --- a/components/VercelChat/tools/segment-fans/FanCard.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from "react"; -import { motion } from "framer-motion"; -import { Fan } from "@/types/fans"; -import formatFollowerCount from "@/lib/utils/formatFollowerCount"; - -interface FanCardProps { - fan: Fan; - index: number; -} - -// Animation variant for items -const itemVariants = { - hidden: { opacity: 0, y: 20 }, - show: { - opacity: 1, - y: 0, - transition: { - type: "spring", - stiffness: 300, - damping: 24 - } - } -}; - -const FanCard: React.FC = ({ fan, index }) => { - return ( - -
- {fan.username} -
- -
-
-

{fan.username}

- - {fan.region} - -
- -

{fan.bio}

- -
- {formatFollowerCount(fan.follower_count)} • {formatFollowerCount(fan.following_count)} -
-
-
- ); -}; - -export default FanCard; \ No newline at end of file diff --git a/components/VercelChat/tools/segment-fans/GetSegmentFansResult.tsx b/components/VercelChat/tools/segment-fans/GetSegmentFansResult.tsx deleted file mode 100644 index 7d3a4fbee..000000000 --- a/components/VercelChat/tools/segment-fans/GetSegmentFansResult.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react"; -import { motion } from "framer-motion"; -import { SegmentFansResult } from "@/types/fans"; -import FanCard from "./FanCard"; -import ErrorState from "./ErrorState"; -import EmptyState from "./EmptyState"; -import SegmentHeader from "./SegmentHeader"; -import { containerVariants } from "./animations"; - -/** - * Component to display segment fans data - */ -const GetSegmentFansResult: React.FC<{ result: SegmentFansResult }> = ({ result }) => { - // Handle error state - if (!result.success || result.status === "error") { - return ; - } - - // Handle empty state - if (!result.fans || result.fans.length === 0) { - return ; - } - - const segmentName = result.fans[0]?.segment_name || "Segment"; - const { total_count } = result.pagination; - - return ( - - {/* Header with segment name and total count */} - - - {/* Grid of fan cards */} - - {result.fans.map((fan, index) => ( - - ))} - - - ); -}; - -export default GetSegmentFansResult; \ No newline at end of file diff --git a/components/VercelChat/tools/segment-fans/GetSegmentFansResultSkeleton.tsx b/components/VercelChat/tools/segment-fans/GetSegmentFansResultSkeleton.tsx deleted file mode 100644 index 80918025c..000000000 --- a/components/VercelChat/tools/segment-fans/GetSegmentFansResultSkeleton.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import React from "react"; -import { motion } from "framer-motion"; - -/** - * Loading skeleton for segment fans data - * Matches the structure defined in types/fans.ts - */ -const GetSegmentFansResultSkeleton = ({ count = 9 }: { count?: number }) => { - // Animation variants for skeleton pulse effect - const pulseVariants = { - initial: { opacity: 0.6 }, - animate: { - opacity: 0.9, - transition: { - repeat: Infinity, - repeatType: "reverse" as const, - duration: 0.8 - } - } - }; - - // Animation variants for staggered appearance - const containerVariants = { - hidden: { opacity: 0 }, - show: { - opacity: 1, - transition: { - staggerChildren: 0.05, - delayChildren: 0.1 - } - } - }; - - const itemVariants = { - hidden: { opacity: 0, y: 20 }, - show: { - opacity: 1, - y: 0, - transition: { - type: "spring", - stiffness: 300, - damping: 24 - } - } - }; - - return ( - - {/* Header skeleton */} - - - - - - {/* Fan cards grid */} - - {/* Generate multiple skeleton cards */} - {Array.from({ length: count }).map((_, index) => ( - - {/* Avatar skeleton */} - - -
- {/* Username and region skeleton */} -
- - -
- - {/* Bio skeleton */} - - - {/* Stats skeleton */} -
- - -
-
-
- ))} -
-
- ); -}; - -export default GetSegmentFansResultSkeleton; \ No newline at end of file diff --git a/components/VercelChat/tools/segment-fans/SegmentHeader.tsx b/components/VercelChat/tools/segment-fans/SegmentHeader.tsx deleted file mode 100644 index 1dea36677..000000000 --- a/components/VercelChat/tools/segment-fans/SegmentHeader.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; -import { motion } from "framer-motion"; -import { headerVariants } from "./animations"; - -interface SegmentHeaderProps { - segmentName: string; - totalCount: number; -} - -/** - * Header component showing segment name and total count - */ -const SegmentHeader: React.FC = ({ segmentName, totalCount }) => { - return ( - -
- - {segmentName || "Segment"} Fans - - - ({totalCount}) - -
-
- ); -}; - -export default SegmentHeader; \ No newline at end of file diff --git a/components/VercelChat/tools/segment-fans/animations.ts b/components/VercelChat/tools/segment-fans/animations.ts deleted file mode 100644 index e1ff9b662..000000000 --- a/components/VercelChat/tools/segment-fans/animations.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Container animations with staggered children -export const containerVariants = { - hidden: { opacity: 0 }, - show: { - opacity: 1, - transition: { - staggerChildren: 0.05, - delayChildren: 0.1 - } - } -}; - -// Header slide-in animation -export const headerVariants = { - hidden: { opacity: 0, y: -10 }, - show: { - opacity: 1, - y: 0, - transition: { duration: 0.3 } - } -}; - -// Simple fade + slide animation for error/empty states -export const fadeInVariants = { - initial: { opacity: 0, y: 10 }, - animate: { opacity: 1, y: 0 }, - transition: { duration: 0.3 } -}; \ No newline at end of file diff --git a/hooks/useArtistSegments.ts b/hooks/useArtistSegments.ts deleted file mode 100644 index 879cb902f..000000000 --- a/hooks/useArtistSegments.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { type Segment } from "@/lib/supabase/getArtistSegments"; -import { useQuery } from "@tanstack/react-query"; - -async function fetchSegments(artistId: string): Promise { - const response = await fetch(`/api/segments?artistId=${artistId}`); - if (!response.ok) { - throw new Error("Failed to fetch segments"); - } - const segments: Segment[] = await response.json(); - return segments.filter((s) => s.size > 0); -} - -export function useArtistSegments(artistId?: string) { - return useQuery({ - queryKey: ["segments", artistId], - queryFn: () => fetchSegments(artistId!), - enabled: !!artistId, - staleTime: 1000 * 60 * 5, // 5 minutes - refetchOnWindowFocus: false, - }); -} diff --git a/hooks/useChatSegment.ts b/hooks/useChatSegment.ts deleted file mode 100644 index 4ecf2bc05..000000000 --- a/hooks/useChatSegment.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { usePrivy } from "@privy-io/react-auth"; -import { getChatSegment } from "@/lib/chats/getChatSegment"; - -interface RoomSegmentResponse { - segmentId: string | null; -} - -export const useChatSegment = (roomId?: string) => { - const { getAccessToken } = usePrivy(); - - return useQuery({ - queryKey: ["roomSegment", roomId], - queryFn: async (): Promise => { - if (!roomId) return { segmentId: null }; - - const accessToken = await getAccessToken(); - if (!accessToken) throw new Error("No access token"); - - const data = await getChatSegment(roomId, accessToken); - return { segmentId: data.segment_id ?? null }; - }, - enabled: !!roomId, - staleTime: 1000 * 60 * 5, - retry: 2, - }); -}; diff --git a/hooks/useCreateSegments.ts b/hooks/useCreateSegments.ts deleted file mode 100644 index 80ebb5d33..000000000 --- a/hooks/useCreateSegments.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useState } from "react"; -import { toast } from "react-toastify"; -import { useArtistProvider } from "@/providers/ArtistProvider"; - -export function useCreateSegments() { - const { selectedArtist } = useArtistProvider(); - const artist_account_id = selectedArtist?.account_id; - const [loading, setLoading] = useState(false); - - const createSegments = async (onSuccess?: () => void) => { - if (!artist_account_id) return; - setLoading(true); - try { - const response = await fetch("/api/segments/create", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - artist_account_id, - prompt: "Segment my fans to help me fund my next project.", - }), - }); - const data = await response.json(); - if (!response.ok || data.error) { - throw new Error(data.error || "Failed to generate segments"); - } - toast.success("Segments generated successfully!"); - if (onSuccess) onSuccess(); - } catch (error) { - toast.error( - error instanceof Error ? error.message : "Failed to generate segments" - ); - console.error(error); - } finally { - setLoading(false); - } - }; - - return { loading, createSegments }; -} diff --git a/hooks/useFansCSVExport.tsx b/hooks/useFansCSVExport.tsx deleted file mode 100644 index 30998cf2e..000000000 --- a/hooks/useFansCSVExport.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import extractMails from "@/lib/extractMail"; -import { mkConfig, generateCsv, download } from "export-to-csv"; - -const useFansCSVExport = () => { - const exportCSV = (fansSegments: any) => { - const segments = fansSegments.map((fanSegment: any) => ({ - ...fanSegment.socials, - segment_name: fanSegment.segment_name, - email: extractMails(fanSegment.socials.bio || ""), - })); - const csvConfig = mkConfig({ useKeysAsHeaders: true }); - const csv = generateCsv(csvConfig)(segments); - download(csvConfig)(csv); - }; - - return { - exportCSV, - }; -}; - -export default useFansCSVExport; diff --git a/lib/agent/getSegments.tsx b/lib/agent/getSegments.tsx deleted file mode 100644 index 981607066..000000000 --- a/lib/agent/getSegments.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { AGENT_API } from "../consts"; - -// eslint-disable-next-line -const getSegments = async (comments: any) => { - try { - const response = await fetch(`${AGENT_API}/api/get_segments`, { - method: "POST", - body: JSON.stringify(comments), - headers: { - "Content-Type": "application/json", - }, - }); - - const data = await response.json(); - - return data.segments_with_icons; - } catch (error) { - console.error(error); - return []; - } -}; - -export default getSegments; diff --git a/lib/chats/getChatSegment.ts b/lib/chats/getChatSegment.ts deleted file mode 100644 index 4336e5d80..000000000 --- a/lib/chats/getChatSegment.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; - -interface ChatSegmentResponse { - status: string; - room_id: string; - segment_id: string | null; - segment_exists: boolean; -} - -/** - * Fetches the segment associated with a chat room. - */ -export async function getChatSegment( - roomId: string, - accessToken: string, -): Promise { - const url = getClientApiBaseUrl(); - - const response = await fetch(`${url}/api/chats/${encodeURIComponent(roomId)}/segment`, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - - if (!response.ok) { - const error = await response.json(); - throw new Error(error.error || "Failed to fetch segment ID"); - } - - return response.json(); -} diff --git a/lib/getFanSegments.tsx b/lib/getFanSegments.tsx deleted file mode 100644 index 946127f63..000000000 --- a/lib/getFanSegments.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { AGENT_API } from "./consts"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const getFanSegments = async (segmentNames: any, commentIds: any) => { - try { - const response = await fetch(`${AGENT_API}/api/get_fans_segments`, { - method: "POST", - body: JSON.stringify({ - segmentNames, - commentIds, - }), - headers: { - "Content-Type": "application/json", - }, - }); - - if (!response.ok) return { error: true }; - const data = await response.json(); - return data.data; - } catch (error) { - console.error(error); - return { error }; - } -}; - -export default getFanSegments; diff --git a/lib/getFunnelReport.tsx b/lib/getFunnelReport.tsx deleted file mode 100644 index e89288ed4..000000000 --- a/lib/getFunnelReport.tsx +++ /dev/null @@ -1,11 +0,0 @@ -const getFunnelReport = async (referenceId: string) => { - try { - const response = await fetch(`/api/funnel_report?id=${referenceId}`); - const data = await response.json(); - return data.data; - } catch (error) { - return { error }; - } -}; - -export default getFunnelReport; diff --git a/lib/segments/createSegmentResponses.ts b/lib/segments/createSegmentResponses.ts deleted file mode 100644 index 0e270400d..000000000 --- a/lib/segments/createSegmentResponses.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Tables } from "@/types/database.types"; -import { GenerateArrayResult } from "../ai/generateArray"; - -interface CreateArtistSegmentsSuccessData { - supabase_segments: Tables<"segments">[]; - supabase_artist_segments: Tables<"artist_segments">[]; - segments: GenerateArrayResult[]; - supabase_fan_segments: Tables<"fan_segments">[]; -} - -export const successResponse = ( - message: string, - data: CreateArtistSegmentsSuccessData, - count: number -) => ({ - success: true, - status: "success", - message, - data, - count, -}); - -export const errorResponse = (message: string) => ({ - success: false, - status: "error", - message, - data: [], - count: 0, -}); diff --git a/lib/segments/createSegments.ts b/lib/segments/createSegments.ts deleted file mode 100644 index f53ecef2a..000000000 --- a/lib/segments/createSegments.ts +++ /dev/null @@ -1,134 +0,0 @@ -import getAccountSocials from "../supabase/account_socials/getAccountSocials"; -import { selectSocialFans } from "../supabase/social_fans/selectSocialFans"; -import { generateSegments } from "./generateSegments"; -import { insertSegments } from "../supabase/segments/insertSegments"; -import { deleteSegments } from "../supabase/segments/deleteSegments"; -import { insertArtistSegments } from "../supabase/artist_segments/insertArtistSegments"; -import { insertFanSegments } from "../supabase/fan_segments/insertFanSegments"; -import { Tables } from "@/types/database.types"; -import { successResponse, errorResponse } from "./createSegmentResponses"; -import { GenerateArrayResult } from "../ai/generateArray"; -import { getFanSegmentsToInsert } from "./getFanSegmentsToInsert"; -import { getArtistById } from "../supabase/artist/getArtistById"; - -interface CreateArtistSegmentsParams { - artist_account_id: string; - prompt: string; -} - -export const createSegments = async ({ - artist_account_id, - prompt, -}: CreateArtistSegmentsParams) => { - try { - // Get artist info for better error messages - const artistInfo = await getArtistById(artist_account_id); - const artistName = artistInfo?.name || "this artist"; - - // Step 1: Get all social IDs for the artist - const accountSocials = await getAccountSocials({ - accountId: artist_account_id, - }); - const socialIds = accountSocials.map( - (as: { social_id: string }) => as.social_id - ); - - if (socialIds.length === 0) { - return { - ...errorResponse("No social account found for this artist"), - feedback: `No Instagram accounts found for ${artistName}. To automatically set up Instagram accounts, please follow these steps:\n` + - `1. Call 'search_web' to search for "${artistName} Instagram handle"\n` + - "2. Call 'update_artist_socials' with the discovered Instagram profile URL\n" + - "3. Call 'create_segments' again to retry segment creation\n" + - "Instagram is required for fan segmentation as it's the primary social platform configured for segments." - } - } - - // Step 2: Get all fans for the artist - const fans = await selectSocialFans({ - social_ids: socialIds, - orderBy: "latest_engagement", - orderDirection: "desc", - }); - - if (fans.length === 0) { - return { - ...errorResponse("No fans found for this artist"), - feedback: `No social_fans records found for ${artistName}. Before creating segments, you need social_fans data. Follow these steps:\n` + - "1. Call 'scrape_instagram_profile' with the artist's Instagram handles to get posts\n" + - "2. Call 'scrape_instagram_comments' with Instagram post URLs to scrape comment data\n" + - "3. Wait for the scraping jobs to complete and process into social_fans records\n" + - "4. Call 'create_segments' again once social_fans records are populated\n" + - "Note: Scraping jobs may take several minutes to complete." - } - } - - // Step 3: Generate segment names using AI - const segments = await generateSegments({ fans, prompt }); - - if (segments.length === 0) { - return errorResponse("Failed to generate segment names"); - } - - // Step 4: Delete existing segments for the artist - await deleteSegments(artist_account_id); - - // Step 5: Insert segments into the database - const segmentsToInsert = segments.map((segment: GenerateArrayResult) => ({ - name: segment.segmentName, - updated_at: new Date().toISOString(), - })); - - const insertedSegments = await insertSegments(segmentsToInsert); - - // Step 6: Associate segments with the artist - const artistSegmentsToInsert = insertedSegments.map( - (segment: Tables<"segments">) => ({ - artist_account_id, - segment_id: segment.id, - updated_at: new Date().toISOString(), - }) - ); - - const insertedArtistSegments = await insertArtistSegments( - artistSegmentsToInsert - ); - - // Step 7: Associate fans with the new segments - // Build a set of valid IDs from the fetched fan list - const validFanIds = new Set(fans.map(f => f.fan_social_id)); - - const fanSegmentsToInsert = getFanSegmentsToInsert( - segments, - insertedSegments - ).filter(fs => { - const ok = validFanIds.has(fs.fan_social_id); - if (!ok) console.warn(`Skipping unknown fan_social_id: ${fs.fan_social_id}`); - return ok; - }); - - if (fanSegmentsToInsert.length === 0) { - return errorResponse("No valid fan IDs matched any segment."); - } - - const insertedFanSegments = await insertFanSegments(fanSegmentsToInsert); - - return successResponse( - `Successfully created ${segments.length} segments for artist`, - { - supabase_segments: insertedSegments, - supabase_artist_segments: insertedArtistSegments, - supabase_fan_segments: insertedFanSegments, - segments, - }, - segments.length - ); - } catch (error) { - console.error("Error creating artist segments:", error); - return errorResponse( - error instanceof Error - ? error.message - : "Failed to create artist segments" - ); - } -}; diff --git a/lib/segments/generateSegments.ts b/lib/segments/generateSegments.ts deleted file mode 100644 index cf1db285a..000000000 --- a/lib/segments/generateSegments.ts +++ /dev/null @@ -1,29 +0,0 @@ -import generateArray from "@/lib/ai/generateArray"; -import { SEGMENT_SYSTEM_PROMPT } from "../consts"; -import getAnalysisPrompt from "./getAnalysisPrompt"; -import { SocialFanWithDetails } from "../supabase/social_fans/selectSocialFans"; -import { GenerateArrayResult } from "../ai/generateArray"; - -export interface GenerateSegmentsParams { - fans: SocialFanWithDetails[]; - prompt: string; -} - -export const generateSegments = async ({ - fans, - prompt, -}: GenerateSegmentsParams): Promise => { - try { - const analysisPrompt = getAnalysisPrompt({ fans, prompt }); - - const result = await generateArray({ - system: SEGMENT_SYSTEM_PROMPT, - prompt: analysisPrompt, - }); - - return result; - } catch (error) { - console.error("Error generating segments:", error); - throw new Error("Failed to generate segments from fan data"); - } -}; diff --git a/lib/segments/getAnalysisPrompt.ts b/lib/segments/getAnalysisPrompt.ts deleted file mode 100644 index 2290b08a7..000000000 --- a/lib/segments/getAnalysisPrompt.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { SEGMENT_FAN_SOCIAL_ID_PROMPT } from "../consts"; -import { GenerateSegmentsParams } from "./generateSegments"; - -const getAnalysisPrompt = ({ fans, prompt }: GenerateSegmentsParams) => { - const fanCount = fans.length; - const fanData = fans.map((fan) => { - const obj = { - fan_social_id: fan.fan_social_id, - username: fan.fan_social.username, - bio: fan.fan_social.bio, - followerCount: fan.fan_social.followerCount, - followingCount: fan.fan_social.followingCount, - comment: fan.latest_engagement_comment?.comment || null, - }; - // Remove keys with null values - return Object.fromEntries( - Object.entries(obj).filter(([, value]) => value !== null) - ); - }); - - const maxFans = 111; - const slicedFanData = fanData.slice(0, maxFans); - - const fanDataString = JSON.stringify(slicedFanData, null, 2); - const analysisPrompt = `Analyze the following fan data and generate segment names based on the provided prompt. - - Fan Data Summary: - - Total fans: ${fanCount} - - Fan data: ${fanDataString} - - Artist's specific prompt: ${prompt} - - Generate segment names that align with the artist's requirements and the fan data characteristics. - ${SEGMENT_FAN_SOCIAL_ID_PROMPT}`; - return analysisPrompt; -}; - -export default getAnalysisPrompt; diff --git a/lib/segments/getFanSegmentsToInsert.ts b/lib/segments/getFanSegmentsToInsert.ts deleted file mode 100644 index 19e9d7cf2..000000000 --- a/lib/segments/getFanSegmentsToInsert.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { GenerateArrayResult } from "../ai/generateArray"; -import { Tables } from "@/types/database.types"; - -/** - * Returns an array of fan-segment associations to insert, based on the AI-generated segments and the inserted segment records. - * Each fan is only associated with the segment(s) they are assigned to in the segments array. - */ -export function getFanSegmentsToInsert( - segments: GenerateArrayResult[], - insertedSegments: Tables<"segments">[] -) { - const segmentNameToId = new Map( - insertedSegments.map((seg) => [seg.name, seg.id]) - ); - - return segments.flatMap((segment: GenerateArrayResult) => { - const segmentId = segmentNameToId.get(segment.segmentName); - if (!segmentId || !segment.fans) return []; - return segment.fans.map((fan_social_id: string) => ({ - fan_social_id, - segment_id: segmentId, - updated_at: new Date().toISOString(), - })); - }); -} diff --git a/lib/supabase/artist_segments/insertArtistSegments.ts b/lib/supabase/artist_segments/insertArtistSegments.ts deleted file mode 100644 index 53bd53400..000000000 --- a/lib/supabase/artist_segments/insertArtistSegments.ts +++ /dev/null @@ -1,21 +0,0 @@ -import serverClient from "../serverClient"; -import { Tables } from "@/types/database.types"; - -type ArtistSegment = Tables<"artist_segments">; -type ArtistSegmentInsert = Partial; - -export const insertArtistSegments = async ( - artistSegments: ArtistSegmentInsert[] -): Promise => { - const { data, error } = await serverClient - .from("artist_segments") - .insert(artistSegments) - .select(); - - if (error) { - console.error("Error inserting artist segments:", error); - throw error; - } - - return data; -}; \ No newline at end of file diff --git a/lib/supabase/fan_segments/insertFanSegments.ts b/lib/supabase/fan_segments/insertFanSegments.ts deleted file mode 100644 index 7ac0497b0..000000000 --- a/lib/supabase/fan_segments/insertFanSegments.ts +++ /dev/null @@ -1,21 +0,0 @@ -import serverClient from "../serverClient"; -import { Tables } from "@/types/database.types"; - -type FanSegment = Tables<"fan_segments">; -type FanSegmentInsert = Partial; - -export const insertFanSegments = async ( - fanSegments: FanSegmentInsert[] -): Promise => { - const { data, error } = await serverClient - .from("fan_segments") - .insert(fanSegments) - .select(); - - if (error) { - console.error("Error inserting fan segments:", error); - throw error; - } - - return data; -}; diff --git a/lib/supabase/fan_segments/selectFanSegments.ts b/lib/supabase/fan_segments/selectFanSegments.ts deleted file mode 100644 index 78ffddc7f..000000000 --- a/lib/supabase/fan_segments/selectFanSegments.ts +++ /dev/null @@ -1,36 +0,0 @@ -import serverClient from "../serverClient"; -import { Tables } from "@/types/database.types"; - -type Social = Tables<"socials">; - -interface SelectFanSegmentsParams { - segment_id: string; -} - -export const selectFanSegments = async ( - params: SelectFanSegmentsParams -): Promise => { - try { - const { data, error } = await serverClient - .from("fan_segments") - .select(`socials!fan_segments_fan_social_id_fkey (*)`) - .eq("segment_id", params.segment_id); - - if (error) { - console.error("Error fetching fan segments with socials:", error); - throw error; - } - - // Extract and flatten the socials data from the joined result - const socialsData = - data - ?.flatMap((item: { socials: Social[] }) => item.socials || []) - .filter((social): social is Social => social !== null) - .sort((a, b) => (b.followerCount || 0) - (a.followerCount || 0)) || []; - - return socialsData; - } catch (error) { - console.error("Error in selectFanSegments:", error); - throw error; - } -}; diff --git a/lib/supabase/getArtistSegmentNames.ts b/lib/supabase/getArtistSegmentNames.ts deleted file mode 100644 index e8f0e7537..000000000 --- a/lib/supabase/getArtistSegmentNames.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ArtistSegment } from "./getArtistSegments"; -import supabase from "./serverClient"; - -/** - * Get all segments associated with an artist - */ -export async function getArtistSegmentNames( - artistId: string -): Promise { - try { - const { data: segments, error: segmentsError } = await supabase - .from("artist_segments") - .select( - ` - *, - segment:segments(*) - ` - ) - .eq("artist_account_id", artistId); - - if (segmentsError) { - console.error("Error fetching segments:", segmentsError); - return []; - } - - if (!segments?.length) { - return []; - } - - return segments; - } catch (error) { - console.error("Error in getArtistSegmentNames:", error); - return []; - } -} diff --git a/lib/supabase/getArtistSegments.ts b/lib/supabase/getArtistSegments.ts deleted file mode 100644 index 63d12a207..000000000 --- a/lib/supabase/getArtistSegments.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { getArtistSegmentNames } from "./getArtistSegmentNames"; -import { getSegmentCounts } from "./getSegmentCounts"; -import { selectFanSegments } from "./fan_segments/selectFanSegments"; -import { Tables } from "@/types/database.types"; - -type Social = Tables<"socials">; - -export interface Segment { - id: string; - name: string; - size: number; - icon?: string; - fans?: Social[]; -} - -export interface ArtistSegment { - id: string; - segment_id: string; - artist_account_id: string; - created_at: string; - segment: { - id: string; - name: string; - }; -} - -export interface SegmentCount { - segment_id: string; - count: number; -} - -export interface FanSegment { - id: string; - artist_segment_id: string; - fan_social_id: string; - created_at: string; -} - -export interface SegmentWithCount extends ArtistSegment { - fan_count: number; -} - -/** - * Get all segments with their fan counts for an artist - */ -export async function getArtistSegments(artistId: string): Promise { - const segments = await getArtistSegmentNames(artistId); - if (!segments.length) return []; - - const segmentIds = segments.map((s) => s.segment_id); - const counts = await getSegmentCounts(segmentIds); - - const countMap = new Map(counts.map((c) => [c.segment_id, c.count])); - - // Get fans for each segment (limit to 5 for social proof) - const segmentsWithFans = await Promise.all( - segments.map(async (segment) => { - const fans = await selectFanSegments({ segment_id: segment.segment_id }); - return { - id: segment.segment_id, - name: segment.segment.name, - size: countMap.get(segment.segment_id) || 0, - icon: undefined, - fans, - }; - }) - ); - - return segmentsWithFans; -} diff --git a/lib/supabase/getSegmentCounts.ts b/lib/supabase/getSegmentCounts.ts deleted file mode 100644 index 83075d661..000000000 --- a/lib/supabase/getSegmentCounts.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { SegmentCount } from "./getArtistSegments"; -import supabase from "./serverClient"; - -/** - * Get fan counts for a list of segment IDs - */ -export async function getSegmentCounts( - segmentIds: string[] -): Promise { - try { - const { data: fanSegments, error: countsError } = await supabase - .from("fan_segments") - .select("segment_id") - .in("segment_id", segmentIds); - - if (countsError) { - console.error("Error fetching fan counts:", countsError); - return []; - } - - const counts = segmentIds.map((segmentId) => ({ - segment_id: segmentId, - count: - fanSegments?.filter((fs) => fs.segment_id === segmentId).length || 0, - })); - - return counts; - } catch (error) { - console.error("Error in getSegmentCounts:", error); - return []; - } -} diff --git a/lib/supabase/segments/deleteSegments.ts b/lib/supabase/segments/deleteSegments.ts deleted file mode 100644 index b5706cb1d..000000000 --- a/lib/supabase/segments/deleteSegments.ts +++ /dev/null @@ -1,46 +0,0 @@ -import serverClient from "../serverClient"; -import { Tables } from "@/types/database.types"; - -type Segment = Tables<"segments">; - -export const deleteSegments = async ( - artist_account_id: string -): Promise => { - // First, get all segment_ids associated with the artist from artist_segments table - const { data: artistSegments, error: artistSegmentsError } = await serverClient - .from("artist_segments") - .select("segment_id") - .eq("artist_account_id", artist_account_id); - - if (artistSegmentsError) { - console.error("Error fetching artist segments:", artistSegmentsError); - return []; - } - - if (!artistSegments || artistSegments.length === 0) { - // No segments to delete - return []; - } - - // Extract segment_ids - const segmentIds = artistSegments.map((as: { segment_id: string }) => as.segment_id); - - // Delete the segments from the segments table - const { data, error } = await serverClient - .from("segments") - .delete() - .in("id", segmentIds) - .select(); - - if (error) { - console.error("Error deleting segments:", error); - return []; - } - - if (!data || data.length === 0) { - console.warn(`No segments found with ids: ${segmentIds.join(", ")}`); - return []; - } - - return data; -}; \ No newline at end of file diff --git a/lib/supabase/segments/insertSegments.ts b/lib/supabase/segments/insertSegments.ts deleted file mode 100644 index 927800104..000000000 --- a/lib/supabase/segments/insertSegments.ts +++ /dev/null @@ -1,21 +0,0 @@ -import serverClient from "../serverClient"; -import { Tables } from "@/types/database.types"; - -type Segment = Tables<"segments">; -type SegmentInsert = Partial; - -export const insertSegments = async ( - segments: SegmentInsert[] -): Promise => { - const { data, error } = await serverClient - .from("segments") - .insert(segments) - .select(); - - if (error) { - console.error("Error inserting segments:", error); - throw error; - } - - return data; -}; \ No newline at end of file diff --git a/lib/tools/createSegments.ts b/lib/tools/createSegments.ts deleted file mode 100644 index d1a21bc6d..000000000 --- a/lib/tools/createSegments.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from "zod"; -import { tool } from "ai"; -import { createSegments } from "@/lib/segments/createSegments"; - -const schema = z.object({ - artist_account_id: z - .string() - .min( - 1, - "artist_account_id is required and should be pulled from the system prompt. Never request this from the user." - ), - prompt: z - .string() - .min( - 1, - "Prompt is required and should be generated by the system if not provided. Never ask the user for this parameter; if not provided, make it up, but do not ever ask the user for it." - ), -}); - -const createSegmentsTool = tool({ - description: - "Create segments by analyzing fan data and generating segment names. This tool fetches all fans for an artist, generates segment names based on the provided prompt, and saves the segments to the database. If required information is missing (social accounts or fan data), the tool will provide step-by-step instructions to gather the missing prerequisites using other available tools.", - inputSchema: schema, - execute: async ({ artist_account_id, prompt }) => { - return await createSegments({ artist_account_id, prompt }); - }, -}); - -export default createSegmentsTool; diff --git a/lib/tools/getArtistSegments.ts b/lib/tools/getArtistSegments.ts deleted file mode 100644 index 4b735db70..000000000 --- a/lib/tools/getArtistSegments.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; -import { z } from "zod"; -import { tool } from "ai"; - -// Response types -interface Segment { - id: string; - artist_account_id: string; - segment_id: string; - updated_at: string; - segment_name: string; - artist_name: string; -} - -interface SegmentResponse { - status: "success" | "error"; - segments: Segment[]; - pagination: { - total_count: number; - page: number; - limit: number; - total_pages: number; - }; -} - -// Zod schema for parameter validation -const schema = z.object({ - artist_account_id: z.string().min(1, "Artist account ID is required"), - page: z.number().min(1).optional().default(1), - limit: z.number().min(1).max(100).optional().default(20), -}); - -const getArtistSegments = tool({ - description: - "Retrieve all segments associated with an artist. This endpoint should be called before using the Segment Fans endpoint to obtain the necessary segment IDs.", - inputSchema: schema, - execute: async ({ artist_account_id, page, limit }) => { - try { - // Construct URL with query parameters - const url = new URL(`${getClientApiBaseUrl()}/api/artist/segments`); - url.searchParams.append("artist_account_id", artist_account_id); - if (page) url.searchParams.append("page", page.toString()); - if (limit) url.searchParams.append("limit", limit.toString()); - - // Make the API request - const response = await fetch(url.toString(), { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - throw new Error(`API request failed with status ${response.status}`); - } - - const data = (await response.json()) as SegmentResponse; - - return { - success: true, - ...data, - }; - } catch (error) { - console.error("Error fetching artist segments:", error); - return { - success: false, - status: "error", - message: - error instanceof Error - ? error.message - : "Failed to fetch artist segments", - segments: [], - pagination: { - total_count: 0, - page: page || 1, - limit: limit || 20, - total_pages: 0, - }, - }; - } - }, -}); - -export default getArtistSegments; diff --git a/lib/tools/getMcpTools.ts b/lib/tools/getMcpTools.ts index 67722e565..fb895782d 100644 --- a/lib/tools/getMcpTools.ts +++ b/lib/tools/getMcpTools.ts @@ -1,6 +1,4 @@ import { ToolSet } from "ai"; -import getSegmentFans from "./getSegmentFans"; -import getArtistSegments from "./getArtistSegments"; import getSocialPosts from "./getSocialPosts"; import getPostComments from "./getPostComments"; import { webDeepResearch } from "./searchWeb"; @@ -15,7 +13,6 @@ import scrapeInstagramComments from "./scrapeInstagramComments"; import artistDeepResearch from "./artistDeepResearch"; import getVideoGameCampaignPlays from "./getVideoGameCampaignPlays"; import getSocialFans from "./getSocialFans"; -import createSegments from "./createSegments"; import createReleaseReport from "./createReleaseReport"; import filesTools from "./files"; import browserTools from "./browser"; @@ -23,9 +20,6 @@ import getCatalogSongs from "./catalogs/getCatalogSongs"; export function getMcpTools(): ToolSet { const tools = { - create_segments: createSegments, - get_artist_segments: getArtistSegments, - get_segment_fans: getSegmentFans, get_social_posts: getSocialPosts, get_post_comments: getPostComments, search_google_images: searchGoogleImages, diff --git a/lib/tools/getSegmentFans.ts b/lib/tools/getSegmentFans.ts deleted file mode 100644 index c8b49c2d2..000000000 --- a/lib/tools/getSegmentFans.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { z } from "zod"; -import { tool } from "ai"; - -import { FanResponse } from "@/types/fans"; - -// Zod schema for parameter validation -const schema = z.object({ - segment_id: z.string().min(1, "Segment ID is required"), - page: z.number().min(1).optional().default(1), - limit: z.number().min(1).max(100).optional().default(20), -}); - -const getSegmentFans = tool({ - description: - "Retrieve all social profiles from fans within a specific segment. This endpoint should be called after obtaining segment IDs from the Artist Segments endpoint.", - inputSchema: schema, - execute: async ({ segment_id, page, limit }) => { - try { - // Construct URL with query parameters - const url = new URL("https://api.recoupable.com/api/segment/fans"); - url.searchParams.append("segment_id", segment_id); - if (page) url.searchParams.append("page", page.toString()); - if (limit) url.searchParams.append("limit", limit.toString()); - - // Make the API request - const response = await fetch(url.toString(), { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - throw new Error(`API request failed with status ${response.status}`); - } - - const data = (await response.json()) as FanResponse; - - return { - success: true, - ...data, - }; - } catch (error) { - console.error("Error fetching segment fans:", error); - return { - success: false, - status: "error", - message: - error instanceof Error - ? error.message - : "Failed to fetch segment fans", - fans: [], - pagination: { - total_count: 0, - page: page || 1, - limit: limit || 20, - total_pages: 0, - }, - }; - } - }, -}); - -export default getSegmentFans; diff --git a/lib/utils/getToolsInfo.ts b/lib/utils/getToolsInfo.ts index e67925b95..0ab1d3ebb 100644 --- a/lib/utils/getToolsInfo.ts +++ b/lib/utils/getToolsInfo.ts @@ -7,8 +7,6 @@ function getToolInfo(toolName: string): { message: string } { } // Artist data tools else if ( - toolName === "get_artist_segments" || - toolName === "create_segments" || toolName === "get_artist_socials" || toolName === "create_new_artist" || toolName === "delete_artist" @@ -17,12 +15,6 @@ function getToolInfo(toolName: string): { message: string } { message: "Artist data processed", }; } - // Segment and fans tools - else if (toolName === "get_segment_fans") { - return { - message: "Fan data analyzed", - }; - } // Social media content tools else if ( toolName === "get_social_posts" || diff --git a/public/segment.svg b/public/segment.svg deleted file mode 100644 index 5d7e48300..000000000 --- a/public/segment.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/types/fans.ts b/types/fans.ts index e8142795f..7143b7019 100644 --- a/types/fans.ts +++ b/types/fans.ts @@ -25,43 +25,3 @@ export type FAN_TYPE = { email: string; timestamp: string | null; }; - -// Fan profile interface -export interface Fan { - id: string; - username: string; - avatar: string; - profile_url: string; - segment_id: string; - segment_name: string; - fan_social_id: string; - region: string; - bio: string; - follower_count: number; - following_count: number; - updated_at: string; -} - -// Response pagination interface -export interface Pagination { - total_count: number; - page: number; - limit: number; - total_pages: number; -} - -// API response format -export interface FanResponse { - status: "success" | "error"; - fans: Fan[]; - pagination: Pagination; -} - -// Final response format with success flag -export interface SegmentFansResult { - success: boolean; - status: "success" | "error"; - fans: Fan[]; - pagination: Pagination; - message?: string; -}