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,
|
buildFinishConfig,
|
||||||
buildGeneratingConfig,
|
buildGeneratingConfig,
|
||||||
} from '../ai-panel';
|
} from '../ai-panel';
|
||||||
import type { AIError, AIItemGroupConfig } from '../components/ai-item/types';
|
import { type AIItemGroupConfig } from '../components/ai-item/types';
|
||||||
import { AIProvider } from '../provider';
|
import { type AIError, AIProvider } from '../provider';
|
||||||
import { reportResponse } from '../utils/action-reporter';
|
import { reportResponse } from '../utils/action-reporter';
|
||||||
import { getAIPanelWidget } from '../utils/ai-widgets';
|
import { getAIPanelWidget } from '../utils/ai-widgets';
|
||||||
import { AIContext } from '../utils/context';
|
import { AIContext } from '../utils/context';
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import type { TemplateResult } from 'lit';
|
|||||||
|
|
||||||
import { getContentFromSlice } from '../../utils';
|
import { getContentFromSlice } from '../../utils';
|
||||||
import { AIChatBlockModel } from '../blocks';
|
import { AIChatBlockModel } from '../blocks';
|
||||||
import type { AIError } from '../components/ai-item/types';
|
import { type AIError, AIProvider } from '../provider';
|
||||||
import { AIProvider } from '../provider';
|
|
||||||
import { reportResponse } from '../utils/action-reporter';
|
import { reportResponse } from '../utils/action-reporter';
|
||||||
import { getAIPanelWidget } from '../utils/ai-widgets';
|
import { getAIPanelWidget } from '../utils/ai-widgets';
|
||||||
import { AIContext } from '../utils/context';
|
import { AIContext } from '../utils/context';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Signal } from '@preact/signals-core';
|
import type { Signal } from '@preact/signals-core';
|
||||||
|
|
||||||
import type { AIError } from '../components/ai-item/types';
|
import type { AIError } from '../provider';
|
||||||
|
|
||||||
export type ChatMessage = {
|
export type ChatMessage = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
.chips-wrapper {
|
.chips-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
margin: 0 -4px 0 -4px;
|
||||||
}
|
}
|
||||||
.add-button,
|
.add-button,
|
||||||
.collapse-button,
|
.collapse-button,
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ import { property, query, state } from 'lit/decorators.js';
|
|||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
|
||||||
import { ChatAbortIcon, ChatSendIcon } from '../_common/icons';
|
import { ChatAbortIcon, ChatSendIcon } from '../_common/icons';
|
||||||
import type { AIError } from '../components/ai-item/types';
|
import { type AIError, AIProvider } from '../provider';
|
||||||
import { AIProvider } from '../provider';
|
|
||||||
import { reportResponse } from '../utils/action-reporter';
|
import { reportResponse } from '../utils/action-reporter';
|
||||||
import { readBlobAsURL } from '../utils/image';
|
import { readBlobAsURL } from '../utils/image';
|
||||||
import type { AINetworkSearchConfig, DocDisplayConfig } from './chat-config';
|
import type { AINetworkSearchConfig, DocDisplayConfig } from './chat-config';
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ import { repeat } from 'lit/directives/repeat.js';
|
|||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
|
|
||||||
import { AffineIcon } from '../_common/icons';
|
import { AffineIcon } from '../_common/icons';
|
||||||
import { type AIError, UnauthorizedError } from '../components/ai-item/types';
|
import { type AIError, AIProvider, UnauthorizedError } from '../provider';
|
||||||
import { AIProvider } from '../provider';
|
|
||||||
import {
|
import {
|
||||||
type ChatContextValue,
|
type ChatContextValue,
|
||||||
type ChatMessage,
|
type ChatMessage,
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import {
|
|||||||
EdgelessEditorActions,
|
EdgelessEditorActions,
|
||||||
PageEditorActions,
|
PageEditorActions,
|
||||||
} from '../../_common/chat-actions-handle';
|
} from '../../_common/chat-actions-handle';
|
||||||
import { type AIError } from '../../components/ai-item/types';
|
|
||||||
import { AIChatErrorRenderer } from '../../messages/error';
|
import { AIChatErrorRenderer } from '../../messages/error';
|
||||||
|
import { type AIError } from '../../provider';
|
||||||
import { type ChatMessage, isChatMessage } from '../chat-context';
|
import { type ChatMessage, isChatMessage } from '../chat-context';
|
||||||
|
|
||||||
export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
||||||
|
|||||||
@@ -28,44 +28,3 @@ export interface AISubItemConfig {
|
|||||||
testId?: string;
|
testId?: string;
|
||||||
handler?: (host: EditorHost) => void;
|
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 {
|
import {
|
||||||
type AIError,
|
type AIError,
|
||||||
|
AIProvider,
|
||||||
PaymentRequiredError,
|
PaymentRequiredError,
|
||||||
UnauthorizedError,
|
UnauthorizedError,
|
||||||
} from '../components/ai-item/types';
|
} from '../provider';
|
||||||
import { AIProvider } from '../provider';
|
|
||||||
|
|
||||||
export class AIErrorWrapper extends SignalWatcher(WithDisposable(LitElement)) {
|
export class AIErrorWrapper extends SignalWatcher(WithDisposable(LitElement)) {
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ import {
|
|||||||
PROMPT_NAME_AFFINE_AI,
|
PROMPT_NAME_AFFINE_AI,
|
||||||
PROMPT_NAME_NETWORK_SEARCH,
|
PROMPT_NAME_NETWORK_SEARCH,
|
||||||
} from '../chat-panel/const';
|
} from '../chat-panel/const';
|
||||||
import type { AIError } from '../components/ai-item/types';
|
import { type AIError, AIProvider } from '../provider';
|
||||||
import { AIProvider } from '../provider';
|
|
||||||
import { reportResponse } from '../utils/action-reporter';
|
import { reportResponse } from '../utils/action-reporter';
|
||||||
import { readBlobAsURL } from '../utils/image';
|
import { readBlobAsURL } from '../utils/image';
|
||||||
import { stopPropagation } from '../utils/selection-utils';
|
import { stopPropagation } from '../utils/selection-utils';
|
||||||
|
|||||||
@@ -28,10 +28,9 @@ import {
|
|||||||
ChatMessagesSchema,
|
ChatMessagesSchema,
|
||||||
} from '../blocks';
|
} from '../blocks';
|
||||||
import type { AINetworkSearchConfig } from '../chat-panel/chat-config';
|
import type { AINetworkSearchConfig } from '../chat-panel/chat-config';
|
||||||
import type { AIError } from '../components/ai-item/types';
|
|
||||||
import type { TextRendererOptions } from '../components/text-renderer';
|
import type { TextRendererOptions } from '../components/text-renderer';
|
||||||
import { AIChatErrorRenderer } from '../messages/error';
|
import { AIChatErrorRenderer } from '../messages/error';
|
||||||
import { AIProvider } from '../provider';
|
import { type AIError, AIProvider } from '../provider';
|
||||||
import { PeekViewStyles } from './styles';
|
import { PeekViewStyles } from './styles';
|
||||||
import type { ChatContext } from './types';
|
import type { ChatContext } from './types';
|
||||||
import { calcChildBound } from './utils';
|
import { calcChildBound } from './utils';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ChatMessage } from '../blocks';
|
import type { ChatMessage } from '../blocks';
|
||||||
import type { AIError } from '../components/ai-item/types';
|
import type { AIError } from '../provider';
|
||||||
|
|
||||||
export type ChatStatus =
|
export type ChatStatus =
|
||||||
| 'success'
|
| 'success'
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { Subject } from 'rxjs';
|
|||||||
import type { ChatContextValue } from '../chat-panel/chat-context';
|
import type { ChatContextValue } from '../chat-panel/chat-context';
|
||||||
import {
|
import {
|
||||||
PaymentRequiredError,
|
PaymentRequiredError,
|
||||||
|
RequestTimeoutError,
|
||||||
UnauthorizedError,
|
UnauthorizedError,
|
||||||
} from '../components/ai-item/types';
|
} from './error';
|
||||||
|
|
||||||
export interface AIUserInfo {
|
export interface AIUserInfo {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -36,6 +37,7 @@ export type ActionEventType =
|
|||||||
| 'aborted:login-required'
|
| 'aborted:login-required'
|
||||||
| 'aborted:server-error'
|
| 'aborted:server-error'
|
||||||
| 'aborted:stop'
|
| 'aborted:stop'
|
||||||
|
| 'aborted:timeout'
|
||||||
| 'result:insert'
|
| 'result:insert'
|
||||||
| 'result:replace'
|
| 'result:replace'
|
||||||
| 'result:use-as-caption'
|
| 'result:use-as-caption'
|
||||||
@@ -199,7 +201,13 @@ export class AIProvider {
|
|||||||
options,
|
options,
|
||||||
event: 'error',
|
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({
|
slots.actions.next({
|
||||||
action: id,
|
action: id,
|
||||||
options,
|
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 { showAILoginRequiredAtom } from '@affine/core/components/affine/auth/ai-login-required';
|
||||||
import type { UserFriendlyError } from '@affine/error';
|
import type { UserFriendlyError } from '@affine/error';
|
||||||
import {
|
import {
|
||||||
@@ -31,6 +26,12 @@ import {
|
|||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { getCurrentStore } from '@toeverything/infra';
|
import { getCurrentStore } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GeneralNetworkError,
|
||||||
|
PaymentRequiredError,
|
||||||
|
UnauthorizedError,
|
||||||
|
} from './error';
|
||||||
|
|
||||||
type OptionsField<T extends GraphQLQuery> =
|
type OptionsField<T extends GraphQLQuery> =
|
||||||
RequestOptions<T>['variables'] extends { options: infer U } ? U : never;
|
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 { handleError } from './copilot-client';
|
||||||
|
import { RequestTimeoutError } from './error';
|
||||||
|
|
||||||
export function delay(ms: number) {
|
export function delay(ms: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
@@ -85,7 +86,7 @@ export function toTextStream(
|
|||||||
messagePromise,
|
messagePromise,
|
||||||
delay(timeout).then(() => {
|
delay(timeout).then(() => {
|
||||||
if (!signal?.aborted) {
|
if (!signal?.aborted) {
|
||||||
throw new Error('Timeout');
|
throw new RequestTimeoutError();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './ai-provider';
|
export * from './ai-provider';
|
||||||
export * from './copilot-client';
|
export * from './copilot-client';
|
||||||
|
export * from './error';
|
||||||
export * from './setup-provider';
|
export * from './setup-provider';
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type AIActionEventProperties = {
|
|||||||
| 'policy wall'
|
| 'policy wall'
|
||||||
| 'server error'
|
| 'server error'
|
||||||
| 'login required'
|
| 'login required'
|
||||||
|
| 'request timeout'
|
||||||
| 'insert'
|
| 'insert'
|
||||||
| 'replace'
|
| 'replace'
|
||||||
| 'use as caption'
|
| 'use as caption'
|
||||||
@@ -193,6 +194,8 @@ function inferControl(
|
|||||||
return 'server error';
|
return 'server error';
|
||||||
} else if (event.event === 'aborted:login-required') {
|
} else if (event.event === 'aborted:login-required') {
|
||||||
return 'login required';
|
return 'login required';
|
||||||
|
} else if (event.event === 'aborted:timeout') {
|
||||||
|
return 'request timeout';
|
||||||
} else if (event.options.control === 'chat-send') {
|
} else if (event.options.control === 'chat-send') {
|
||||||
return 'AI chat send button';
|
return 'AI chat send button';
|
||||||
} else if (event.options.control === 'block-action-bar') {
|
} 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 { choose } from 'lit/directives/choose.js';
|
||||||
import { literal, unsafeStatic } from 'lit/static-html.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 { AIPanelGenerating } from './components/index.js';
|
||||||
import type { AffineAIPanelState, AffineAIPanelWidgetConfig } from './type.js';
|
import type { AffineAIPanelState, AffineAIPanelWidgetConfig } from './type.js';
|
||||||
|
|
||||||
export const AFFINE_AI_PANEL_WIDGET = 'affine-ai-panel-widget';
|
export const AFFINE_AI_PANEL_WIDGET = 'affine-ai-panel-widget';
|
||||||
|
|
||||||
export class AffineAIPanelWidget extends WidgetComponent {
|
export class AffineAIPanelWidget extends WidgetComponent {
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
|
|||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
import { choose } from 'lit/directives/choose.js';
|
import { choose } from 'lit/directives/choose.js';
|
||||||
|
|
||||||
import {
|
import { type AIItemGroupConfig } from '../../../../components/ai-item/types.js';
|
||||||
AIErrorType,
|
import { AIErrorType } from '../../../../provider';
|
||||||
type AIItemGroupConfig,
|
|
||||||
} from '../../../../components/ai-item/types.js';
|
|
||||||
import type { AIPanelErrorConfig, CopyConfig } from '../../type.js';
|
import type { AIPanelErrorConfig, CopyConfig } from '../../type.js';
|
||||||
import { filterAIItemGroup } from '../../utils.js';
|
import { filterAIItemGroup } from '../../utils.js';
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import type { Signal } from '@preact/signals-core';
|
import type { Signal } from '@preact/signals-core';
|
||||||
import type { nothing, TemplateResult } from 'lit';
|
import type { nothing, TemplateResult } from 'lit';
|
||||||
|
|
||||||
import type {
|
import type { AIItemGroupConfig } from '../../components/ai-item/types';
|
||||||
AIError,
|
import type { AIError } from '../../provider';
|
||||||
AIItemGroupConfig,
|
|
||||||
} from '../../components/ai-item/types';
|
|
||||||
|
|
||||||
export interface CopyConfig {
|
export interface CopyConfig {
|
||||||
allowed: boolean;
|
allowed: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user