diff --git a/browser-extension/src/background.ts b/browser-extension/src/background.ts index 522b2a9..6abb706 100644 --- a/browser-extension/src/background.ts +++ b/browser-extension/src/background.ts @@ -5,7 +5,7 @@ import { AgentSession } from "./scripts/agent-session"; import { findAgentSession, saveAgentSession, removeAgentSession, } from "./scripts/agent-session-repository"; import { saveAgentPrompts } from "./scripts/prompt-repository"; import { BrowserMessage, ToggleSidebar, ActiveTabListener, ActivateAgent, AgentActivation, InteractionSummary, } from "./scripts/browser-message"; -import { HttpServiceError } from "./scripts/http"; +import { BaseError } from "./scripts/http"; import { isActiveTabListener, setTabListenerActive, removeTabListenerStatus, } from "./scripts/tab-listener-status-repository"; import { removeTabState } from "./scripts/tab-state-repository"; @@ -174,8 +174,9 @@ async function asyncProcessRequest(req: RequestEvent) { sendToTab( tabId, new InteractionSummary( - false, - e instanceof HttpServiceError ? e.detail : undefined + false, + e instanceof BaseError ? e.detail : undefined, + e instanceof BaseError ? e.getType() : undefined ) ); } diff --git a/browser-extension/src/pages/Index.vue b/browser-extension/src/pages/Index.vue index bb46aa6..8bcc2eb 100644 --- a/browser-extension/src/pages/Index.vue +++ b/browser-extension/src/pages/Index.vue @@ -165,7 +165,14 @@ const onAgentActivation = (msg: AgentActivation) => { } const onInteractionSummary = (msg: InteractionSummary) => { - const text = msg.text ? msg.text : t('interactionSummaryError', { contactEmail: agent.value!.manifest.contactEmail }) + let text = msg.text ?? '' + + if (!msg.success) { + text = msg.errorType === 'NetworkError' ? t('networkError') : t('interactionSummaryError', { + contactEmail: agent.value!.manifest.contactEmail + }) + } + const lastMessage = messages.value[messages.value.length - 1] const messagePosition = lastMessage.isComplete ? messages.value.length : messages.value.length - 1 messages.value.splice(messagePosition, 0, msg.success ? ChatMessage.agentMessage(text) : ChatMessage.agentErrorMessage(text)) @@ -257,12 +264,14 @@ const sidebarClasses = computed(() => [ "interactionSummaryError": "I could not process some information from the current site. This might impact the information and answers I provide. If the issue persists please contact [support](mailto:{contactEmail}?subject=Interaction%20issue)", "agentAnswerError": "I am currently unable to complete your request. You can try again and if the issue persists contact [support](mailto:{contactEmail}?subject=Question%20issue)", "flowStepMissingElement": "I could not find the element '{selector}'. This might be due to recent changes in the page which I am not aware of. Please try again and if the issue persists contact [support](mailto:{contactEmail}?subject=Navigation%20element).", + "networkError": "It seems there is a problem with the network connection. Please check your connection and try again." }, "es": { "activationError": "No se pudo activar el Copiloto {agentName}. Puedes intentar de nuevo y si el problema persiste contactar al [soporte del Copiloto {agentName}](mailto:{contactEmail}?subject=Activation%20issue)", "interactionSummaryError": "No pude procesar informacion generada por la página actual. Esto puede impactar en la información y respuestas que te puedo dar. Si el problema persiste por favor contacta a [soporte](mailto:{contactEmail})?subject=Interaction%20issue", "agentAnswerError": "Ahora no puedo completar tu pedido. Puedes intentar de nuevo y si el problema persiste contactar a [soporte](mailto:{contactEmail}?subject=Question%20issue)", - "flowStepMissingElement": "No pude encontrar el elemento '{selector}'. Esto puede ser debido a cambios recientes en la página de los cuales no tengo conocimiento. Por favor intenta de nuevo y si el problema persiste contacta a [soporte](mailto:{contactEmail}?subject=Navigation%20element).", + "flowStepMissingElement": "No pude encontrar el elemento '{selector}'. Esto puede ser debido a cambios recientes en la página de los cuales no tengo conocimiento. Por favor intenta de nuevo y si el problema persiste contacta a [soporte](mailto:{contactEmail}?subject=Navigation%20element).", + "networkError": "Parece que hay un problema con la conexión a la red. Por favor verifica tu conexión y vuelve a intentarlo." } } diff --git a/browser-extension/src/scripts/agent-session.ts b/browser-extension/src/scripts/agent-session.ts index f67038e..0ae48da 100644 --- a/browser-extension/src/scripts/agent-session.ts +++ b/browser-extension/src/scripts/agent-session.ts @@ -105,7 +105,7 @@ export class AgentSession { private async pollInteraction(msgSender: (msg: BrowserMessage) => void) { try { - const summary = await this.agent.solveInteractionSummary(undefined, this.id!, this.authService) + const summary = await this.retryInteractionSummary(undefined, this.id!, this.authService) if (summary) { await msgSender(new InteractionSummary(true, summary)) } @@ -120,7 +120,7 @@ export class AgentSession { for (const a of actions) { if (a.recordInteraction) { const interactionDetail = await this.findInteraction(req, a.recordInteraction) - return await this.agent.solveInteractionSummary(interactionDetail, this.id!, this.authService) + return await this.retryInteractionSummary(interactionDetail, this.id!, this.authService) } } } @@ -189,4 +189,23 @@ export class AgentSession { } } + private async retryInteractionSummary(interactionDetail: any | undefined, sessionId: string, authService?: AuthService, maxRetries = 2, delayMs = 5000): Promise { + let attempts = 0 + let lastError: any + + while (attempts <= maxRetries) { + try { + return await this.agent.solveInteractionSummary(interactionDetail, sessionId, authService) + } catch (error) { + lastError = error + attempts++ + + if (attempts <= maxRetries) { + await new Promise(resolve => setTimeout(resolve, delayMs)) + } + } + } + + throw lastError + } } diff --git a/browser-extension/src/scripts/browser-message.ts b/browser-extension/src/scripts/browser-message.ts index 241254d..ed53fd1 100644 --- a/browser-extension/src/scripts/browser-message.ts +++ b/browser-extension/src/scripts/browser-message.ts @@ -102,15 +102,17 @@ export class AgentActivation extends BrowserMessage { export class InteractionSummary extends BrowserMessage { text?: string success: boolean + errorType?: string - constructor(success: boolean, text?: string) { + constructor(success: boolean, text?: string, errorType?: string) { super("interactionSummary") this.text = text this.success = success + this.errorType = errorType } public static fromJsonObject(obj: any): InteractionSummary { - return new InteractionSummary(obj.success, obj.text) + return new InteractionSummary(obj.success, obj.text, obj.errorType) } } diff --git a/browser-extension/src/scripts/http.ts b/browser-extension/src/scripts/http.ts index 49f3c26..3efd444 100644 --- a/browser-extension/src/scripts/http.ts +++ b/browser-extension/src/scripts/http.ts @@ -4,7 +4,21 @@ export const fetchJson = async (url: string, options?: RequestInit) => { } const fetchResponse = async (url: string, options?: RequestInit) => { - let ret = await fetch(url, options) + let ret + + try { + ret = await fetch(url, options) + } catch (error) { + // This handles the case where the user is temporarily disconnected from the internet + const partialErrorMessage = "Failed to fetch" + + if (error instanceof TypeError && error.message.includes(partialErrorMessage)) { + throw new NetworkError(partialErrorMessage) + } + + throw error + } + if (ret.status < 200 || ret.status >= 300) { let body = await ret.text() console.warn(`Problem with ${options?.method ? options.method : 'GET'} ${url}`, { status: ret.status, body: body }) @@ -19,14 +33,26 @@ const fetchResponse = async (url: string, options?: RequestInit) => { return ret } -export class HttpServiceError extends Error { +export class BaseError extends Error { detail?: string + readonly type: string = 'BaseError' constructor(detail?: string) { super() this.detail = detail } + getType(): string { + return this.type + } +} + +export class HttpServiceError extends BaseError { + readonly type: string = 'HttpServiceError' +} + +export class NetworkError extends BaseError { + readonly type: string = 'NetworkError' } export async function* fetchStreamJson(url: string, options?: RequestInit): AsyncIterable {