From 9ba14f2f332c5a88b235c210764e9159f2a31aed Mon Sep 17 00:00:00 2001 From: tapframe Date: Thu, 11 Sep 2025 17:06:32 +0530 Subject: [PATCH] fix --- package-lock.json | 107 +++++++++++++++++++++++++++ package.json | 5 +- src/screens/AIChatScreen.tsx | 139 +++++++++++++++++++++++++++++++++-- src/services/aiService.ts | 96 ++++++++++++++++++------ src/services/tmdbService.ts | 2 +- 5 files changed, 315 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5fc83d5..b4afb90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "react-native-get-random-values": "^1.11.0", "react-native-image-colors": "^2.5.0", "react-native-immersive-mode": "^2.0.2", + "react-native-markdown-display": "^7.0.2", "react-native-paper": "^5.13.1", "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", @@ -6104,6 +6105,15 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001741", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", @@ -6615,6 +6625,15 @@ "node": ">=8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, "node_modules/css-in-js-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", @@ -6636,6 +6655,17 @@ "nth-check": "~1.0.1" } }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", @@ -9826,6 +9856,15 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -10024,6 +10063,37 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "license": "BSD-2-Clause" + }, "node_modules/marky": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", @@ -10071,6 +10141,12 @@ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "license": "CC0-1.0" }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "license": "MIT" + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -12098,6 +12174,15 @@ "integrity": "sha512-BWIKcEYtzjRV6GpkX0Km5/w2E7fgIcywiQOT7JZTc5NSbv/YI9kpFinB9lRFsOoRVGmiqq/O3VfP/oH2clIiBA==", "license": "MIT" }, + "node_modules/react-native-fit-image": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/react-native-fit-image/-/react-native-fit-image-1.5.5.tgz", + "integrity": "sha512-Wl3Vq2DQzxgsWKuW4USfck9zS7YzhvLNPpkwUUCF90bL32e1a0zOVQ3WsJILJOwzmPdHfzZmWasiiAUNBkhNkg==", + "license": "Beerware", + "dependencies": { + "prop-types": "^15.5.10" + } + }, "node_modules/react-native-gesture-handler": { "version": "2.20.2", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz", @@ -12149,6 +12234,22 @@ "react-native": ">=0.60.5" } }, + "node_modules/react-native-markdown-display": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/react-native-markdown-display/-/react-native-markdown-display-7.0.2.tgz", + "integrity": "sha512-Mn4wotMvMfLAwbX/huMLt202W5DsdpMO/kblk+6eUs55S57VVNni1gzZCh5qpznYLjIQELNh50VIozEfY6fvaQ==", + "license": "MIT", + "dependencies": { + "css-to-react-native": "^3.0.0", + "markdown-it": "^10.0.0", + "prop-types": "^15.7.2", + "react-native-fit-image": "^1.5.5" + }, + "peerDependencies": { + "react": ">=16.2.0", + "react-native": ">=0.50.4" + } + }, "node_modules/react-native-paper": { "version": "5.14.5", "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.14.5.tgz", @@ -14545,6 +14646,12 @@ "node": "*" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "license": "MIT" + }, "node_modules/undici": { "version": "7.15.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz", diff --git a/package.json b/package.json index 6521a3f..30ba791 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "react-native-get-random-values": "^1.11.0", "react-native-image-colors": "^2.5.0", "react-native-immersive-mode": "^2.0.2", + "react-native-markdown-display": "^7.0.2", "react-native-paper": "^5.13.1", "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", @@ -67,8 +68,8 @@ "react-native-url-polyfill": "^2.0.0", "react-native-video": "^6.12.0", "react-native-vlc-media-player": "^1.0.87", - "react-native-wheel-color-picker": "^1.3.1", - "react-native-web": "~0.19.13" + "react-native-web": "~0.19.13", + "react-native-wheel-color-picker": "^1.3.1" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/src/screens/AIChatScreen.tsx b/src/screens/AIChatScreen.tsx index fbc8170..6044386 100644 --- a/src/screens/AIChatScreen.tsx +++ b/src/screens/AIChatScreen.tsx @@ -20,6 +20,7 @@ import { useTheme } from '../contexts/ThemeContext'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { aiService, ChatMessage, ContentContext, createMovieContext, createEpisodeContext, generateConversationStarters } from '../services/aiService'; import { tmdbService } from '../services/tmdbService'; +import Markdown from 'react-native-markdown-display'; import Animated, { useAnimatedStyle, useSharedValue, @@ -105,12 +106,126 @@ const ChatBubble: React.FC = ({ message, isLast }) => { { backgroundColor: currentTheme.colors.elevation2 } ] ]}> - - {message.content} - + {isUser ? ( + + {message.content} + + ) : ( + + {message.content} + + )} { if ('showTitle' in context) { const sxe = messageText.match(/s(\d+)e(\d+)/i); const words = messageText.match(/season\s+(\d+)[^\d]+episode\s+(\d+)/i); - const season = sxe ? parseInt(sxe[1], 10) : (words ? parseInt(words[1], 10) : undefined); - const episode = sxe ? parseInt(sxe[2], 10) : (words ? parseInt(words[2], 10) : undefined); + const seasonOnly = messageText.match(/s(\d+)(?!e)/i) || messageText.match(/season\s+(\d+)/i); + + let season = sxe ? parseInt(sxe[1], 10) : (words ? parseInt(words[1], 10) : undefined); + let episode = sxe ? parseInt(sxe[2], 10) : (words ? parseInt(words[2], 10) : undefined); + + // If only season mentioned (like "s2" or "season 2"), default to episode 1 + if (!season && seasonOnly) { + season = parseInt(seasonOnly[1], 10); + episode = 1; + } if (season && episode) { try { diff --git a/src/services/aiService.ts b/src/services/aiService.ts index be2b68a..92482e2 100644 --- a/src/services/aiService.ts +++ b/src/services/aiService.ts @@ -12,6 +12,7 @@ export interface MovieContext { title: string; overview: string; releaseDate: string; + released?: boolean; genres: string[]; cast: Array<{ name: string; @@ -104,15 +105,18 @@ class AIService { if (isEpisode) { const ep = context as EpisodeContext; - return `You are an AI assistant specialized in TV shows and episodes. You have detailed knowledge about "${ep.showTitle}" Season ${ep.seasonNumber}, Episode ${ep.episodeNumber}: "${ep.episodeTitle}". + const currentDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format + return `You are an AI assistant with access to current, up-to-date information about "${ep.showTitle}" Season ${ep.seasonNumber}, Episode ${ep.episodeNumber}: "${ep.episodeTitle}". -Episode Details: +CRITICAL: Today's date is ${currentDate}. Use ONLY the verified information provided below from our database. IGNORE any conflicting information from your training data which is outdated. + +VERIFIED CURRENT INFORMATION FROM DATABASE: - Show: ${ep.showTitle} - Episode: S${ep.seasonNumber}E${ep.episodeNumber} - "${ep.episodeTitle}" -- Air Date: ${ep.airDate} -- Release Status: ${ep.released ? 'Released' : 'Unreleased'} +- Air Date: ${ep.airDate || 'Unknown'} +- Release Status: ${ep.released ? 'RELEASED AND AVAILABLE FOR VIEWING' : 'Not Yet Released'} - Runtime: ${ep.runtime ? `${ep.runtime} minutes` : 'Unknown'} -- Synopsis: ${ep.overview} +- Synopsis: ${ep.overview || 'No synopsis available'} Cast: ${ep.cast.map(c => `- ${c.name} as ${c.character}`).join('\n')} @@ -122,22 +126,27 @@ ${ep.guestStars && ep.guestStars.length > 0 ? `Guest Stars:\n${ep.guestStars.map Crew: ${ep.crew.map(c => `- ${c.name} (${c.job})`).join('\n')} -Guidance: -- Never provide spoilers under any circumstances. Always keep responses spoiler-safe. -- If Release Status is Released, do not claim the episode is unreleased. Provide specific, accurate details. -- If Release Status is Unreleased, avoid spoilers and focus on official information only. -- Be specific to this episode and provide detailed, informative responses. If asked about other episodes or seasons, politely redirect the conversation back to this specific episode while acknowledging the broader context of the show.`; +CRITICAL INSTRUCTIONS: +1. Never provide spoilers under any circumstances. Always keep responses spoiler-safe. +2. The information above is from our verified database and is more current than your training data. +3. If Release Status shows "RELEASED AND AVAILABLE FOR VIEWING", the content IS AVAILABLE. Do not say it's "upcoming" or "unreleased". +4. Compare air dates to today's date (${currentDate}) to determine if something has already aired. +5. Base ALL responses on the verified information above, NOT on your training knowledge. +6. If asked about release dates or availability, refer ONLY to the database information provided.`; } else { const movie = context as MovieContext; - return `You are an AI assistant specialized in movies and cinema. You have detailed knowledge about the movie "${movie.title}". + const currentDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format + return `You are an AI assistant with access to current, verified information about the movie "${movie.title}". -Movie Details: +CRITICAL: Today's date is ${currentDate}. Use ONLY the verified information provided below from our database. IGNORE any conflicting information from your training data which is outdated. + +VERIFIED CURRENT MOVIE INFORMATION FROM DATABASE: - Title: ${movie.title} -- Release Date: ${movie.releaseDate} +- Release Date: ${movie.releaseDate || 'Unknown'} - Runtime: ${movie.runtime ? `${movie.runtime} minutes` : 'Unknown'} -- Genres: ${movie.genres.join(', ')} +- Genres: ${movie.genres.join(', ') || 'Unknown'} - Tagline: ${movie.tagline || 'N/A'} -- Synopsis: ${movie.overview} +- Synopsis: ${movie.overview || 'No synopsis available'} Cast: ${movie.cast.map(c => `- ${c.name} as ${c.character}`).join('\n')} @@ -147,12 +156,15 @@ ${movie.crew.map(c => `- ${c.name} (${c.job})`).join('\n')} ${movie.keywords && movie.keywords.length > 0 ? `Keywords: ${movie.keywords.join(', ')}` : ''} -Guidance: -- Never provide spoilers under any circumstances. Always keep responses spoiler-safe. -- You can discuss themes, production, performances, and high-level plot setup without revealing twists, surprises, or outcomes. -- If users explicitly request spoilers, refuse gently and offer a spoiler-safe summary or analysis instead. +CRITICAL INSTRUCTIONS: +1. Never provide spoilers under any circumstances. Always keep responses spoiler-safe. +2. The information above is from our verified database and is more current than your training data. +3. Use the release date and today's date (${currentDate}) to determine availability - don't contradict database information. +4. Base ALL responses on the verified information above, NOT on your training knowledge. +5. If asked about release dates or availability, refer ONLY to the database information provided. +6. You can discuss themes, production, performances, and high-level plot setup without revealing twists, surprises, or outcomes. -You should answer questions about this movie, including plot analysis, character development, themes, cinematography, production notes, trivia, and critical analysis. Provide detailed, informative responses that demonstrate deep knowledge of the film while remaining spoiler-safe. Be specific and focus on this particular movie.`; +Answer questions about this movie using only the verified database information above, including plot analysis, character development, themes, cinematography, production notes, and trivia. Provide detailed, informative responses while remaining spoiler-safe.`; } } @@ -200,7 +212,7 @@ You should answer questions about this movie, including plot analysis, character 'X-Title': 'Nuvio - AI Chat', }, body: JSON.stringify({ - model: 'anthropic/claude-3.5-sonnet', // Using Claude for better analysis + model: 'openrouter/sonoma-dusk-alpha', messages, max_tokens: 1000, temperature: 0.7, @@ -246,7 +258,44 @@ You should answer questions about this movie, including plot analysis, character crewCount: movieData.credits?.crew?.length || 0, hasKeywords: !!(movieData.keywords?.keywords || movieData.keywords?.results), keywordCount: (movieData.keywords?.keywords || movieData.keywords?.results)?.length || 0, - genreCount: movieData.genres?.length || 0 + genreCount: movieData.genres?.length || 0, + tmdbStatus: movieData.status, + tmdbReleaseDate: movieData.release_date, + tmdbReleaseDatesBlock: !!movieData.release_dates + }); + } + + // Prefer US theatrical release date from release_dates if available + let releaseDate: string = movieData.release_date || movieData.first_air_date || ''; + try { + const groups = movieData.release_dates?.results as any[] | undefined; + const us = groups?.find(g => g.iso_3166_1 === 'US'); + const theatric = us?.release_dates?.find((r: any) => r.type === 3 || r.type === 2 || r.type === 4); + const anyDate = us?.release_dates?.[0]?.release_date || theatric?.release_date; + if (anyDate) { + // TMDB returns full ISO timestamps; keep only date part + releaseDate = String(anyDate).split('T')[0]; + } + } catch {} + const statusText: string = (movieData.status || '').toString().toLowerCase(); + let released = statusText === 'released'; + if (!released && releaseDate) { + const d = new Date(releaseDate); + if (!isNaN(d.getTime())) released = d.getTime() <= Date.now(); + } + if (!released) { + const hasOverview = typeof movieData.overview === 'string' && movieData.overview.trim().length > 40; + const hasRuntime = typeof movieData.runtime === 'number' && movieData.runtime > 0; + const hasVotes = typeof movieData.vote_average === 'number' && movieData.vote_average > 0; + if (hasOverview || hasRuntime || hasVotes) released = true; + } + + if (__DEV__) { + console.log('[AIService] Movie release resolution:', { + resolvedReleaseDate: releaseDate, + statusText: (movieData.status || '').toString(), + computedReleased: released, + today: new Date().toISOString().split('T')[0] }); } @@ -254,7 +303,8 @@ You should answer questions about this movie, including plot analysis, character id: movieData.id?.toString() || '', title: movieData.title || movieData.name || '', overview: movieData.overview || '', - releaseDate: movieData.release_date || movieData.first_air_date || '', + releaseDate, + released, genres: movieData.genres?.map((g: any) => g.name) || [], cast: movieData.credits?.cast?.slice(0, 10).map((c: any) => ({ name: c.name, diff --git a/src/services/tmdbService.ts b/src/services/tmdbService.ts index 0575724..3ce3c91 100644 --- a/src/services/tmdbService.ts +++ b/src/services/tmdbService.ts @@ -587,7 +587,7 @@ export class TMDBService { headers: await this.getHeaders(), params: await this.getParams({ language: 'en-US', - append_to_response: 'external_ids,credits,keywords' // Append external IDs, cast/crew, and keywords for AI context + append_to_response: 'external_ids,credits,keywords,release_dates' // Include release dates for accurate availability }), }); return response.data;