@@ -15,7 +15,7 @@ type MessageContent =
1515 | { type : "text" ; text : string }
1616 | { type : "image_url" ; image_url : { url : string } } ;
1717
18- type Msg = {
18+ type ChatMessage = {
1919 role : "user" | "assistant" ;
2020 content : string | MessageContent [ ] ;
2121 persona ?: string ;
@@ -28,9 +28,9 @@ const CHAT_URL = `${SUPABASE_URL}/functions/v1/beauty-assistant`;
2828function getTextContent ( content : string | MessageContent [ ] ) : string {
2929 if ( typeof content === "string" ) return content ;
3030 return content
31- . filter ( ( p ) : p is { type : "text" ; text : string } => ( p as { type : string } ) . type === "text" )
32- . filter ( ( p ) : p is { type : "text" ; text : string } => typeof p === "object" && p !== null && "type" in p && p . type === "text" )
33- . map ( ( p ) => p . text )
31+ . filter ( ( contentPart ) : contentPart is { type : "text" ; text : string } => ( contentPart as { type : string } ) . type === "text" )
32+ . filter ( ( contentPart ) : contentPart is { type : "text" ; text : string } => typeof contentPart === "object" && contentPart !== null && "type" in contentPart && contentPart . type === "text" )
33+ . map ( ( contentPart ) => contentPart . text )
3434 . join ( " " ) ;
3535}
3636
@@ -44,25 +44,25 @@ async function streamChat({
4444 onDone,
4545 onSafetyFlags,
4646} : {
47- messages : Msg [ ] ;
47+ messages : ChatMessage [ ] ;
4848 forcePersona ?: string ;
4949 userProfile ?: { skin_type : string | null ; skin_concern : string ; tags : string [ ] } | null ;
5050 campaignSource ?: string | null ;
51- onPersona : ( p : string ) => void ;
51+ onPersona : ( persona : string ) => void ;
5252 onDelta : ( text : string ) => void ;
5353 onDone : ( ) => void ;
5454 onSafetyFlags ?: ( flags : string [ ] ) => void ;
5555} ) {
56- const payload = messages . map ( ( m ) => ( {
57- role : m . role ,
58- content : m . content ,
56+ const payload = messages . map ( ( chatMessage ) => ( {
57+ role : chatMessage . role ,
58+ content : chatMessage . content ,
5959 } ) ) ;
6060
6161 const { data : { session } } = await supabase . auth . getSession ( ) ;
6262 const token = session ?. access_token ;
6363 if ( ! token ) throw new Error ( "Please sign in to use the AI concierge." ) ;
6464
65- const resp = await fetch ( CHAT_URL , {
65+ const chatResponse = await fetch ( CHAT_URL , {
6666 method : "POST" ,
6767 headers : {
6868 "Content-Type" : "application/json" ,
@@ -71,37 +71,37 @@ async function streamChat({
7171 body : JSON . stringify ( { messages : payload , forcePersona, userProfile, ...( campaignSource ? { source : campaignSource } : { } ) } ) ,
7272 } ) ;
7373
74- if ( ! resp . ok ) {
75- const err = await resp . json ( ) . catch ( ( ) => ( { error : "Connection failed" } ) ) ;
76- throw new Error ( err . error || `Error ${ resp . status } ` ) ;
74+ if ( ! chatResponse . ok ) {
75+ const errorResponse = await chatResponse . json ( ) . catch ( ( ) => ( { error : "Connection failed" } ) ) ;
76+ throw new Error ( errorResponse . error || `Error ${ chatResponse . status } ` ) ;
7777 }
7878
79- const persona = resp . headers . get ( "X-Persona" ) ;
79+ const persona = chatResponse . headers . get ( "X-Persona" ) ;
8080 if ( persona ) onPersona ( persona ) ;
8181
82- const safetyFlags = resp . headers . get ( "X-Safety-Flags" ) ;
82+ const safetyFlags = chatResponse . headers . get ( "X-Safety-Flags" ) ;
8383 if ( safetyFlags && safetyFlags !== "none" && onSafetyFlags ) {
8484 onSafetyFlags ( safetyFlags . split ( "," ) ) ;
8585 }
8686
87- if ( ! resp . body ) throw new Error ( "No response body" ) ;
87+ if ( ! chatResponse . body ) throw new Error ( "No response body" ) ;
8888
89- const reader = resp . body . getReader ( ) ;
89+ const reader = chatResponse . body . getReader ( ) ;
9090 const decoder = new TextDecoder ( ) ;
91- let buffer = "" ;
91+ let streamBuffer = "" ;
9292
9393 while ( true ) {
9494 const { done, value } = await reader . read ( ) ;
9595 if ( done ) break ;
96- buffer += decoder . decode ( value , { stream : true } ) ;
97-
98- let idx : number ;
99- while ( ( idx = buffer . indexOf ( "\n" ) ) !== - 1 ) {
100- let line = buffer . slice ( 0 , idx ) ;
101- buffer = buffer . slice ( idx + 1 ) ;
102- if ( line . endsWith ( "\r" ) ) line = line . slice ( 0 , - 1 ) ;
103- if ( ! line . startsWith ( "data: " ) ) continue ;
104- const json = line . slice ( 6 ) . trim ( ) ;
96+ streamBuffer += decoder . decode ( value , { stream : true } ) ;
97+
98+ let newlineIndex : number ;
99+ while ( ( newlineIndex = streamBuffer . indexOf ( "\n" ) ) !== - 1 ) {
100+ let sseDataLine = streamBuffer . slice ( 0 , newlineIndex ) ;
101+ streamBuffer = streamBuffer . slice ( newlineIndex + 1 ) ;
102+ if ( sseDataLine . endsWith ( "\r" ) ) sseDataLine = sseDataLine . slice ( 0 , - 1 ) ;
103+ if ( ! sseDataLine . startsWith ( "data: " ) ) continue ;
104+ const json = sseDataLine . slice ( 6 ) . trim ( ) ;
105105 if ( json === "[DONE]" ) {
106106 onDone ( ) ;
107107 return ;
@@ -111,7 +111,7 @@ async function streamChat({
111111 const content = parsed . choices ?. [ 0 ] ?. delta ?. content ;
112112 if ( content ) onDelta ( content ) ;
113113 } catch {
114- buffer = line + "\n" + buffer ;
114+ streamBuffer = sseDataLine + "\n" + streamBuffer ;
115115 break ;
116116 }
117117 }
@@ -185,7 +185,7 @@ const quickPrompts = [
185185
186186export default function AIConcierge ( ) {
187187 const [ open , setOpen ] = useState ( false ) ;
188- const [ messages , setMessages ] = useState < Msg [ ] > ( [ ] ) ;
188+ const [ messages , setMessages ] = useState < ChatMessage [ ] > ( [ ] ) ;
189189 const [ input , setInput ] = useState ( "" ) ;
190190 const [ isLoading , setIsLoading ] = useState ( false ) ;
191191 const [ currentPersona , setCurrentPersona ] = useState < string > ( "ms_zain" ) ;
@@ -358,7 +358,7 @@ export default function AIConcierge() {
358358 userContent = text . trim ( ) ;
359359 }
360360
361- const userMsg : Msg = { role : "user" , content : userContent , imagePreview } ;
361+ const userMsg : ChatMessage = { role : "user" , content : userContent , imagePreview } ;
362362 setMessages ( ( prev ) => [ ...prev , userMsg ] ) ;
363363 setInput ( "" ) ;
364364 setIsLoading ( true ) ;
@@ -371,8 +371,8 @@ export default function AIConcierge() {
371371 setMessages ( ( prev ) => {
372372 const last = prev [ prev . length - 1 ] ;
373373 if ( last ?. role === "assistant" ) {
374- return prev . map ( ( m , i ) =>
375- i === prev . length - 1 ? { ...m , content : assistantSoFar , persona : detectedPersona } : m
374+ return prev . map ( ( chatMessage , messageIndex ) =>
375+ messageIndex === prev . length - 1 ? { ...chatMessage , content : assistantSoFar , persona : detectedPersona } : chatMessage
376376 ) ;
377377 }
378378 return [ ...prev , { role : "assistant" , content : assistantSoFar , persona : detectedPersona } ] ;
@@ -384,9 +384,9 @@ export default function AIConcierge() {
384384 messages : [ ...messages , userMsg ] ,
385385 userProfile,
386386 campaignSource : deepLinkSource . current ,
387- onPersona : ( p ) => {
388- detectedPersona = p ;
389- setCurrentPersona ( p ) ;
387+ onPersona : ( detectedPersonaId ) => {
388+ detectedPersona = detectedPersonaId ;
389+ setCurrentPersona ( detectedPersonaId ) ;
390390 } ,
391391 onDelta : upsert ,
392392 onDone : ( ) => { setIsLoading ( false ) ; playNotificationSound ( ) ; } ,
0 commit comments