From ac815142b31347fed3c305d0d42f700f35f68f45 Mon Sep 17 00:00:00 2001 From: akumatus Date: Sat, 29 Mar 2025 04:27:39 +0000 Subject: [PATCH] refactor(core): add request time out error for ai (#11244) ### Why make this change? Seperate front end timeout errors from server side errors. ### What changed? - Add `RequestTimeoutError` which extends from `BaseAIError`. - Track as `request timeout` instead of `server error`. --- .../src/blocksuite/ai/actions/doc-handler.ts | 4 +- .../blocksuite/ai/actions/edgeless-handler.ts | 3 +- .../blocksuite/ai/chat-panel/chat-context.ts | 2 +- .../ai/chat-panel/chat-panel-chips.ts | 1 + .../ai/chat-panel/chat-panel-input.ts | 3 +- .../ai/chat-panel/chat-panel-messages.ts | 3 +- .../ai/chat-panel/message/assistant.ts | 2 +- .../blocksuite/ai/components/ai-item/types.ts | 41 --------------- .../core/src/blocksuite/ai/messages/error.ts | 4 +- .../ai/peek-view/chat-block-input.ts | 3 +- .../ai/peek-view/chat-block-peek-view.ts | 3 +- .../core/src/blocksuite/ai/peek-view/types.ts | 2 +- .../src/blocksuite/ai/provider/ai-provider.ts | 12 ++++- .../blocksuite/ai/provider/copilot-client.ts | 11 ++-- .../core/src/blocksuite/ai/provider/error.ts | 51 +++++++++++++++++++ .../blocksuite/ai/provider/event-source.ts | 3 +- .../core/src/blocksuite/ai/provider/index.ts | 1 + .../src/blocksuite/ai/provider/tracker.ts | 3 ++ .../ai/widgets/ai-panel/ai-panel.ts | 3 +- .../ai-panel/components/state/error.ts | 6 +-- .../blocksuite/ai/widgets/ai-panel/type.ts | 6 +-- 21 files changed, 91 insertions(+), 76 deletions(-) create mode 100644 packages/frontend/core/src/blocksuite/ai/provider/error.ts diff --git a/packages/frontend/core/src/blocksuite/ai/actions/doc-handler.ts b/packages/frontend/core/src/blocksuite/ai/actions/doc-handler.ts index cbc6eef26..cd6e0c32a 100644 --- a/packages/frontend/core/src/blocksuite/ai/actions/doc-handler.ts +++ b/packages/frontend/core/src/blocksuite/ai/actions/doc-handler.ts @@ -10,8 +10,8 @@ import { buildFinishConfig, buildGeneratingConfig, } from '../ai-panel'; -import type { AIError, AIItemGroupConfig } from '../components/ai-item/types'; -import { AIProvider } from '../provider'; +import { type AIItemGroupConfig } from '../components/ai-item/types'; +import { type AIError, AIProvider } from '../provider'; import { reportResponse } from '../utils/action-reporter'; import { getAIPanelWidget } from '../utils/ai-widgets'; import { AIContext } from '../utils/context'; diff --git a/packages/frontend/core/src/blocksuite/ai/actions/edgeless-handler.ts b/packages/frontend/core/src/blocksuite/ai/actions/edgeless-handler.ts index c8205782b..6464b3ac6 100644 --- a/packages/frontend/core/src/blocksuite/ai/actions/edgeless-handler.ts +++ b/packages/frontend/core/src/blocksuite/ai/actions/edgeless-handler.ts @@ -20,8 +20,7 @@ import type { TemplateResult } from 'lit'; import { getContentFromSlice } from '../../utils'; import { AIChatBlockModel } from '../blocks'; -import type { AIError } from '../components/ai-item/types'; -import { AIProvider } from '../provider'; +import { type AIError, AIProvider } from '../provider'; import { reportResponse } from '../utils/action-reporter'; import { getAIPanelWidget } from '../utils/ai-widgets'; import { AIContext } from '../utils/context'; diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-context.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-context.ts index 77a9d664c..673967851 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-context.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-context.ts @@ -1,6 +1,6 @@ import type { Signal } from '@preact/signals-core'; -import type { AIError } from '../components/ai-item/types'; +import type { AIError } from '../provider'; export type ChatMessage = { id: string; diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-chips.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-chips.ts index 04502f26f..3c06aa275 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-chips.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-chips.ts @@ -43,6 +43,7 @@ export class ChatPanelChips extends SignalWatcher( .chips-wrapper { display: flex; flex-wrap: wrap; + margin: 0 -4px 0 -4px; } .add-button, .collapse-button, diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-input.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-input.ts index dbd2feb6a..72885fa6a 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-input.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-input.ts @@ -14,8 +14,7 @@ import { property, query, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { ChatAbortIcon, ChatSendIcon } from '../_common/icons'; -import type { AIError } from '../components/ai-item/types'; -import { AIProvider } from '../provider'; +import { type AIError, AIProvider } from '../provider'; import { reportResponse } from '../utils/action-reporter'; import { readBlobAsURL } from '../utils/image'; import type { AINetworkSearchConfig, DocDisplayConfig } from './chat-config'; diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts index e37a6f5e5..7bb10db2f 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts @@ -14,8 +14,7 @@ import { repeat } from 'lit/directives/repeat.js'; import { debounce } from 'lodash-es'; import { AffineIcon } from '../_common/icons'; -import { type AIError, UnauthorizedError } from '../components/ai-item/types'; -import { AIProvider } from '../provider'; +import { type AIError, AIProvider, UnauthorizedError } from '../provider'; import { type ChatContextValue, type ChatMessage, diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/message/assistant.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/message/assistant.ts index 7a51e8dd0..7164ca4e8 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/message/assistant.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/message/assistant.ts @@ -12,8 +12,8 @@ import { EdgelessEditorActions, PageEditorActions, } from '../../_common/chat-actions-handle'; -import { type AIError } from '../../components/ai-item/types'; import { AIChatErrorRenderer } from '../../messages/error'; +import { type AIError } from '../../provider'; import { type ChatMessage, isChatMessage } from '../chat-context'; export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) { diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-item/types.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-item/types.ts index ba2cfe3a9..39edd60d6 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-item/types.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-item/types.ts @@ -28,44 +28,3 @@ export interface AISubItemConfig { testId?: string; handler?: (host: EditorHost) => void; } - -abstract class BaseAIError extends Error { - abstract readonly type: AIErrorType; -} - -export enum AIErrorType { - GeneralNetworkError = 'GeneralNetworkError', - PaymentRequired = 'PaymentRequired', - Unauthorized = 'Unauthorized', -} - -export class UnauthorizedError extends BaseAIError { - readonly type = AIErrorType.Unauthorized; - - constructor() { - super('Unauthorized'); - } -} - -// user has used up the quota -export class PaymentRequiredError extends BaseAIError { - readonly type = AIErrorType.PaymentRequired; - - constructor() { - super('Payment required'); - } -} - -// general 500x error -export class GeneralNetworkError extends BaseAIError { - readonly type = AIErrorType.GeneralNetworkError; - - constructor(message: string = 'Network error') { - super(message); - } -} - -export type AIError = - | UnauthorizedError - | PaymentRequiredError - | GeneralNetworkError; diff --git a/packages/frontend/core/src/blocksuite/ai/messages/error.ts b/packages/frontend/core/src/blocksuite/ai/messages/error.ts index 9a63fb147..e155b94b9 100644 --- a/packages/frontend/core/src/blocksuite/ai/messages/error.ts +++ b/packages/frontend/core/src/blocksuite/ai/messages/error.ts @@ -10,10 +10,10 @@ import { property } from 'lit/decorators.js'; import { type AIError, + AIProvider, PaymentRequiredError, UnauthorizedError, -} from '../components/ai-item/types'; -import { AIProvider } from '../provider'; +} from '../provider'; export class AIErrorWrapper extends SignalWatcher(WithDisposable(LitElement)) { static override styles = css` diff --git a/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-input.ts b/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-input.ts index 15b92c7f6..58922b843 100644 --- a/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-input.ts +++ b/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-input.ts @@ -14,8 +14,7 @@ import { PROMPT_NAME_AFFINE_AI, PROMPT_NAME_NETWORK_SEARCH, } from '../chat-panel/const'; -import type { AIError } from '../components/ai-item/types'; -import { AIProvider } from '../provider'; +import { type AIError, AIProvider } from '../provider'; import { reportResponse } from '../utils/action-reporter'; import { readBlobAsURL } from '../utils/image'; import { stopPropagation } from '../utils/selection-utils'; diff --git a/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-peek-view.ts b/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-peek-view.ts index ecb86a276..a55b01122 100644 --- a/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-peek-view.ts +++ b/packages/frontend/core/src/blocksuite/ai/peek-view/chat-block-peek-view.ts @@ -28,10 +28,9 @@ import { ChatMessagesSchema, } from '../blocks'; import type { AINetworkSearchConfig } from '../chat-panel/chat-config'; -import type { AIError } from '../components/ai-item/types'; import type { TextRendererOptions } from '../components/text-renderer'; import { AIChatErrorRenderer } from '../messages/error'; -import { AIProvider } from '../provider'; +import { type AIError, AIProvider } from '../provider'; import { PeekViewStyles } from './styles'; import type { ChatContext } from './types'; import { calcChildBound } from './utils'; diff --git a/packages/frontend/core/src/blocksuite/ai/peek-view/types.ts b/packages/frontend/core/src/blocksuite/ai/peek-view/types.ts index 8f6b5c17b..5f0610417 100644 --- a/packages/frontend/core/src/blocksuite/ai/peek-view/types.ts +++ b/packages/frontend/core/src/blocksuite/ai/peek-view/types.ts @@ -1,5 +1,5 @@ import type { ChatMessage } from '../blocks'; -import type { AIError } from '../components/ai-item/types'; +import type { AIError } from '../provider'; export type ChatStatus = | 'success' diff --git a/packages/frontend/core/src/blocksuite/ai/provider/ai-provider.ts b/packages/frontend/core/src/blocksuite/ai/provider/ai-provider.ts index c52f10c5b..9fecaaa00 100644 --- a/packages/frontend/core/src/blocksuite/ai/provider/ai-provider.ts +++ b/packages/frontend/core/src/blocksuite/ai/provider/ai-provider.ts @@ -5,8 +5,9 @@ import { Subject } from 'rxjs'; import type { ChatContextValue } from '../chat-panel/chat-context'; import { PaymentRequiredError, + RequestTimeoutError, UnauthorizedError, -} from '../components/ai-item/types'; +} from './error'; export interface AIUserInfo { id: string; @@ -36,6 +37,7 @@ export type ActionEventType = | 'aborted:login-required' | 'aborted:server-error' | 'aborted:stop' + | 'aborted:timeout' | 'result:insert' | 'result:replace' | 'result:use-as-caption' @@ -199,7 +201,13 @@ export class AIProvider { options, event: 'error', }); - if (err instanceof PaymentRequiredError) { + if (err instanceof RequestTimeoutError) { + slots.actions.next({ + action: id, + options, + event: 'aborted:timeout', + }); + } else if (err instanceof PaymentRequiredError) { slots.actions.next({ action: id, options, diff --git a/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts b/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts index fef24e417..5bbc602b3 100644 --- a/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts +++ b/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts @@ -1,8 +1,3 @@ -import { - GeneralNetworkError, - PaymentRequiredError, - UnauthorizedError, -} from '@affine/core/blocksuite/ai/components/ai-item/types'; import { showAILoginRequiredAtom } from '@affine/core/components/affine/auth/ai-login-required'; import type { UserFriendlyError } from '@affine/error'; import { @@ -31,6 +26,12 @@ import { } from '@affine/graphql'; import { getCurrentStore } from '@toeverything/infra'; +import { + GeneralNetworkError, + PaymentRequiredError, + UnauthorizedError, +} from './error'; + type OptionsField = RequestOptions['variables'] extends { options: infer U } ? U : never; diff --git a/packages/frontend/core/src/blocksuite/ai/provider/error.ts b/packages/frontend/core/src/blocksuite/ai/provider/error.ts new file mode 100644 index 000000000..9c810d468 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/provider/error.ts @@ -0,0 +1,51 @@ +abstract class BaseAIError extends Error { + abstract readonly type: AIErrorType; +} + +export enum AIErrorType { + GeneralNetworkError = 'GeneralNetworkError', + PaymentRequired = 'PaymentRequired', + Unauthorized = 'Unauthorized', + RequestTimeout = 'RequestTimeout', +} + +export class UnauthorizedError extends BaseAIError { + readonly type = AIErrorType.Unauthorized; + + constructor() { + super('Unauthorized'); + } +} + +// user has used up the quota +export class PaymentRequiredError extends BaseAIError { + readonly type = AIErrorType.PaymentRequired; + + constructor() { + super('Payment required'); + } +} + +// general 500x error +export class GeneralNetworkError extends BaseAIError { + readonly type = AIErrorType.GeneralNetworkError; + + constructor(message: string = 'Network error') { + super(message); + } +} + +// request timeout +export class RequestTimeoutError extends BaseAIError { + readonly type = AIErrorType.RequestTimeout; + + constructor(message: string = 'Request timeout') { + super(message); + } +} + +export type AIError = + | UnauthorizedError + | PaymentRequiredError + | GeneralNetworkError + | RequestTimeoutError; diff --git a/packages/frontend/core/src/blocksuite/ai/provider/event-source.ts b/packages/frontend/core/src/blocksuite/ai/provider/event-source.ts index 34dadcfaf..4fd55c565 100644 --- a/packages/frontend/core/src/blocksuite/ai/provider/event-source.ts +++ b/packages/frontend/core/src/blocksuite/ai/provider/event-source.ts @@ -1,4 +1,5 @@ import { handleError } from './copilot-client'; +import { RequestTimeoutError } from './error'; export function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); @@ -85,7 +86,7 @@ export function toTextStream( messagePromise, delay(timeout).then(() => { if (!signal?.aborted) { - throw new Error('Timeout'); + throw new RequestTimeoutError(); } }), ]) diff --git a/packages/frontend/core/src/blocksuite/ai/provider/index.ts b/packages/frontend/core/src/blocksuite/ai/provider/index.ts index 2b882bd25..4dee66f37 100644 --- a/packages/frontend/core/src/blocksuite/ai/provider/index.ts +++ b/packages/frontend/core/src/blocksuite/ai/provider/index.ts @@ -1,3 +1,4 @@ export * from './ai-provider'; export * from './copilot-client'; +export * from './error'; export * from './setup-provider'; diff --git a/packages/frontend/core/src/blocksuite/ai/provider/tracker.ts b/packages/frontend/core/src/blocksuite/ai/provider/tracker.ts index 1afca8653..75eaef5ce 100644 --- a/packages/frontend/core/src/blocksuite/ai/provider/tracker.ts +++ b/packages/frontend/core/src/blocksuite/ai/provider/tracker.ts @@ -39,6 +39,7 @@ type AIActionEventProperties = { | 'policy wall' | 'server error' | 'login required' + | 'request timeout' | 'insert' | 'replace' | 'use as caption' @@ -193,6 +194,8 @@ function inferControl( return 'server error'; } else if (event.event === 'aborted:login-required') { return 'login required'; + } else if (event.event === 'aborted:timeout') { + return 'request timeout'; } else if (event.options.control === 'chat-send') { return 'AI chat send button'; } else if (event.options.control === 'block-action-bar') { diff --git a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts index af2010cdd..55b745b55 100644 --- a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts +++ b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/ai-panel.ts @@ -31,10 +31,9 @@ import { property, query } from 'lit/decorators.js'; import { choose } from 'lit/directives/choose.js'; import { literal, unsafeStatic } from 'lit/static-html.js'; -import type { AIError } from '../../components/ai-item/types.js'; +import { type AIError } from '../../provider'; import type { AIPanelGenerating } from './components/index.js'; import type { AffineAIPanelState, AffineAIPanelWidgetConfig } from './type.js'; - export const AFFINE_AI_PANEL_WIDGET = 'affine-ai-panel-widget'; export class AffineAIPanelWidget extends WidgetComponent { diff --git a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/components/state/error.ts b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/components/state/error.ts index 28b1a734b..60a9dc0de 100644 --- a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/components/state/error.ts +++ b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/components/state/error.ts @@ -5,10 +5,8 @@ import { css, html, LitElement, nothing, unsafeCSS } from 'lit'; import { property } from 'lit/decorators.js'; import { choose } from 'lit/directives/choose.js'; -import { - AIErrorType, - type AIItemGroupConfig, -} from '../../../../components/ai-item/types.js'; +import { type AIItemGroupConfig } from '../../../../components/ai-item/types.js'; +import { AIErrorType } from '../../../../provider'; import type { AIPanelErrorConfig, CopyConfig } from '../../type.js'; import { filterAIItemGroup } from '../../utils.js'; diff --git a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/type.ts b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/type.ts index 0076a663c..0dc21c3d8 100644 --- a/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/type.ts +++ b/packages/frontend/core/src/blocksuite/ai/widgets/ai-panel/type.ts @@ -1,10 +1,8 @@ import type { Signal } from '@preact/signals-core'; import type { nothing, TemplateResult } from 'lit'; -import type { - AIError, - AIItemGroupConfig, -} from '../../components/ai-item/types'; +import type { AIItemGroupConfig } from '../../components/ai-item/types'; +import type { AIError } from '../../provider'; export interface CopyConfig { allowed: boolean;