@@ -4,6 +4,7 @@ import * as ejs from 'ejs';
44import * as path from 'path' ;
55import Anthropic from '@anthropic-ai/sdk' ;
66import { googleCalendarService , CreateEventData } from './services/googleCalendarService' ;
7+ import { gmailService } from './services/gmailService' ;
78
89/**
910 * Interface representing a stored photo with metadata
@@ -382,15 +383,28 @@ class ExampleMentraOSApp extends AppServer {
382383 */
383384 private async getRecentEmailsForContext ( userId : string ) : Promise < any [ ] > {
384385 try {
385- // This would integrate with the Gmail service from the dashboard
386- // For now, return empty array as placeholder
387386 this . logger . debug ( `Getting recent emails for context for user ${ userId } ` ) ;
388387
389- // TODO: Implement integration with gmailService from dashboard-sep
390- // const gmailService = require('./dashboard-sep/src/services/gmailService');
391- // return await gmailService.getUnrepliedEmails();
388+ if ( ! gmailService . isUserAuthorized ( userId ) ) {
389+ this . logger . debug ( `User ${ userId } not authorized for Gmail access` ) ;
390+ return [ ] ;
391+ }
392392
393- return [ ] ;
393+ // Get recent emails from Gmail API
394+ const recentEmails = await gmailService . getRecentEmails ( userId , 5 ) ;
395+
396+ // Convert to context format for email processing
397+ const emailContext = recentEmails . map ( email => ( {
398+ id : email . id ,
399+ subject : email . subject ,
400+ from : email . from ,
401+ snippet : email . snippet ,
402+ date : email . date ,
403+ isRead : email . isRead
404+ } ) ) ;
405+
406+ this . logger . debug ( `Retrieved ${ emailContext . length } emails for context` ) ;
407+ return emailContext ;
394408 } catch ( error ) {
395409 this . logger . warn ( 'Failed to get recent emails for context:' , error ) ;
396410 return [ ] ;
@@ -805,71 +819,79 @@ class ExampleMentraOSApp extends AppServer {
805819 try {
806820 session . logger . info ( `Processing email request: ${ spokenText } ` ) ;
807821
808- const requestBody = {
809- messages : [
810- {
811- role : 'user' ,
812- content : spokenText
813- }
814- ] ,
815- context_emails : await this . getRecentEmailsForContext ( userId ) // Populate with recent emails for context
816- } ;
822+ // Check if user is authorized for Gmail access
823+ if ( ! gmailService . isUserAuthorized ( userId ) ) {
824+ const authUrl = googleCalendarService . generateAuthUrl ( userId ) ;
825+ if ( authUrl ) {
826+ session . layouts . showTextWall ( "🔐 Please authorize Gmail access" , { durationMs : 5000 } ) ;
827+ session . audio . speak ( "Please authorize Gmail access to use email features. Check your dashboard for the authorization link." , {
828+ voice_settings : { stability : 0.8 , speed : 0.95 }
829+ } ) ;
830+ return ;
831+ } else {
832+ throw new Error ( 'Gmail not configured properly' ) ;
833+ }
834+ }
817835
818- // Add timeout to prevent hanging
819- const controller = new AbortController ( ) ;
820- const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 20000 ) ; // 20 second timeout for email processing
836+ // Get recent emails for context
837+ const contextEmails = await this . getRecentEmailsForContext ( userId ) ;
838+ session . logger . info ( `Got ${ contextEmails . length } emails for context` ) ;
821839
822- const result = await this . retryApiCall ( async ( ) => {
823- const response = await fetch ( 'https://email.globalstarxyz.com/chat' , {
824- method : 'POST' ,
825- headers : {
826- 'Content-Type' : 'application/json'
827- } ,
828- body : JSON . stringify ( requestBody ) ,
829- signal : controller . signal
830- } ) ;
840+ // Process the email request locally based on common email actions
841+ const lowerText = spokenText . toLowerCase ( ) ;
842+ let responseMessage = "" ;
843+ let actionPerformed = false ;
831844
832- clearTimeout ( timeoutId ) ;
845+ if ( lowerText . includes ( 'check' ) || lowerText . includes ( 'inbox' ) || lowerText . includes ( 'unread' ) ) {
846+ // Check email summary
847+ const unreadCount = await gmailService . getUnreadCount ( userId ) ;
848+ const recentEmails = await gmailService . getRecentEmails ( userId , 3 ) ;
833849
834- if ( ! response . ok ) {
835- const errorText = await response . text ( ) . catch ( ( ) => 'Unknown error' ) ;
836- throw new Error ( `Email agent API error: ${ response . status } ${ response . statusText } - ${ errorText } ` ) ;
837- }
850+ if ( unreadCount === 0 ) {
851+ responseMessage = "You have no unread emails. Your inbox is all caught up!" ;
852+ } else {
853+ const emailSummary = recentEmails . slice ( 0 , 2 ) . map ( email =>
854+ `${ email . from } : ${ email . subject } `
855+ ) . join ( '. ' ) ;
838856
839- return await response . json ( ) ;
840- } ) ;
841- session . logger . info ( `Email agent response: ${ JSON . stringify ( result ) } ` ) ;
857+ responseMessage = `You have ${ unreadCount } unread email ${ unreadCount > 1 ? 's' : '' } . Recent messages: ${ emailSummary } ` ;
858+ }
859+ actionPerformed = true ;
842860
843- // Validate response structure
844- if ( ! result || typeof result !== 'object' ) {
845- throw new Error ( 'Invalid response from email agent' ) ;
846- }
861+ } else if ( lowerText . includes ( 'reply' ) || lowerText . includes ( 'respond' ) ) {
862+ // Reply functionality
863+ responseMessage = "To reply to emails, please use the dashboard interface or specify which email you'd like to reply to." ;
864+ actionPerformed = true ;
847865
848- // Show response to user with fallback message
849- const responseMessage = result . assistant_reply || result . response || 'Email request processed' ;
850- session . layouts . showTextWall ( `📧 ${ responseMessage } ` , { durationMs : 8000 } ) ;
866+ } else if ( lowerText . includes ( 'send' ) || lowerText . includes ( 'compose' ) || lowerText . includes ( 'write' ) ) {
867+ // Send/compose functionality
868+ responseMessage = "To compose and send emails, please use the dashboard interface where you can specify recipients and content." ;
869+ actionPerformed = true ;
851870
852- // Play audio response through glasses speakers using ElevenLabs TTS
853- try {
854- await session . audio . speak ( responseMessage , {
855- voice_settings : {
856- stability : 0.6 , // Slightly more variation for email responses
857- similarity_boost : 0.8 , // Natural sounding
858- style : 0.3 , // More expressive for email interactions
859- speed : 0.85 // Slightly faster for email responses
860- }
861- } ) ;
862- session . logger . info ( 'Email response played via TTS' ) ;
863- } catch ( ttsError ) {
864- session . logger . warn ( 'Failed to play email response via TTS:' , ttsError ) ;
865- // TTS failure shouldn't break the main functionality
871+ } else {
872+ // Generic email summary
873+ const unreadCount = await gmailService . getUnreadCount ( userId ) ;
874+ if ( contextEmails . length > 0 ) {
875+ const latestEmail = contextEmails [ 0 ] ;
876+ responseMessage = `Latest email from ${ latestEmail . from } : ${ latestEmail . subject } . You have ${ unreadCount } unread emails total.` ;
877+ } else {
878+ responseMessage = `You have ${ unreadCount } unread emails. Use the dashboard to manage your email.` ;
879+ }
880+ actionPerformed = true ;
866881 }
867882
868- // Process actions if any (email sending, replies, etc.)
869- if ( result . actions && Array . isArray ( result . actions ) && result . actions . length > 0 ) {
870- session . logger . info ( `Processing ${ result . actions . length } email actions` ) ;
871- for ( const action of result . actions ) {
872- await this . processEmailAction ( action , session , userId ) ;
883+ if ( actionPerformed ) {
884+ // Show response to user
885+ session . layouts . showTextWall ( `📧 ${ responseMessage } ` , { durationMs : 8000 } ) ;
886+
887+ // Play audio response through glasses speakers
888+ try {
889+ await session . audio . speak ( responseMessage , {
890+ voice_settings : { stability : 0.6 , speed : 0.85 }
891+ } ) ;
892+ session . logger . info ( 'Email response played via TTS' ) ;
893+ } catch ( audioError ) {
894+ session . logger . warn ( 'Failed to play email response TTS:' , audioError ) ;
873895 }
874896 }
875897
@@ -879,32 +901,24 @@ class ExampleMentraOSApp extends AppServer {
879901 // Provide specific error feedback to user
880902 let errorMessage = "❌ Email request failed" ;
881903 if ( error instanceof Error ) {
882- if ( error . name === 'AbortError' ) {
883- errorMessage = "⏱️ Email request timed out. Please try again." ;
884- } else if ( error . message . includes ( 'fetch' ) ) {
885- errorMessage = "🌐 Unable to connect to email service. Check your internet connection." ;
886- } else if ( error . message . includes ( '404' ) ) {
887- errorMessage = "🔍 Email service not found. Please contact support." ;
888- } else if ( error . message . includes ( '500' ) ) {
889- errorMessage = "⚙️ Email service temporarily unavailable. Try again in a moment." ;
890- } else if ( error . message . includes ( 'authorization' ) || error . message . includes ( '401' ) ) {
891- errorMessage = "🔒 Email authentication required. Please set up Gmail access." ;
904+ if ( error . message . includes ( 'not authorized' ) || error . message . includes ( 'Gmail not configured' ) ) {
905+ errorMessage = "🔐 Gmail authorization required" ;
906+ } else if ( error . message . includes ( 'quota' ) || error . message . includes ( 'rate limit' ) ) {
907+ errorMessage = "⏱️ Gmail rate limit reached" ;
892908 }
893909 }
894910
911+ // Display error on glasses
895912 session . layouts . showTextWall ( errorMessage , { durationMs : 5000 } ) ;
896913
897914 // Play error message via TTS
898915 try {
899- // Simplify error message for audio
900916 let audioErrorMessage = "Sorry, there was an issue with your email request." ;
901917 if ( error instanceof Error ) {
902- if ( error . name === 'AbortError' ) {
903- audioErrorMessage = "Email request timed out. Please try again." ;
904- } else if ( error . message . includes ( 'fetch' ) ) {
905- audioErrorMessage = "Unable to connect to email service." ;
906- } else if ( error . message . includes ( '401' ) ) {
907- audioErrorMessage = "Email authentication required. Please set up Gmail access." ;
918+ if ( error . message . includes ( 'not authorized' ) || error . message . includes ( 'Gmail not configured' ) ) {
919+ audioErrorMessage = "Please authorize Gmail access first to use email features." ;
920+ } else if ( error . message . includes ( 'quota' ) || error . message . includes ( 'rate limit' ) ) {
921+ audioErrorMessage = "Gmail rate limit reached. Please try again later." ;
908922 }
909923 }
910924
@@ -1224,13 +1238,17 @@ class ExampleMentraOSApp extends AppServer {
12241238 */
12251239 private async createGoogleCalendarEvent ( userId : string , eventData : any ) : Promise < void > {
12261240 try {
1241+ this . logger . info ( `[Calendar Auth Debug] Attempting to create event for user: ${ userId } ` ) ;
1242+
12271243 if ( ! googleCalendarService . isConfigured ( ) ) {
12281244 this . logger . warn ( 'Google Calendar not configured, skipping event creation' ) ;
12291245 return ;
12301246 }
12311247
12321248 if ( ! googleCalendarService . isUserAuthorized ( userId ) ) {
12331249 this . logger . warn ( `User ${ userId } not authorized with Google Calendar` ) ;
1250+ this . logger . info ( `[Calendar Auth Debug] Available authorized users: ${ JSON . stringify ( Array . from ( googleCalendarService . getAuthorizedUsers ( ) ) ) } ` ) ;
1251+ this . logger . info ( `[Calendar Auth Debug] Google Calendar service configured: ${ googleCalendarService . isConfigured ( ) } ` ) ;
12341252 return ;
12351253 }
12361254
@@ -1262,13 +1280,17 @@ class ExampleMentraOSApp extends AppServer {
12621280 */
12631281 private async fetchGoogleCalendarEvents ( userId : string , dateRange ?: any ) : Promise < void > {
12641282 try {
1283+ this . logger . info ( `[Calendar Auth Debug] Attempting to fetch events for user: ${ userId } ` ) ;
1284+
12651285 if ( ! googleCalendarService . isConfigured ( ) ) {
12661286 this . logger . warn ( 'Google Calendar not configured, skipping event fetch' ) ;
12671287 return ;
12681288 }
12691289
12701290 if ( ! googleCalendarService . isUserAuthorized ( userId ) ) {
12711291 this . logger . warn ( `User ${ userId } not authorized with Google Calendar` ) ;
1292+ this . logger . info ( `[Calendar Auth Debug] Available authorized users: ${ JSON . stringify ( Array . from ( googleCalendarService . getAuthorizedUsers ( ) ) ) } ` ) ;
1293+ this . logger . info ( `[Calendar Auth Debug] Google Calendar service configured: ${ googleCalendarService . isConfigured ( ) } ` ) ;
12721294 return ;
12731295 }
12741296
@@ -2251,14 +2273,33 @@ class ExampleMentraOSApp extends AppServer {
22512273 }
22522274
22532275 try {
2254- // Placeholder for email integration
2255- // In a real implementation, this would integrate with Gmail API
2256- const emails = await this . getRecentEmailsForContext ( userId ) ;
2276+ if ( ! gmailService . isUserAuthorized ( userId ) ) {
2277+ res . json ( {
2278+ emails : [ ] ,
2279+ unreadCount : 0 ,
2280+ lastSync : new Date ( ) . toISOString ( ) ,
2281+ authRequired : true ,
2282+ authUrl : googleCalendarService . generateAuthUrl ( userId )
2283+ } ) ;
2284+ return ;
2285+ }
2286+
2287+ // Get recent emails from Gmail API
2288+ const emails = await gmailService . getRecentEmails ( userId , 10 ) ;
2289+ const unreadCount = await gmailService . getUnreadCount ( userId ) ;
22572290
22582291 res . json ( {
2259- emails : emails ,
2260- unreadCount : 0 ,
2261- lastSync : new Date ( ) . toISOString ( )
2292+ emails : emails . map ( email => ( {
2293+ id : email . id ,
2294+ subject : email . subject ,
2295+ from : email . from ,
2296+ snippet : email . snippet ,
2297+ date : email . date ,
2298+ isRead : email . isRead
2299+ } ) ) ,
2300+ unreadCount : unreadCount ,
2301+ lastSync : new Date ( ) . toISOString ( ) ,
2302+ authRequired : false
22622303 } ) ;
22632304 } catch ( error ) {
22642305 this . logger . error ( 'Error getting emails:' , error ) ;
@@ -2347,15 +2388,44 @@ class ExampleMentraOSApp extends AppServer {
23472388 }
23482389 } ) ;
23492390
2391+ // Public Google OAuth authentication route (doesn't require prior auth)
2392+ app . get ( '/auth/google/start' , async ( req : any , res : any ) => {
2393+ // Get userId from query parameter or use email as default
2394+ const userId = req . query . userId || 'ryanrong24@gmail.com' ;
2395+
2396+ this . logger . info ( `[OAuth Start Debug] Starting Google auth for user: ${ userId } ` ) ;
2397+
2398+ if ( ! googleCalendarService . isConfigured ( ) ) {
2399+ this . logger . error ( '[OAuth Start Debug] Google Calendar not configured' ) ;
2400+ res . status ( 500 ) . json ( { error : 'Google Calendar not configured' } ) ;
2401+ return ;
2402+ }
2403+
2404+ const authUrl = googleCalendarService . generateAuthUrl ( userId ) ;
2405+ if ( authUrl ) {
2406+ this . logger . info ( `[OAuth Start Debug] Generated auth URL for user ${ userId } ` ) ;
2407+ this . logger . info ( `[OAuth Start Debug] Redirecting to: ${ authUrl . substring ( 0 , 100 ) } ...` ) ;
2408+ res . redirect ( authUrl ) ;
2409+ } else {
2410+ this . logger . error ( '[OAuth Start Debug] Failed to generate auth URL' ) ;
2411+ res . status ( 500 ) . json ( { error : 'Failed to generate authentication URL' } ) ;
2412+ }
2413+ } ) ;
2414+
23502415 app . get ( '/auth/google/callback' , async ( req : any , res : any ) => {
23512416 const { code, state : userId } = req . query ;
2417+
2418+ this . logger . info ( `[OAuth Callback Debug] Received callback - code: ${ code ? 'present' : 'missing' } , userId: ${ userId || 'missing' } ` ) ;
2419+ this . logger . info ( `[OAuth Callback Debug] Full query params:` , JSON . stringify ( req . query , null , 2 ) ) ;
23522420
23532421 if ( ! code || ! userId ) {
2422+ this . logger . error ( `[OAuth Callback Debug] Missing required parameters - code: ${ ! ! code } , userId: ${ ! ! userId } ` ) ;
23542423 res . status ( 400 ) . send ( 'Missing authorization code or user ID' ) ;
23552424 return ;
23562425 }
23572426
23582427 try {
2428+ this . logger . info ( `[OAuth Callback Debug] Attempting to handle auth callback for user: ${ userId } ` ) ;
23592429 const success = await googleCalendarService . handleAuthCallback ( code , userId ) ;
23602430
23612431 if ( success ) {
0 commit comments