From 8698ca54814d85c934bf46ab6e4763c11bdfcdf7 Mon Sep 17 00:00:00 2001 From: Pankaj Sharma <8818473+iamphoenix310@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:42:24 +0530 Subject: [PATCH 1/2] Fix type errors for AI response --- src/app/api/chat/index+api.ts | 25 +++++++++++++++++++++++++ src/app/api/chat/speech+api.ts | 25 +++++++++++++++++++++++++ src/app/chat/[id].tsx | 30 ++++++++++++++++++++++-------- src/app/index.tsx | 19 ++++++++++++------- src/components/MessageListItem.tsx | 23 ++++++++++++++++++++--- src/services/chatService.ts | 18 +++++++++++++----- src/types/types.ts | 1 + 7 files changed, 118 insertions(+), 23 deletions(-) diff --git a/src/app/api/chat/index+api.ts b/src/app/api/chat/index+api.ts index 54d4145..4693d2e 100644 --- a/src/app/api/chat/index+api.ts +++ b/src/app/api/chat/index+api.ts @@ -26,9 +26,34 @@ export async function POST(request: Request) { ...(previousResponseId && { previous_response_id: previousResponseId }), }); + let relatedQuestions: string[] | undefined = undefined; + try { + const questionsCompletion = await openai.chat.completions.create({ + model: 'gpt-4.1', + messages: [ + { + role: 'user', + content: + `Provide 3 advanced follow-up questions related to: ${message}. ` + + 'Respond in JSON format as { "questions": ["q1","q2","q3"] }.', + }, + ], + response_format: { type: 'json_object' }, + }); + const parsed = JSON.parse( + questionsCompletion.choices[0].message.content || '{}' + ); + if (Array.isArray(parsed.questions)) { + relatedQuestions = parsed.questions; + } + } catch (err) { + console.error('Failed to generate related questions:', err); + } + return Response.json({ responseMessage: response.output_text, responseId: response.id, + ...(relatedQuestions && { relatedQuestions }), }); } catch (error) { console.error('OpenAI error:', error); diff --git a/src/app/api/chat/speech+api.ts b/src/app/api/chat/speech+api.ts index 3ce2091..7b808a9 100644 --- a/src/app/api/chat/speech+api.ts +++ b/src/app/api/chat/speech+api.ts @@ -21,10 +21,35 @@ export async function POST(request: Request) { ...(previousResponseId && { previous_response_id: previousResponseId }), }); + let relatedQuestions: string[] | undefined = undefined; + try { + const questionsCompletion = await openai.chat.completions.create({ + model: 'gpt-4.1', + messages: [ + { + role: 'user', + content: + `Provide 3 advanced follow-up questions related to: ${transcription.text}. ` + + 'Respond in JSON format as { "questions": ["q1","q2","q3"] }.', + }, + ], + response_format: { type: 'json_object' }, + }); + const parsed = JSON.parse( + questionsCompletion.choices[0].message.content || '{}' + ); + if (Array.isArray(parsed.questions)) { + relatedQuestions = parsed.questions; + } + } catch (err) { + console.error('Failed to generate related questions:', err); + } + return Response.json({ responseId: response.id, responseMessage: response.output_text, transcribedMessage: transcription.text, + ...(relatedQuestions && { relatedQuestions }), }); } catch (error) { console.log(error); diff --git a/src/app/chat/[id].tsx b/src/app/chat/[id].tsx index 7bb92f7..e56bcdf 100644 --- a/src/app/chat/[id].tsx +++ b/src/app/chat/[id].tsx @@ -9,7 +9,9 @@ import { getTextResponse, createAIImage, getSpeechResponse, + type ChatResponse, } from '@/services/chatService'; +import type { Message } from '@/types/types'; export default function ChatScreen() { const { id } = useLocalSearchParams(); @@ -29,6 +31,10 @@ export default function ChatScreen() { const addNewMessage = useChatStore((state) => state.addNewMessage); + const handleQuestionPress = async (question: string) => { + await handleSend(question, null, false, null); + }; + useEffect(() => { const timeout = setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: true }); @@ -60,7 +66,7 @@ export default function ChatScreen() { )?.responseId; try { - let data; + let data: ChatResponse | { image: string }; if (audioBase64) { data = await getSpeechResponse(audioBase64, previousResponseId); const myMessage = { @@ -75,13 +81,16 @@ export default function ChatScreen() { data = await getTextResponse(message, imageBase64, previousResponseId); } - const aiResponseMessage = { + const aiResponseMessage: Message = { id: Date.now().toString(), - message: data.responseMessage, - responseId: data.responseId, - image: data.image, - role: 'assistant' as const, - }; + role: 'assistant', + ...("responseMessage" in data && { + message: data.responseMessage, + responseId: data.responseId, + relatedQuestions: data.relatedQuestions, + }), + ...("image" in data && { image: data.image }), + } as Message; addNewMessage(chat.id, aiResponseMessage); } catch (error) { @@ -104,7 +113,12 @@ export default function ChatScreen() { } + renderItem={({ item }) => ( + + )} ListFooterComponent={() => isWaitingForResponse && ( diff --git a/src/app/index.tsx b/src/app/index.tsx index 9efbb90..3540e7f 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -7,7 +7,9 @@ import { createAIImage, getSpeechResponse, getTextResponse, + type ChatResponse, } from '@/services/chatService'; +import type { Message } from '@/types/types'; export default function HomeScreen() { const createNewChat = useChatStore((state) => state.createNewChat); @@ -36,7 +38,7 @@ export default function HomeScreen() { router.push(`/chat/${newChatId}`); try { - let data; + let data: ChatResponse | { image: string }; if (audioBase64) { data = await getSpeechResponse(audioBase64); const myMessage = { @@ -51,13 +53,16 @@ export default function HomeScreen() { data = await getTextResponse(message, imageBase64); } - const aiResponseMessage = { + const aiResponseMessage: Message = { id: Date.now().toString(), - message: data.responseMessage, - responseId: data.responseId, - image: data.image, - role: 'assistant' as const, - }; + role: 'assistant', + ...("responseMessage" in data && { + message: data.responseMessage, + responseId: data.responseId, + relatedQuestions: data.relatedQuestions, + }), + ...("image" in data && { image: data.image }), + } as Message; addNewMessage(newChatId, aiResponseMessage); diff --git a/src/components/MessageListItem.tsx b/src/components/MessageListItem.tsx index bdb7ec0..a876d23 100644 --- a/src/components/MessageListItem.tsx +++ b/src/components/MessageListItem.tsx @@ -1,14 +1,18 @@ -import { Text, View, Image } from 'react-native'; +import { Text, View, Image, Pressable } from 'react-native'; import Markdown from 'react-native-markdown-display'; import { Message } from '@/types/types'; import { markdownStyles } from '@/utils/markdown'; interface MessageListItemProps { messageItem: Message; + onQuestionPress?: (question: string) => void; } -export default function MessageListItem({ messageItem }: MessageListItemProps) { - const { message, role, image } = messageItem; +export default function MessageListItem({ + messageItem, + onQuestionPress, +}: MessageListItemProps) { + const { message, role, image, relatedQuestions } = messageItem; const isUser = role === 'user'; return ( @@ -32,6 +36,19 @@ export default function MessageListItem({ messageItem }: MessageListItemProps) { {message} )} + {!isUser && relatedQuestions?.length ? ( + + {relatedQuestions.map((q, idx) => ( + onQuestionPress && onQuestionPress(q)} + className='bg-[#262626] rounded-lg p-2' + > + {q} + + ))} + + ) : null} ); } diff --git a/src/services/chatService.ts b/src/services/chatService.ts index 0e4bfd4..586de99 100644 --- a/src/services/chatService.ts +++ b/src/services/chatService.ts @@ -1,4 +1,12 @@ -export async function createAIImage(prompt: string) { +export interface ChatResponse { + responseMessage: string; + responseId: string; + image?: string; + transcribedMessage?: string; + relatedQuestions?: string[]; +} + +export async function createAIImage(prompt: string): Promise<{ image: string }> { const res = await fetch('/api/chat/createImage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -14,7 +22,7 @@ export const getTextResponse = async ( message: string, imageBase64: string | null, previousResponseId?: string -) => { +): Promise => { const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -23,13 +31,13 @@ export const getTextResponse = async ( const data = await res.json(); if (!res.ok) throw new Error(data.error); - return data; + return data as ChatResponse; }; export const getSpeechResponse = async ( audioBase64: string, previousResponseId?: string -) => { +): Promise => { const res = await fetch('/api/chat/speech', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -38,5 +46,5 @@ export const getSpeechResponse = async ( const data = await res.json(); if (!res.ok) throw new Error(data.error); - return data; + return data as ChatResponse; }; diff --git a/src/types/types.ts b/src/types/types.ts index 522ea6d..d44919c 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -4,6 +4,7 @@ export interface Message { message?: string; responseId?: string; image?: string; + relatedQuestions?: string[]; } export interface Chat { From 212cdd2847d79972a4545885cdcb2891eb6da514 Mon Sep 17 00:00:00 2001 From: Pankaj Sharma <8818473+iamphoenix310@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:49:14 +0530 Subject: [PATCH 2/2] Style related questions list --- src/components/MessageListItem.tsx | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/MessageListItem.tsx b/src/components/MessageListItem.tsx index a876d23..90ed824 100644 --- a/src/components/MessageListItem.tsx +++ b/src/components/MessageListItem.tsx @@ -37,16 +37,24 @@ export default function MessageListItem({ )} {!isUser && relatedQuestions?.length ? ( - - {relatedQuestions.map((q, idx) => ( - onQuestionPress && onQuestionPress(q)} - className='bg-[#262626] rounded-lg p-2' - > - {q} - - ))} + + + Related questions + + + {relatedQuestions.map((q, idx) => { + const display = q.length > 80 ? `${q.slice(0, 77)}...` : q; + return ( + onQuestionPress?.(q)} + className='bg-[#262626] rounded-lg p-2' + > + {display} + + ); + })} + ) : null}