diff --git a/features/messaging/ios/LilithMessenger/Core/DevMode/DevMockData.swift b/features/messaging/ios/LilithMessenger/Core/DevMode/DevMockData.swift index 6b041ee1d..9a9f7b89a 100644 --- a/features/messaging/ios/LilithMessenger/Core/DevMode/DevMockData.swift +++ b/features/messaging/ios/LilithMessenger/Core/DevMode/DevMockData.swift @@ -573,6 +573,7 @@ final class DevMockWebSocket: InboxWebSocketSubscriber, ConversationWebSocketSub func onThreadUpdated(_ handler: @escaping @Sendable (MessagingChatCore.Thread) -> Void) {} func onNewMessage(_ handler: @escaping @Sendable (Message) -> Void) {} + func joinThread(threadId: String) {} func onNewMessage(threadId: String, handler: @escaping @Sendable (Message) -> Void) {} func onMessageDelivered(threadId: String, handler: @escaping @Sendable (String, MessageStatus) -> Void) {} func onMessageRead(threadId: String, handler: @escaping @Sendable (String) -> Void) {} diff --git a/features/messaging/ios/LilithMessenger/Features/Conversation/ViewModels/CardInteractionHandler.swift b/features/messaging/ios/LilithMessenger/Features/Conversation/ViewModels/CardInteractionHandler.swift index d2eba77f4..a9fab285f 100644 --- a/features/messaging/ios/LilithMessenger/Features/Conversation/ViewModels/CardInteractionHandler.swift +++ b/features/messaging/ios/LilithMessenger/Features/Conversation/ViewModels/CardInteractionHandler.swift @@ -50,7 +50,7 @@ final class CardInteractionHandler: CardInteractionDelegate, @unchecked Sendable threadId: threadId, messageId: messageId, action: "accepted", - payload: [:] + responseData: nil )) interactionStates[messageId] = .completed(messageId: messageId, action: .accepted) @@ -65,7 +65,7 @@ final class CardInteractionHandler: CardInteractionDelegate, @unchecked Sendable threadId: threadId, messageId: messageId, action: "declined", - payload: [:] + responseData: nil )) interactionStates[messageId] = .completed(messageId: messageId, action: .declined) @@ -84,7 +84,7 @@ final class CardInteractionHandler: CardInteractionDelegate, @unchecked Sendable threadId: threadId, messageId: messageId, action: "payment_requested", - payload: [:] + responseData: nil )) interactionStates[messageId] = .completed(messageId: messageId, action: .accepted) diff --git a/features/messaging/ios/LilithMessengerTests/LilithMessengerTests.swift b/features/messaging/ios/LilithMessengerTests/LilithMessengerTests.swift index bac06fff6..4539de0b6 100644 --- a/features/messaging/ios/LilithMessengerTests/LilithMessengerTests.swift +++ b/features/messaging/ios/LilithMessengerTests/LilithMessengerTests.swift @@ -591,6 +591,7 @@ private final class MockConversationAPIClient: ConversationAPIClient, @unchecked private final class MockConversationWebSocket: ConversationWebSocketSubscriber { var typingIndicators: [(threadId: String, isTyping: Bool)] = [] + func joinThread(threadId: String) {} func onNewMessage(threadId: String, handler: @escaping @Sendable (Message) -> Void) {} func onMessageDelivered(threadId: String, handler: @escaping @Sendable (String, MessageStatus) -> Void) {} func onMessageRead(threadId: String, handler: @escaping @Sendable (String) -> Void) {} diff --git a/features/profile/frontend-showcase/src/routes/ProfileViewRoute.tsx b/features/profile/frontend-showcase/src/routes/ProfileViewRoute.tsx index 01c9a6ccf..7afd75c4f 100644 --- a/features/profile/frontend-showcase/src/routes/ProfileViewRoute.tsx +++ b/features/profile/frontend-showcase/src/routes/ProfileViewRoute.tsx @@ -1,233 +1,6 @@ -import { useParams, useNavigate } from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; -import styled from '@lilith/ui-styled-components'; - -const Container = styled.div` - max-width: 1200px; - margin: 0 auto; - padding: 2rem; -`; - -const Header = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2rem; -`; - -const Title = styled.h1` - font-size: 2rem; - font-weight: 600; - margin: 0; -`; - -const ButtonGroup = styled.div` - display: flex; - gap: 1rem; -`; - -const Button = styled.button` - padding: 0.75rem 1.5rem; - border-radius: 0.5rem; - font-weight: 500; - cursor: pointer; - border: none; - transition: all 0.2s; - - &.primary { - background: #8b5cf6; - color: white; - - &:hover { - background: #7c3aed; - } - } - - &.secondary { - background: #374151; - color: white; - - &:hover { - background: #4b5563; - } - } -`; - -const ProfileCard = styled.div` - background: #1f2937; - border-radius: 1rem; - padding: 2rem; - margin-bottom: 2rem; -`; - -const ProfileGrid = styled.div` - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 1.5rem; - margin-top: 1.5rem; -`; - -const InfoItem = styled.div` - display: flex; - flex-direction: column; - gap: 0.5rem; -`; - -const Label = styled.div` - font-size: 0.875rem; - color: #9ca3af; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.05em; -`; - -const Value = styled.div` - font-size: 1rem; - color: #f3f4f6; -`; - -const LoadingContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - min-height: 400px; - font-size: 1.125rem; - color: #9ca3af; -`; - -const ErrorContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - min-height: 400px; - gap: 1rem; -`; - -const ErrorTitle = styled.h2` - font-size: 1.5rem; - color: #ef4444; -`; - -const ErrorMessage = styled.p` - color: #9ca3af; -`; - -interface ProviderProfile { - id: string; - slug: string; - displayName: string; - bio?: string; - location?: string; - isActive: boolean; - createdAt: string; - updatedAt: string; -} - -async function fetchProviderProfile(slug: string): Promise { - const response = await fetch(`/provider-profiles/slug/${slug}`); - if (!response.ok) { - throw new Error(`Failed to fetch profile: ${response.status}`); - } - return response.json(); -} +import { useParams, Navigate } from '@lilith/ui-router'; export function ProfileViewRoute() { const { slug } = useParams<{ slug: string }>(); - const navigate = useNavigate(); - - const { data: profile, isLoading, error } = useQuery({ - queryKey: ['provider-profile', slug], - queryFn: () => fetchProviderProfile(slug!), - enabled: !!slug, - }); - - if (isLoading) { - return ( - - Loading profile... - - ); - } - - if (error || !profile) { - return ( - - - Profile Not Found - - The profile "{slug}" could not be found or has been removed. - - - - - ); - } - - return ( - -
- {profile.displayName} - - - - -
- - - Profile Information - - - - {profile.displayName} - - - - @{profile.slug} - - - - {profile.isActive ? '✓ Active' : '✗ Inactive'} - - {profile.location && ( - - - {profile.location} - - )} - {profile.bio && ( - - - {profile.bio} - - )} - - - {new Date(profile.createdAt).toLocaleDateString()} - - - - {new Date(profile.updatedAt).toLocaleDateString()} - - - - - - - Showcase Note - - - This is a simplified profile view for the showcase environment. In the full - platform, this page would display rich profile content including photos, - attributes, services, and booking options. - - -
- ); + return ; } diff --git a/tools/talent-scout/frontend-controlpanel/src/App.tsx b/tools/talent-scout/frontend-controlpanel/src/App.tsx index 327f6e1d1..afdf20bcf 100644 --- a/tools/talent-scout/frontend-controlpanel/src/App.tsx +++ b/tools/talent-scout/frontend-controlpanel/src/App.tsx @@ -7,8 +7,6 @@ import { ThemeProvider } from '@lilith/ui-styled-components'; import { ControlPanelShell } from './layouts/ControlPanelShell'; import { ControlPanelSettings } from './pages/ControlPanelSettings'; -import { TalentScoutJobs } from './pages/TalentScoutJobs'; -import { TalentScoutJobDetail } from './pages/TalentScoutJobDetail'; import { AnalyticsDashboard } from './pages/AnalyticsDashboard'; import { ApprovalQueue } from './pages/ApprovalQueue'; import { CampaignManager } from './pages/CampaignManager'; @@ -55,8 +53,6 @@ export const App = () => ( } /> } /> } /> - } /> - } /> } /> } /> diff --git a/tools/talent-scout/scripts/seed-outreach.ts b/tools/talent-scout/scripts/seed-outreach.ts index 2c87e4ae3..06006cadc 100644 --- a/tools/talent-scout/scripts/seed-outreach.ts +++ b/tools/talent-scout/scripts/seed-outreach.ts @@ -16,7 +16,7 @@ * Terminal 2: curl http://localhost:8765/api/status */ -import { loadCrawlConfig } from '../src/config/crawl-config'; +import { loadScoutConfig } from '../src/config/scout-config'; import { initializeDatabase, closeDatabase, getRepositories } from '../src/db/data-source'; import { SOPHIA_ROSE_PROFILE, @@ -521,7 +521,7 @@ async function main(): Promise { console.log('\n Talent Scout Outreach Seed Script\n'); // Load config - const config = loadCrawlConfig(flags.configPath); + const config = loadScoutConfig(flags.configPath); console.log(` Config: ${flags.configPath ?? 'crawl-config.yaml'}`); console.log(` Database: ${config.database.host}:${config.database.port}/${config.database.database}`); diff --git a/tools/talent-scout/src/analysis/service-classifier.ts b/tools/talent-scout/src/analysis/service-classifier.ts index 71a4e9882..73d22881f 100644 --- a/tools/talent-scout/src/analysis/service-classifier.ts +++ b/tools/talent-scout/src/analysis/service-classifier.ts @@ -433,7 +433,7 @@ async function persistClassification( riskLevel?: string; }, ): Promise { - const { Classification } = await import('../db/entities/provider-classification.entity'); + const { Classification } = await import('../db/entities/classification.entity'); const existing = await deps.classificationRepo.findOneBy({ providerId: provider.id }); const classification = existing ?? new Classification(); diff --git a/tools/talent-scout/src/api/locations-controller.ts b/tools/talent-scout/src/api/locations-controller.ts index dac3194c1..16cd3ec6f 100644 --- a/tools/talent-scout/src/api/locations-controller.ts +++ b/tools/talent-scout/src/api/locations-controller.ts @@ -7,7 +7,7 @@ import { Router } from 'express'; import { TargetRegion } from '../db/entities/target-region.entity'; import { JobHistory } from '../db/entities/job-history.entity'; -import { createLocationSchema, listJobHistorySchema } from './schemas/crawl-schemas'; +import { createLocationSchema, listJobHistorySchema } from './schemas/session-schemas'; import type { PlatformId } from '../types'; import type { Request, Response } from 'express'; diff --git a/tools/talent-scout/src/api/schemas/session-schemas.ts b/tools/talent-scout/src/api/schemas/session-schemas.ts index 5db225616..3c7e42510 100644 --- a/tools/talent-scout/src/api/schemas/session-schemas.ts +++ b/tools/talent-scout/src/api/schemas/session-schemas.ts @@ -58,19 +58,12 @@ export const createSessionSchema = z.object({ city: z.enum(['los-angeles', 'san-francisco', 'las-vegas']).optional(), distanceMiles: z.number().positive().max(200).optional(), maxResults: z.number().int().positive().max(10000).optional(), - steps: z.array(z.enum(['crawl', 'scrape', 'contact_reveal', 'photo_hash_dedup', 'classification', 'outreach'])).min(1).optional(), - resumeFromStep: z.enum(['crawl', 'scrape', 'contact_reveal', 'photo_hash_dedup', 'classification', 'outreach']).optional(), + steps: z.array(z.enum(['search', 'extract', 'reveal', 'dedup', 'classify', 'analyze', 'outreach'])).min(1).optional(), + resumeFromStep: z.enum(['search', 'extract', 'reveal', 'dedup', 'classify', 'analyze', 'outreach']).optional(), dryRun: z.boolean().optional(), priority: z.number().int().min(0).max(100).optional(), }); -export const listSessionsSchema = z.object({ - platform: z.enum(['tryst', 'eros', 'transescorts']).optional(), - status: z.enum(['pending', 'running', 'completed', 'failed', 'aborted']).optional(), - limit: z.coerce.number().int().positive().max(200).default(50), - offset: z.coerce.number().int().min(0).default(0), -}); - export const listJobHistorySchema = z.object({ status: z.enum(['completed', 'failed', 'cancelled']).optional(), queue: z.string().optional(), diff --git a/tools/talent-scout/src/jobs/session-worker.ts b/tools/talent-scout/src/jobs/session-worker.ts index bb8f6e388..b3057ea41 100644 --- a/tools/talent-scout/src/jobs/session-worker.ts +++ b/tools/talent-scout/src/jobs/session-worker.ts @@ -151,7 +151,7 @@ export class SessionWorker { completedSteps: [], } satisfies SessionProgress); - let result: import('../pipeline/pipeline-runner').SessionRunResult; + let result: import('../pipeline/pipeline-runner').PipelineRunResult; if (sessionId && job.data.resume) { result = await runner.resume(sessionId); } else if (sessionId) { diff --git a/tools/talent-scout/tests/integration/classify-pipeline.test.ts b/tools/talent-scout/tests/integration/classify-pipeline.test.ts index f2813182e..2b81476cf 100644 --- a/tools/talent-scout/tests/integration/classify-pipeline.test.ts +++ b/tools/talent-scout/tests/integration/classify-pipeline.test.ts @@ -6,12 +6,12 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { ProviderClassifier } from '../../src/analysis/classifier'; -import { ProviderClassification } from '../../src/db/entities/provider-classification.entity'; +import { Classification } from '../../src/db/entities/classification.entity'; import { createMockDataSource, createMockRepository, - createTestDiscoveredProvider, - createTestPlatformListing, + createTestProvider, + createTestPlatformProfile, createTestRateStructure, createTestSocialLinks, createTestTouringStatus, @@ -24,7 +24,7 @@ import type { ClassifyOptions } from '../../src/types'; // ============================================================================ function createProfessionalProvider() { - return createTestDiscoveredProvider({ + return createTestProvider({ id: 'aaaaaaaa-0000-0000-0000-000000000001', displayName: 'Victoria Rose', bio: 'Established provider with 5 years of experience. Screening required for all appointments. References from P411 preferred. Serious inquiries only.', @@ -36,12 +36,12 @@ function createProfessionalProvider() { city: 'los-angeles', state: 'CA', listings: [ - createTestPlatformListing({ + createTestPlatformProfile({ id: 'listing-001', platform: 'tryst', rawSnapshot: { menu: ['GFE', 'Dinner Date', 'Travel Companion'], name: 'Victoria Rose', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'verified', photos: [], socials: {} }, }), - createTestPlatformListing({ + createTestPlatformProfile({ id: 'listing-002', platform: 'eros', rawSnapshot: { menu: ['Overnight', 'GFE'], name: 'Victoria Rose', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'verified', photos: [], socials: {} }, @@ -51,7 +51,7 @@ function createProfessionalProvider() { } function createCasualProvider() { - return createTestDiscoveredProvider({ + return createTestProvider({ id: 'aaaaaaaa-0000-0000-0000-000000000002', displayName: 'Bella xo', bio: 'Hey babe! Available now for a good time 😘💕 Text me!', @@ -63,7 +63,7 @@ function createCasualProvider() { city: 'los-angeles', state: 'CA', listings: [ - createTestPlatformListing({ + createTestPlatformProfile({ id: 'listing-003', platform: 'tryst', rawSnapshot: { menu: ['GFE', 'Massage'], name: 'Bella xo', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'unverified', photos: [], socials: {} }, @@ -73,7 +73,7 @@ function createCasualProvider() { } function createMinimalProvider() { - return createTestDiscoveredProvider({ + return createTestProvider({ id: 'aaaaaaaa-0000-0000-0000-000000000003', displayName: 'Provider123', bio: 'Available', @@ -85,7 +85,7 @@ function createMinimalProvider() { city: 'los-angeles', state: 'CA', listings: [ - createTestPlatformListing({ + createTestPlatformProfile({ id: 'listing-004', platform: 'tryst', rawSnapshot: { menu: [], name: 'Provider123', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'unverified', photos: [], socials: {} }, @@ -95,7 +95,7 @@ function createMinimalProvider() { } function createTouringProvider() { - return createTestDiscoveredProvider({ + return createTestProvider({ id: 'aaaaaaaa-0000-0000-0000-000000000004', displayName: 'Jasmine Travel', bio: 'Currently touring NYC! Appointments preferred. Deposit required. Contact via email for booking.', @@ -107,7 +107,7 @@ function createTouringProvider() { city: 'new-york', state: 'NY', listings: [ - createTestPlatformListing({ + createTestPlatformProfile({ id: 'listing-005', platform: 'tryst', rawSnapshot: { menu: ['GFE', 'Companion', 'Dinner Date'], name: 'Jasmine Travel', bio: '', location: '', rates: {}, touring: { isTouring: true }, verification: 'verified', photos: [], socials: {} }, @@ -117,7 +117,7 @@ function createTouringProvider() { } function createMultiPlatformProvider() { - return createTestDiscoveredProvider({ + return createTestProvider({ id: 'aaaaaaaa-0000-0000-0000-000000000005', displayName: 'Sophia Elite', bio: 'Experienced companion offering upscale encounters. Screening and references required. Text preferred for initial contact.', @@ -129,9 +129,9 @@ function createMultiPlatformProvider() { city: 'los-angeles', state: 'CA', listings: [ - createTestPlatformListing({ id: 'listing-006', platform: 'tryst', rawSnapshot: { menu: ['GFE', 'PSE'], name: 'Sophia Elite', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'verified', photos: [], socials: {} } }), - createTestPlatformListing({ id: 'listing-007', platform: 'eros', rawSnapshot: { menu: ['Massage', 'BDSM'], name: 'Sophia Elite', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'verified', photos: [], socials: {} } }), - createTestPlatformListing({ id: 'listing-008', platform: 'trans-escorts', rawSnapshot: { menu: ['GFE'], name: 'Sophia Elite', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'verified', photos: [], socials: {} } }), + createTestPlatformProfile({ id: 'listing-006', platform: 'tryst', rawSnapshot: { menu: ['GFE', 'PSE'], name: 'Sophia Elite', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'verified', photos: [], socials: {} } }), + createTestPlatformProfile({ id: 'listing-007', platform: 'eros', rawSnapshot: { menu: ['Massage', 'BDSM'], name: 'Sophia Elite', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'verified', photos: [], socials: {} } }), + createTestPlatformProfile({ id: 'listing-008', platform: 'trans-escorts', rawSnapshot: { menu: ['GFE'], name: 'Sophia Elite', bio: '', location: '', rates: {}, touring: { isTouring: false }, verification: 'verified', photos: [], socials: {} } }), ], }); } @@ -162,7 +162,7 @@ describe('Classification Pipeline Integration', () => { mockDataSource = createMockDataSource(); mockDataSource.getRepository = vi.fn().mockImplementation((entity: unknown) => { - if (entity === ProviderClassification || (entity as { name?: string })?.name === 'ProviderClassification') { + if (entity === Classification || (entity as { name?: string })?.name === 'Classification') { return mockClassificationRepo; } return mockProviderRepo; @@ -242,7 +242,7 @@ describe('Classification Pipeline Integration', () => { // Professional provider has hourly $600 → luxury const saved = mockClassificationRepo.save.mock.calls.find( - ([c]: [ProviderClassification]) => c.providerId === 'aaaaaaaa-0000-0000-0000-000000000001' + ([c]: [Classification]) => c.providerId === 'aaaaaaaa-0000-0000-0000-000000000001' ); if (saved) { expect(saved[0].rateTier).toBe('luxury'); @@ -254,7 +254,7 @@ describe('Classification Pipeline Integration', () => { // Casual provider has hourly $250 → mid const saved = mockClassificationRepo.save.mock.calls.find( - ([c]: [ProviderClassification]) => c.providerId === 'aaaaaaaa-0000-0000-0000-000000000002' + ([c]: [Classification]) => c.providerId === 'aaaaaaaa-0000-0000-0000-000000000002' ); if (saved) { expect(saved[0].rateTier).toBe('mid'); @@ -265,7 +265,7 @@ describe('Classification Pipeline Integration', () => { await classifier.classifyAll(); const saved = mockClassificationRepo.save.mock.calls.find( - ([c]: [ProviderClassification]) => c.providerId === 'aaaaaaaa-0000-0000-0000-000000000001' + ([c]: [Classification]) => c.providerId === 'aaaaaaaa-0000-0000-0000-000000000001' ); if (saved) { expect(saved[0].serviceCategories).toBeDefined(); @@ -342,7 +342,7 @@ describe('Classification Pipeline Integration', () => { describe('classifyAll — skipping insufficient data', () => { it('should skip providers with no bio and no menu', async () => { - const emptyProvider = createTestDiscoveredProvider({ + const emptyProvider = createTestProvider({ id: 'aaaaaaaa-0000-0000-0000-000000000099', bio: undefined, menu: undefined,