mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-26 02:35:58 +08:00
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`.
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<T extends GraphQLQuery> =
|
||||
RequestOptions<T>['variables'] extends { options: infer U } ? U : never;
|
||||
|
||||
|
||||
51
packages/frontend/core/src/blocksuite/ai/provider/error.ts
Normal file
51
packages/frontend/core/src/blocksuite/ai/provider/error.ts
Normal file
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './ai-provider';
|
||||
export * from './copilot-client';
|
||||
export * from './error';
|
||||
export * from './setup-provider';
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user