From 4b553d153ab1a50f5acc7a4f43a825f58b326d07 Mon Sep 17 00:00:00 2001 From: donteatfriedrice Date: Fri, 24 Jan 2025 12:39:10 +0000 Subject: [PATCH] feat(core): update chat error style (#9885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [BS-2487](https://linear.app/affine-design/issue/BS-2487/报错样式更新) --- .../blocksuite/presets/ai/messages/error.ts | 343 +++++++++++------- .../affine-cloud-copilot/e2e/copilot.spec.ts | 8 +- 2 files changed, 213 insertions(+), 138 deletions(-) diff --git a/packages/frontend/core/src/blocksuite/presets/ai/messages/error.ts b/packages/frontend/core/src/blocksuite/presets/ai/messages/error.ts index 3eced68ca2..00c51ee996 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/messages/error.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/messages/error.ts @@ -2,169 +2,244 @@ import { type EditorHost } from '@blocksuite/affine/block-std'; import { type AIError, PaymentRequiredError, + scrollbarStyle, UnauthorizedError, + unsafeCSSVarV2, } from '@blocksuite/affine/blocks'; -import { WithDisposable } from '@blocksuite/affine/global/utils'; -import { html, LitElement, nothing, type TemplateResult } from 'lit'; +import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/utils'; +import { ToggleDownIcon } from '@blocksuite/icons/lit'; +import { signal } from '@preact/signals-core'; +import { baseTheme } from '@toeverything/theme'; +import { css, html, LitElement, nothing, unsafeCSS } from 'lit'; import { property } from 'lit/decorators.js'; -import { styleMap } from 'lit/directives/style-map.js'; import { ErrorTipIcon } from '../_common/icons'; import { AIProvider } from '../provider'; -export class AIErrorWrapper extends WithDisposable(LitElement) { - @property({ attribute: false }) - accessor text!: TemplateResult<1>; - - protected override render() { - return html` -
-
-
${ErrorTipIcon}
-
${this.text}
-
- -
`; - } -} - -export const PaymentRequiredErrorRenderer = (host: EditorHost) => html` - + `; + + private readonly _showDetailContent = signal(false); + + protected override render() { + return html`
+
+
${ErrorTipIcon}
+
+
${this.text}
+ ${this.showDetailPanel + ? html`
+
+ (this._showDetailContent.value = + !this._showDetailContent.value)} + > + Show detail + + ${ToggleDownIcon({ width: '16px', height: '16px' })} + +
+ ${this._showDetailContent.value + ? html`
${this.errorMessage}
` + : nothing} +
` + : nothing} +
+
+
+ + ${this.actionText} + ${this.actionTooltip + ? html`${this.actionTooltip}` + : nothing} + +
+
`; + } + + @property({ attribute: false }) + accessor text: string = ''; + + @property({ attribute: false }) + accessor onClick: () => void = () => {}; + + @property({ attribute: false }) + accessor errorMessage: string = ''; + + @property({ attribute: false }) + accessor actionText: string = 'Contact us'; + + @property({ attribute: false }) + accessor actionTooltip: string = ''; + + @property({ attribute: false }) + accessor showDetailPanel: boolean = false; +} + +const PaymentRequiredErrorRenderer = (host: EditorHost) => html` -
AIProvider.slots.requestUpgradePlan.emit({ host: host })} - class="upgrade" - > -
Upgrade
-
+ .text=${"You've reached the current usage cap for AFFiNE AI. You can subscribe to AFFiNE AI to continue the AI experience!"} + .actionText=${'Upgrade'} + .onClick=${() => AIProvider.slots.requestUpgradePlan.emit({ host })} + > +`; + +const LoginRequiredErrorRenderer = (host: EditorHost) => html` + AIProvider.slots.requestLogin.emit({ host })} + > `; type ErrorProps = { - text?: TemplateResult<1>; - template?: TemplateResult<1>; - error?: TemplateResult<1>; + text?: string; + errorMessage?: string; + actionText?: string; + actionTooltip?: string; }; -const generateText = (error?: TemplateResult<1>) => - html`${error || 'An error occurred'}, If this issue persists please let us - know. - support@toeverything.info - `; +const generalErrorText = + 'An error occurred, If this issue persists please let us know.'; -const nope = html`${nothing}`; const GeneralErrorRenderer = (props: ErrorProps = {}) => { - const { text = generateText(props.error), template = nope } = props; - return html`${template}`; + const onClick = () => { + window.open('mailto:support@toeverything.info', '_blank'); + }; + + return html``; }; +export function AIChatErrorRenderer(host: EditorHost, error: AIError) { + if (error instanceof PaymentRequiredError) { + return PaymentRequiredErrorRenderer(host); + } else if (error instanceof UnauthorizedError) { + return LoginRequiredErrorRenderer(host); + } else { + return GeneralErrorRenderer({ + errorMessage: error.message, + }); + } +} + declare global { interface HTMLElementTagNameMap { 'ai-error-wrapper': AIErrorWrapper; } } - -export function AIChatErrorRenderer(host: EditorHost, error: AIError) { - console.error(error); - if (error instanceof PaymentRequiredError) { - return PaymentRequiredErrorRenderer(host); - } else if (error instanceof UnauthorizedError) { - return GeneralErrorRenderer({ - text: html`You need to login to AFFiNE Cloud to continue using AFFiNE AI.`, - template: html`
AIProvider.slots.requestLogin.emit({ host })} - > - Login -
`, - }); - } else { - const tip = error.message; - return GeneralErrorRenderer({ - error: html` - An error occurred${tip}`, - }); - } -} diff --git a/tests/affine-cloud-copilot/e2e/copilot.spec.ts b/tests/affine-cloud-copilot/e2e/copilot.spec.ts index 6f2e8a9b80..1cfa8ebeba 100644 --- a/tests/affine-cloud-copilot/e2e/copilot.spec.ts +++ b/tests/affine-cloud-copilot/e2e/copilot.spec.ts @@ -164,8 +164,8 @@ test('can trigger login at chat side panel', async ({ page }) => { await waitForEditorLoad(page); await clickNewPageButton(page); await makeChat(page, 'hello'); - const loginTips = await page.waitForSelector('ai-error-wrapper'); - expect(await loginTips.innerText()).toBe('Login'); + const loginButton = await page.getByTestId('ai-error-action-button'); + expect(await loginButton.innerText()).toBe('Login'); }); test('can chat after login at chat side panel', async ({ page }) => { @@ -173,8 +173,8 @@ test('can chat after login at chat side panel', async ({ page }) => { await waitForEditorLoad(page); await clickNewPageButton(page); await makeChat(page, 'hello'); - const loginTips = await page.waitForSelector('ai-error-wrapper'); - (await loginTips.$('div'))!.click(); + const loginButton = await page.getByTestId('ai-error-action-button'); + await loginButton.click(); // login const user = await getUser(); await loginUserDirectly(page, user);