From 83a58c863a7505d95ad7d849c3dd95910546d5df Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Fri, 17 Apr 2026 06:53:41 +0530 Subject: [PATCH] chore: remove dead segments surface (#447) Deletes GET /api/artist/segments, GET /api/chats/[id]/segment, the create_segments MCP tool registration, and the lib/segments, lib/artist/*segments*, lib/chats/getChatSegmentHandler, and lib/supabase/{artist_segments,fan_segments,segments,segment_rooms} modules. Also prunes the two remaining tool-chain references to the create_segments tool in lib/chat/toolChains/{toolChains.ts, createNewArtistToolChain.ts} so the orchestration does not reference a tool that no longer exists. The Segments feature is being removed entirely; the earlier migration plan is abandoned. Part of SEGMENTS_SURFACE_REMOVAL_PLAN.md. Co-authored-by: Claude Opus 4.6 --- app/api/artist/segments/route.ts | 32 ----- app/api/chats/[id]/segment/route.ts | 35 ------ lib/artist/getArtistSegments.ts | 74 ----------- lib/artist/getArtistSegmentsHandler.ts | 54 -------- lib/artist/mapArtistSegments.ts | 31 ----- lib/artist/validateArtistSegmentsQuery.ts | 59 --------- .../toolChains/createNewArtistToolChain.ts | 1 - lib/chat/toolChains/toolChains.ts | 1 - .../__tests__/getChatSegmentHandler.test.ts | 96 -------------- lib/chats/getChatSegmentHandler.ts | 36 ------ lib/mcp/tools/index.ts | 2 - lib/mcp/tools/registerCreateSegmentsTool.ts | 55 -------- lib/segments/consts.ts | 16 --- lib/segments/createSegmentResponses.ts | 29 ----- lib/segments/createSegments.ts | 117 ------------------ lib/segments/generateSegments.ts | 33 ----- lib/segments/getAnalysisPrompt.ts | 36 ------ lib/segments/getFanSegmentsToInsert.ts | 27 ---- .../artist_segments/insertArtistSegments.ts | 22 ---- .../artist_segments/selectArtistSegments.ts | 25 ---- .../selectArtistSegmentsCount.ts | 21 ---- .../selectArtistSegmentsWithDetails.ts | 44 ------- .../fan_segments/insertFanSegments.ts | 22 ---- .../selectSegmentRoomByRoomId.ts | 25 ---- lib/supabase/segments/deleteSegments.ts | 35 ------ lib/supabase/segments/insertSegments.ts | 22 ---- 26 files changed, 950 deletions(-) delete mode 100644 app/api/artist/segments/route.ts delete mode 100644 app/api/chats/[id]/segment/route.ts delete mode 100644 lib/artist/getArtistSegments.ts delete mode 100644 lib/artist/getArtistSegmentsHandler.ts delete mode 100644 lib/artist/mapArtistSegments.ts delete mode 100644 lib/artist/validateArtistSegmentsQuery.ts delete mode 100644 lib/chats/__tests__/getChatSegmentHandler.test.ts delete mode 100644 lib/chats/getChatSegmentHandler.ts delete mode 100644 lib/mcp/tools/registerCreateSegmentsTool.ts delete mode 100644 lib/segments/consts.ts 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/artist_segments/selectArtistSegments.ts delete mode 100644 lib/supabase/artist_segments/selectArtistSegmentsCount.ts delete mode 100644 lib/supabase/artist_segments/selectArtistSegmentsWithDetails.ts delete mode 100644 lib/supabase/fan_segments/insertFanSegments.ts delete mode 100644 lib/supabase/segment_rooms/selectSegmentRoomByRoomId.ts delete mode 100644 lib/supabase/segments/deleteSegments.ts delete mode 100644 lib/supabase/segments/insertSegments.ts diff --git a/app/api/artist/segments/route.ts b/app/api/artist/segments/route.ts deleted file mode 100644 index 4724960ce..000000000 --- a/app/api/artist/segments/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { getArtistSegmentsHandler } from "@/lib/artist/getArtistSegmentsHandler"; - -/** - * OPTIONS handler for CORS preflight requests. - * - * @returns A NextResponse with CORS headers. - */ -export async function OPTIONS() { - return new NextResponse(null, { - status: 200, - headers: getCorsHeaders(), - }); -} - -/** - * GET /api/artist/segments - * - * Retrieves all segments associated with an artist account. - * - * Query parameters: - * - artist_account_id (required): The unique identifier of the artist account - * - page (optional): Page number for pagination (default: 1) - * - limit (optional): Number of segments per page (default: 20, max: 100) - * - * @param request - The request object containing query parameters. - * @returns A NextResponse with segments and pagination metadata. - */ -export async function GET(request: NextRequest) { - return getArtistSegmentsHandler(request); -} diff --git a/app/api/chats/[id]/segment/route.ts b/app/api/chats/[id]/segment/route.ts deleted file mode 100644 index f0bbdb72c..000000000 --- a/app/api/chats/[id]/segment/route.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; -import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { getChatSegmentHandler } from "@/lib/chats/getChatSegmentHandler"; - -/** - * OPTIONS handler for CORS preflight requests. - * - * @returns A NextResponse with CORS headers. - */ -export async function OPTIONS() { - return new NextResponse(null, { - status: 200, - headers: getCorsHeaders(), - }); -} - -/** - * GET /api/chats/[id]/segment - * - * Retrieves the segment associated with a chat room. - * Returns 404 when the room does not exist or is not accessible by the caller. - * - * @param request - The incoming request object. - * @param root0 - The route context object. - * @param root0.params - The dynamic route params containing chat id. - * @returns A NextResponse with segment linkage data or an error. - */ -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ id: string }> }, -): Promise { - const { id } = await params; - return getChatSegmentHandler(request, id); -} diff --git a/lib/artist/getArtistSegments.ts b/lib/artist/getArtistSegments.ts deleted file mode 100644 index f4267ae2e..000000000 --- a/lib/artist/getArtistSegments.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { selectArtistSegmentsCount } from "@/lib/supabase/artist_segments/selectArtistSegmentsCount"; -import { selectArtistSegmentsWithDetails } from "@/lib/supabase/artist_segments/selectArtistSegmentsWithDetails"; -import type { ArtistSegmentsQuery } from "@/lib/artist/validateArtistSegmentsQuery"; -import { mapArtistSegments, type MappedArtistSegment } from "@/lib/artist/mapArtistSegments"; - -interface GetArtistSegmentsResponse { - status: "success" | "error"; - segments: MappedArtistSegment[]; - pagination: { - total_count: number; - page: number; - limit: number; - total_pages: number; - }; - message?: string; -} - -export const getArtistSegments = async ({ - artist_account_id, - page, - limit, -}: ArtistSegmentsQuery): Promise => { - try { - const offset = (page - 1) * limit; - - const total_count = await selectArtistSegmentsCount(artist_account_id); - - if (total_count === 0) { - return { - status: "success", - segments: [], - pagination: { - total_count: 0, - page, - limit, - total_pages: 0, - }, - }; - } - - const data = await selectArtistSegmentsWithDetails(artist_account_id, offset, limit); - - if (!data) { - return { - status: "error", - segments: [], - pagination: { - total_count, - page, - limit, - total_pages: Math.ceil(total_count / limit), - }, - }; - } - - const formattedSegments = mapArtistSegments(data); - - const total_pages = Math.ceil(total_count / limit); - - return { - status: "success", - segments: formattedSegments, - pagination: { - total_count, - page, - limit, - total_pages, - }, - }; - } catch (error) { - console.error("[ERROR] Error in getArtistSegments:", error); - throw error; - } -}; diff --git a/lib/artist/getArtistSegmentsHandler.ts b/lib/artist/getArtistSegmentsHandler.ts deleted file mode 100644 index 559cb4cc1..000000000 --- a/lib/artist/getArtistSegmentsHandler.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { getArtistSegments } from "@/lib/artist/getArtistSegments"; -import { validateArtistSegmentsQuery } from "@/lib/artist/validateArtistSegmentsQuery"; - -/** - * Handler for retrieving artist segments with pagination. - * - * Parameters: - * - artist_account_id (required): The unique identifier of the artist account - * - page (optional): Page number for pagination (default: 1) - * - limit (optional): Number of segments per page (default: 20, max: 100) - * - * @param request - The request object containing query parameters. - * @returns A NextResponse with segments and pagination metadata. - */ -export async function getArtistSegmentsHandler(request: NextRequest): Promise { - try { - const { searchParams } = new URL(request.url); - - const validatedQuery = validateArtistSegmentsQuery(searchParams); - if (validatedQuery instanceof NextResponse) { - return validatedQuery; - } - - const result = await getArtistSegments(validatedQuery); - - const statusCode = result.status === "success" ? 200 : 500; - - return NextResponse.json(result, { - status: statusCode, - headers: getCorsHeaders(), - }); - } catch (error) { - console.error("[ERROR] getArtistSegmentsHandler error:", error); - return NextResponse.json( - { - status: "error", - message: error instanceof Error ? error.message : "An unknown error occurred", - segments: [], - pagination: { - total_count: 0, - page: 1, - limit: 20, - total_pages: 0, - }, - }, - { - status: 500, - headers: getCorsHeaders(), - }, - ); - } -} diff --git a/lib/artist/mapArtistSegments.ts b/lib/artist/mapArtistSegments.ts deleted file mode 100644 index b6e40af8c..000000000 --- a/lib/artist/mapArtistSegments.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { SegmentQueryResult } from "@/lib/supabase/artist_segments/selectArtistSegmentsWithDetails"; - -export interface MappedArtistSegment { - id: string; - artist_account_id: string; - segment_id: string; - updated_at: string; - segment_name: string; - artist_name: string; -} - -/** - * Maps Supabase query results into the expected artist segment response format. - * - * @param segments - The raw query results from Supabase with joined segment and account data - * @returns Array of mapped artist segments - */ -export function mapArtistSegments(segments: SegmentQueryResult[]): MappedArtistSegment[] { - return segments.map(segment => { - const { segments, accounts } = segment; - - return { - id: segment.id, - artist_account_id: segment.artist_account_id, - segment_id: segment.segment_id, - updated_at: segment.updated_at || "", - segment_name: segments?.name || "Unknown Segment", - artist_name: accounts?.name || "Unknown Artist", - }; - }); -} diff --git a/lib/artist/validateArtistSegmentsQuery.ts b/lib/artist/validateArtistSegmentsQuery.ts deleted file mode 100644 index 54d5ad794..000000000 --- a/lib/artist/validateArtistSegmentsQuery.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { NextResponse } from "next/server"; -import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { z } from "zod"; - -export const artistSegmentsQuerySchema = z.object({ - artist_account_id: z.string().min(1, "artist_account_id parameter is required"), - page: z - .string() - .optional() - .default("1") - .transform(val => parseInt(val, 10)) - .pipe(z.number().int().positive()), - limit: z - .string() - .optional() - .default("20") - .transform(val => parseInt(val, 10)) - .pipe(z.number().int().min(1).max(100)), -}); - -export type ArtistSegmentsQuery = z.infer; - -/** - * Validates artist segments query parameters. - * - * @param searchParams - The URL search parameters to validate. - * @returns A NextResponse with an error if validation fails, or the validated query parameters if validation passes. - */ -export function validateArtistSegmentsQuery( - searchParams: URLSearchParams, -): NextResponse | ArtistSegmentsQuery { - const params = Object.fromEntries(searchParams.entries()); - - const validationResult = artistSegmentsQuerySchema.safeParse(params); - - if (!validationResult.success) { - const firstError = validationResult.error.issues[0]; - return NextResponse.json( - { - status: "error", - missing_fields: firstError.path, - error: firstError.message, - segments: [], - pagination: { - total_count: 0, - page: 1, - limit: 20, - total_pages: 0, - }, - }, - { - status: 400, - headers: getCorsHeaders(), - }, - ); - } - - return validationResult.data; -} diff --git a/lib/chat/toolChains/createNewArtistToolChain.ts b/lib/chat/toolChains/createNewArtistToolChain.ts index ab2160743..e18fba6eb 100644 --- a/lib/chat/toolChains/createNewArtistToolChain.ts +++ b/lib/chat/toolChains/createNewArtistToolChain.ts @@ -36,6 +36,5 @@ export const createNewArtistToolChain: ToolChainItem[] = [ system: "Using the arweaveUrl returned from generate_txt_file, update the artist's knowledges array to include the new knowledge base entry. Set the knowledge object with: url (the arweaveUrl), name (e.g., 'Artist Knowledge Base Report'), and type ('text/plain').", }, - { toolName: "create_segments" }, { toolName: "youtube_login" }, ]; diff --git a/lib/chat/toolChains/toolChains.ts b/lib/chat/toolChains/toolChains.ts index 98f2c6f78..1761d8ba4 100644 --- a/lib/chat/toolChains/toolChains.ts +++ b/lib/chat/toolChains/toolChains.ts @@ -29,7 +29,6 @@ export const TOOL_MODEL_MAP: Record = { get_spotify_album: "openai/gpt-5.4-mini", search_web: "openai/gpt-5.4-mini", generate_txt_file: "openai/gpt-5.4-mini", - create_segments: "openai/gpt-5.4-mini", youtube_login: "openai/gpt-5.4-mini", web_deep_research: "openai/gpt-5.4-mini", create_knowledge_base: "openai/gpt-5.4-mini", diff --git a/lib/chats/__tests__/getChatSegmentHandler.test.ts b/lib/chats/__tests__/getChatSegmentHandler.test.ts deleted file mode 100644 index 18cfb92cd..000000000 --- a/lib/chats/__tests__/getChatSegmentHandler.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { describe, expect, it, vi, beforeEach } from "vitest"; -import { NextRequest, NextResponse } from "next/server"; -import { getChatSegmentHandler } from "@/lib/chats/getChatSegmentHandler"; -import { validateChatAccess } from "@/lib/chats/validateChatAccess"; -import { selectSegmentRoomByRoomId } from "@/lib/supabase/segment_rooms/selectSegmentRoomByRoomId"; - -vi.mock("@/lib/chats/validateChatAccess", () => ({ - validateChatAccess: vi.fn(), -})); - -vi.mock("@/lib/supabase/segment_rooms/selectSegmentRoomByRoomId", () => ({ - selectSegmentRoomByRoomId: vi.fn(), -})); - -const createRequest = () => new NextRequest("http://localhost/api/chats/chat-id/segment"); - -describe("getChatSegmentHandler", () => { - const roomId = "123e4567-e89b-42d3-a456-426614174000"; - const segmentId = "123e4567-e89b-42d3-a456-426614174003"; - - beforeEach(() => { - vi.clearAllMocks(); - }); - - it("returns validation/auth response from resolver", async () => { - vi.mocked(validateChatAccess).mockResolvedValue( - NextResponse.json({ status: "error", error: "Unauthorized" }, { status: 401 }), - ); - - const response = await getChatSegmentHandler(createRequest(), roomId); - const body = await response.json(); - - expect(response.status).toBe(401); - expect(body).toEqual({ - status: "error", - error: "Unauthorized", - }); - expect(selectSegmentRoomByRoomId).not.toHaveBeenCalled(); - }); - - it("returns linked segment when segment_room exists", async () => { - vi.mocked(validateChatAccess).mockResolvedValue({ - room: { - id: roomId, - account_id: "11111111-1111-1111-1111-111111111111", - artist_id: null, - topic: "Test", - updated_at: null, - }, - accountId: "11111111-1111-1111-1111-111111111111", - }); - vi.mocked(selectSegmentRoomByRoomId).mockResolvedValue({ - room_id: roomId, - segment_id: segmentId, - id: "123e4567-e89b-42d3-a456-426614174004", - created_at: null, - updated_at: null, - }); - - const response = await getChatSegmentHandler(createRequest(), roomId); - const body = await response.json(); - - expect(response.status).toBe(200); - expect(body).toEqual({ - status: "success", - room_id: roomId, - segment_id: segmentId, - segment_exists: true, - }); - }); - - it("returns null segment when no segment_room exists", async () => { - vi.mocked(validateChatAccess).mockResolvedValue({ - room: { - id: roomId, - account_id: "11111111-1111-1111-1111-111111111111", - artist_id: null, - topic: "Test", - updated_at: null, - }, - accountId: "11111111-1111-1111-1111-111111111111", - }); - vi.mocked(selectSegmentRoomByRoomId).mockResolvedValue(null); - - const response = await getChatSegmentHandler(createRequest(), roomId); - const body = await response.json(); - - expect(response.status).toBe(200); - expect(body).toEqual({ - status: "success", - room_id: roomId, - segment_id: null, - segment_exists: false, - }); - }); -}); diff --git a/lib/chats/getChatSegmentHandler.ts b/lib/chats/getChatSegmentHandler.ts deleted file mode 100644 index ab3d85186..000000000 --- a/lib/chats/getChatSegmentHandler.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; -import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { validateChatAccess } from "@/lib/chats/validateChatAccess"; -import { selectSegmentRoomByRoomId } from "@/lib/supabase/segment_rooms/selectSegmentRoomByRoomId"; - -/** - * Handles GET /api/chats/[id]/segment. - * - * Returns the segment associated with a chat room if one exists. - * - * @param request - The incoming request object for auth context. - * @param id - The chat room ID from route params. - * @returns A NextResponse with segment linkage data or an error. - */ -export async function getChatSegmentHandler( - request: NextRequest, - id: string, -): Promise { - const roomResult = await validateChatAccess(request, id); - if (roomResult instanceof NextResponse) { - return roomResult; - } - - const segmentRoom = await selectSegmentRoomByRoomId(roomResult.room.id); - - return NextResponse.json( - { - status: "success", - room_id: roomResult.room.id, - segment_id: segmentRoom?.segment_id || null, - segment_exists: Boolean(segmentRoom?.segment_id), - }, - { status: 200, headers: getCorsHeaders() }, - ); -} diff --git a/lib/mcp/tools/index.ts b/lib/mcp/tools/index.ts index e95da17fb..4425239cf 100644 --- a/lib/mcp/tools/index.ts +++ b/lib/mcp/tools/index.ts @@ -13,7 +13,6 @@ import { registerWebDeepResearchTool } from "./registerWebDeepResearchTool"; import { registerArtistDeepResearchTool } from "./registerArtistDeepResearchTool"; import { registerAllFileTools } from "./files"; import { registerAllFlamingoTools } from "./flamingo"; -import { registerCreateSegmentsTool } from "./registerCreateSegmentsTool"; import { registerAllYouTubeTools } from "./youtube"; import { registerTranscribeTools } from "./transcribe"; import { registerSendEmailTool } from "./registerSendEmailTool"; @@ -52,6 +51,5 @@ export const registerAllTools = (server: McpServer): void => { registerArtistDeepResearchTool(server); registerSendEmailTool(server); registerUpdateAccountInfoTool(server); - registerCreateSegmentsTool(server); registerAllYouTubeTools(server); }; diff --git a/lib/mcp/tools/registerCreateSegmentsTool.ts b/lib/mcp/tools/registerCreateSegmentsTool.ts deleted file mode 100644 index bece16a7f..000000000 --- a/lib/mcp/tools/registerCreateSegmentsTool.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { z } from "zod"; -import { createSegments } from "@/lib/segments/createSegments"; -import { getToolResultSuccess } from "@/lib/mcp/getToolResultSuccess"; -import { getToolResultError } from "@/lib/mcp/getToolResultError"; - -const createSegmentsSchema = z.object({ - artist_account_id: z - .string() - .min(1, "Artist account ID is required") - .describe( - "The artist_account_id to create segments for. If not provided, check system prompt for the active artist_account_id.", - ), - prompt: z - .string() - .min(1, "Prompt is required") - .describe( - "The prompt to use for generating segment names. This should be generated by the system if not provided.", - ), -}); - -export type CreateSegmentsArgs = z.infer; - -/** - * Registers the "create_segments" tool on the MCP server. - * Creates segments by analyzing fan data and generating segment names. - * - * @param server - The MCP server instance to register the tool on. - */ -export function registerCreateSegmentsTool(server: McpServer): void { - server.registerTool( - "create_segments", - { - 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: createSegmentsSchema, - }, - async (args: CreateSegmentsArgs) => { - try { - const result = await createSegments(args); - - if (result.success) { - return getToolResultSuccess(result); - } - - return getToolResultError(result.message || "Failed to create segments"); - } catch (error) { - console.error("Error creating segments:", error); - return getToolResultError( - error instanceof Error ? error.message : "Failed to create segments", - ); - } - }, - ); -} diff --git a/lib/segments/consts.ts b/lib/segments/consts.ts deleted file mode 100644 index 341222cec..000000000 --- a/lib/segments/consts.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const SEGMENT_FAN_SOCIAL_ID_PROMPT = `For each Segment Name return an array of fan_social_id included in the segment. Do not make these up. Only use the actual fan_social_id provided in the fan data prompt input.`; - -export const SEGMENT_SYSTEM_PROMPT = `You are an expert music industry analyst specializing in fan segmentation. - Your task is to analyze fan data and generate meaningful segment names that would be useful for marketing and engagement strategies. - - Guidelines for segment names: - - Keep names concise and descriptive (2-4 words) - - Focus on engagement patterns, demographics, or behavioral characteristics - - Use clear, actionable language that marketers can understand - - Avoid generic terms like "fans" or "followers" - - Consider factors like engagement frequency, recency, and intensity - - Generate 5-10 segment names that cover different aspects of the fan base - - The segment names should help artists and managers understand their audience better for targeted marketing campaigns. - - ${SEGMENT_FAN_SOCIAL_ID_PROMPT}`; diff --git a/lib/segments/createSegmentResponses.ts b/lib/segments/createSegmentResponses.ts deleted file mode 100644 index ec25b5db2..000000000 --- a/lib/segments/createSegmentResponses.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Tables } from "@/types/database.types"; -import type { GenerateArrayResult } from "./generateSegments"; - -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 df358664c..000000000 --- a/lib/segments/createSegments.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { selectAccountSocials } from "@/lib/supabase/account_socials/selectAccountSocials"; -import { selectSocialFans } from "@/lib/supabase/social_fans/selectSocialFans"; -import { generateSegments } from "./generateSegments"; -import { insertSegments } from "@/lib/supabase/segments/insertSegments"; -import { deleteSegments } from "@/lib/supabase/segments/deleteSegments"; -import { insertArtistSegments } from "@/lib/supabase/artist_segments/insertArtistSegments"; -import { insertFanSegments } from "@/lib/supabase/fan_segments/insertFanSegments"; -import { Tables } from "@/types/database.types"; -import { successResponse, errorResponse } from "./createSegmentResponses"; -import type { GenerateArrayResult } from "./generateSegments"; -import { getFanSegmentsToInsert } from "./getFanSegmentsToInsert"; -import { selectAccounts } from "@/lib/supabase/accounts/selectAccounts"; -import { CreateSegmentsArgs } from "../mcp/tools/registerCreateSegmentsTool"; - -export const createSegments = async ({ artist_account_id, prompt }: CreateSegmentsArgs) => { - try { - // Get artist info for better error messages - const accounts = await selectAccounts(artist_account_id); - const artistInfo = accounts[0]; - const artistName = artistInfo?.name || "this artist"; - - // Step 1: Get all social IDs for the artist - const accountSocials = await selectAccountSocials(artist_account_id, 0, 10000); - 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 40beeb091..000000000 --- a/lib/segments/generateSegments.ts +++ /dev/null @@ -1,33 +0,0 @@ -import generateArray from "@/lib/ai/generateArray"; -import { SEGMENT_SYSTEM_PROMPT } from "./consts"; -import getAnalysisPrompt from "./getAnalysisPrompt"; -import type { SocialFanWithDetails } from "@/lib/supabase/social_fans/selectSocialFans"; - -export interface GenerateSegmentsParams { - fans: SocialFanWithDetails[]; - prompt: string; -} - -export interface GenerateArrayResult { - segmentName: string; - fans: 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 d6a05ffde..000000000 --- a/lib/segments/getAnalysisPrompt.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { SEGMENT_FAN_SOCIAL_ID_PROMPT } from "./consts"; -import type { 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 a6f7097d9..000000000 --- a/lib/segments/getFanSegmentsToInsert.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { GenerateArrayResult } from "./generateSegments"; -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. - * - * @param segments - The AI-generated segments. - * @param insertedSegments - The inserted segment records. - * @returns An array of fan-segment associations to insert. - */ -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 1e442eec0..000000000 --- a/lib/supabase/artist_segments/insertArtistSegments.ts +++ /dev/null @@ -1,22 +0,0 @@ -import supabase from "../serverClient"; -import { Tables, TablesInsert } from "@/types/database.types"; - -/** - * Inserts artist_segments associations into the database. - * - * @param artistSegments - Array of artist_segment objects to insert - * @returns Array of inserted artist_segments - * @throws Error if the insertion fails - */ -export const insertArtistSegments = async ( - artistSegments: TablesInsert<"artist_segments">[], -): Promise[]> => { - const { data, error } = await supabase.from("artist_segments").insert(artistSegments).select(); - - if (error) { - console.error("Error inserting artist segments:", error); - throw error; - } - - return data || []; -}; diff --git a/lib/supabase/artist_segments/selectArtistSegments.ts b/lib/supabase/artist_segments/selectArtistSegments.ts deleted file mode 100644 index 370991b34..000000000 --- a/lib/supabase/artist_segments/selectArtistSegments.ts +++ /dev/null @@ -1,25 +0,0 @@ -import supabase from "../serverClient"; - -/** - * Selects artist_segments records for an artist account. - * - * @param artist_account_id - The artist account ID - * @returns Array of artist_segments records with segment_id, or empty array if none found or on error - */ -export async function selectArtistSegments(artist_account_id: string) { - const { data, error } = await supabase - .from("artist_segments") - .select("segment_id") - .eq("artist_account_id", artist_account_id); - - if (error) { - console.error("Error fetching artist segments:", error); - return []; - } - - if (!data || data.length === 0) { - return []; - } - - return data; -} diff --git a/lib/supabase/artist_segments/selectArtistSegmentsCount.ts b/lib/supabase/artist_segments/selectArtistSegmentsCount.ts deleted file mode 100644 index b9a1fb2f3..000000000 --- a/lib/supabase/artist_segments/selectArtistSegmentsCount.ts +++ /dev/null @@ -1,21 +0,0 @@ -import supabase from "../serverClient"; - -/** - * Selects the count of artist_segments records for a given artist account. - * - * @param artist_account_id - The unique identifier of the artist account - * @returns The count of artist_segments records, or 0 if none found - * @throws Error if the query fails - */ -export async function selectArtistSegmentsCount(artist_account_id: string): Promise { - const { count, error } = await supabase - .from("artist_segments") - .select("*", { count: "exact", head: true }) - .eq("artist_account_id", artist_account_id); - - if (error) { - throw new Error(`Failed to fetch artist segments count: ${error.message}`); - } - - return count ?? 0; -} diff --git a/lib/supabase/artist_segments/selectArtistSegmentsWithDetails.ts b/lib/supabase/artist_segments/selectArtistSegmentsWithDetails.ts deleted file mode 100644 index 56dce627f..000000000 --- a/lib/supabase/artist_segments/selectArtistSegmentsWithDetails.ts +++ /dev/null @@ -1,44 +0,0 @@ -import supabase from "../serverClient"; -import type { Tables } from "@/types/database.types"; - -/** - * Type for the Supabase query result with joined segments and accounts - */ -export type SegmentQueryResult = Tables<"artist_segments"> & { - segments: Tables<"segments"> | null; - accounts: Tables<"accounts"> | null; -}; - -/** - * Selects artist segments with joined segment and account data, filtered by artist account ID. - * - * @param artist_account_id - The unique identifier of the artist account - * @param offset - The number of records to skip - * @param limit - The maximum number of records to return - * @returns The query results with joined segment and account data - * @throws Error if the query fails - */ -export async function selectArtistSegmentsWithDetails( - artist_account_id: string, - offset: number, - limit: number, -): Promise { - const queryText = ` - *, - segments(*), - accounts:artist_account_id(*) - `; - - const { data, error } = await supabase - .from("artist_segments") - .select(queryText) - .eq("artist_account_id", artist_account_id) - .order("updated_at", { ascending: false }) - .range(offset, offset + limit - 1); - - if (error) { - throw new Error(`Failed to fetch artist segments: ${error.message}`); - } - - return data as unknown as SegmentQueryResult[] | null; -} diff --git a/lib/supabase/fan_segments/insertFanSegments.ts b/lib/supabase/fan_segments/insertFanSegments.ts deleted file mode 100644 index c1951886c..000000000 --- a/lib/supabase/fan_segments/insertFanSegments.ts +++ /dev/null @@ -1,22 +0,0 @@ -import supabase from "../serverClient"; -import { Tables, TablesInsert } from "@/types/database.types"; - -/** - * Inserts fan_segments associations into the database. - * - * @param fanSegments - Array of fan_segment objects to insert - * @returns Array of inserted fan_segments - * @throws Error if the insertion fails - */ -export const insertFanSegments = async ( - fanSegments: TablesInsert<"fan_segments">[], -): Promise[]> => { - const { data, error } = await supabase.from("fan_segments").insert(fanSegments).select(); - - if (error) { - console.error("Error inserting fan segments:", error); - throw error; - } - - return data || []; -}; diff --git a/lib/supabase/segment_rooms/selectSegmentRoomByRoomId.ts b/lib/supabase/segment_rooms/selectSegmentRoomByRoomId.ts deleted file mode 100644 index b0d7bee2c..000000000 --- a/lib/supabase/segment_rooms/selectSegmentRoomByRoomId.ts +++ /dev/null @@ -1,25 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; -import type { Tables } from "@/types/database.types"; - -/** - * Retrieves a segment_rooms row by room_id. - * - * @param roomId - The chat room ID. - * @returns The first matching segment_rooms row or null if none exists. - */ -export async function selectSegmentRoomByRoomId( - roomId: string, -): Promise | null> { - const { data, error } = await supabase - .from("segment_rooms") - .select("*") - .eq("room_id", roomId) - .maybeSingle(); - - if (error) { - console.error("[ERROR] selectSegmentRoomByRoomId:", error); - throw error; - } - - return data; -} diff --git a/lib/supabase/segments/deleteSegments.ts b/lib/supabase/segments/deleteSegments.ts deleted file mode 100644 index c4fa7a2b8..000000000 --- a/lib/supabase/segments/deleteSegments.ts +++ /dev/null @@ -1,35 +0,0 @@ -import supabase from "../serverClient"; -import { Tables } from "@/types/database.types"; -import { selectArtistSegments } from "../artist_segments/selectArtistSegments"; - -/** - * Deletes all segments associated with an artist account. - * - * @param artist_account_id - The artist account ID - * @returns Array of deleted segments - */ -export const deleteSegments = async (artist_account_id: string): Promise[]> => { - // Get all segment_ids associated with the artist - const artistSegments = await selectArtistSegments(artist_account_id); - const segmentIds = artistSegments.map((item: { segment_id: string }) => item.segment_id); - - if (segmentIds.length === 0) { - // No segments to delete - return []; - } - - // Delete the segments from the segments table - const { data, error } = await supabase.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; -}; diff --git a/lib/supabase/segments/insertSegments.ts b/lib/supabase/segments/insertSegments.ts deleted file mode 100644 index 4c4c1111c..000000000 --- a/lib/supabase/segments/insertSegments.ts +++ /dev/null @@ -1,22 +0,0 @@ -import supabase from "../serverClient"; -import { Tables, TablesInsert } from "@/types/database.types"; - -/** - * Inserts segments into the database. - * - * @param segments - Array of segment objects to insert - * @returns Array of inserted segments - * @throws Error if the insertion fails - */ -export const insertSegments = async ( - segments: TablesInsert<"segments">[], -): Promise[]> => { - const { data, error } = await supabase.from("segments").insert(segments).select(); - - if (error) { - console.error("Error inserting segments:", error); - throw error; - } - - return data || []; -};