mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(core): optimize artifact preview loading (#13224)
fix AI-369 #### PR Dependency Tree * **PR #13224** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a loading skeleton component for artifact previews, providing a smoother visual experience during loading states. * Artifact loading skeleton is now globally available as a custom element. * **Refactor** * Streamlined icon and loading state handling in AI tools, centralizing logic and removing redundant loading indicators. * Simplified card metadata by removing loading and icon properties from card meta methods. * **Chores** * Improved resource management for code block highlighting, ensuring efficient disposal and avoiding unnecessary operations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -39,6 +39,13 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
|
|||||||
private readonly _loadTheme = async (
|
private readonly _loadTheme = async (
|
||||||
highlighter: HighlighterCore
|
highlighter: HighlighterCore
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
// It is possible that by the time the highlighter is ready all instances
|
||||||
|
// have already been unmounted. In that case there is no need to load
|
||||||
|
// themes or update state.
|
||||||
|
if (CodeBlockHighlighter._refCount === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const config = this.std.getOptional(CodeBlockConfigExtension.identifier);
|
const config = this.std.getOptional(CodeBlockConfigExtension.identifier);
|
||||||
const darkTheme = config?.theme?.dark ?? CODE_BLOCK_DEFAULT_DARK_THEME;
|
const darkTheme = config?.theme?.dark ?? CODE_BLOCK_DEFAULT_DARK_THEME;
|
||||||
const lightTheme = config?.theme?.light ?? CODE_BLOCK_DEFAULT_LIGHT_THEME;
|
const lightTheme = config?.theme?.light ?? CODE_BLOCK_DEFAULT_LIGHT_THEME;
|
||||||
@@ -78,14 +85,27 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
|
|||||||
override unmounted(): void {
|
override unmounted(): void {
|
||||||
CodeBlockHighlighter._refCount--;
|
CodeBlockHighlighter._refCount--;
|
||||||
|
|
||||||
// Only dispose the shared highlighter when no instances are using it
|
// Dispose the shared highlighter **after** any in-flight creation finishes.
|
||||||
if (
|
if (CodeBlockHighlighter._refCount !== 0) {
|
||||||
CodeBlockHighlighter._refCount === 0 &&
|
return;
|
||||||
CodeBlockHighlighter._sharedHighlighter
|
}
|
||||||
) {
|
|
||||||
CodeBlockHighlighter._sharedHighlighter.dispose();
|
const doDispose = (highlighter: HighlighterCore | null) => {
|
||||||
|
if (highlighter) {
|
||||||
|
highlighter.dispose();
|
||||||
|
}
|
||||||
CodeBlockHighlighter._sharedHighlighter = null;
|
CodeBlockHighlighter._sharedHighlighter = null;
|
||||||
CodeBlockHighlighter._highlighterPromise = null;
|
CodeBlockHighlighter._highlighterPromise = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (CodeBlockHighlighter._sharedHighlighter) {
|
||||||
|
// Highlighter already created – dispose immediately.
|
||||||
|
doDispose(CodeBlockHighlighter._sharedHighlighter);
|
||||||
|
} else if (CodeBlockHighlighter._highlighterPromise) {
|
||||||
|
// Highlighter still being created – wait for it, then dispose.
|
||||||
|
CodeBlockHighlighter._highlighterPromise
|
||||||
|
.then(doDispose)
|
||||||
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||||
|
import { css, html, LitElement, nothing, type TemplateResult } from 'lit';
|
||||||
|
import { property } from 'lit/decorators.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ArtifactSkeleton
|
||||||
|
*
|
||||||
|
* A lightweight loading skeleton used while an artifact preview is fetching / processing.
|
||||||
|
* It mimics the layout of a document – an optional icon followed by several animated grey lines.
|
||||||
|
*
|
||||||
|
* Animation is implemented with pure CSS keyframes (no framer-motion dependency).
|
||||||
|
* Only a single prop is supported for now:
|
||||||
|
* - `icon` – TemplateResult that will be rendered at the top-left position.
|
||||||
|
*/
|
||||||
|
export class ArtifactSkeleton extends LitElement {
|
||||||
|
/* ----- Styling --------------------------------------------------------------------------- */
|
||||||
|
static override styles = css`
|
||||||
|
:host {
|
||||||
|
/* The host is an inline-block so it can size to its contents. */
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
/* The size roughly follows the design used in the legacy React implementation. */
|
||||||
|
width: 250px;
|
||||||
|
height: 200px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional icon wrapper */
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 11px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: ${unsafeCSSVarV2('icon/activated')};
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base line style */
|
||||||
|
.line {
|
||||||
|
position: absolute;
|
||||||
|
left: 11px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: ${unsafeCSSVarV2('layer/background/tertiary')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyframes for each line – width cycles through a handful of values to create movement */
|
||||||
|
@keyframes line1Anim {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
width: 98px;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
width: 85px;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes line2Anim {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
width: 195px;
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
width: 210px;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
width: 165px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes line3Anim {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
width: 163px;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
width: 155px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes line4Anim {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
width: 107px;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
width: 130px;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
85% {
|
||||||
|
width: 115px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes line5Anim {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
width: 134px;
|
||||||
|
}
|
||||||
|
35% {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
65% {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
width: 145px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes line6Anim {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
width: 154px;
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
width: 135px;
|
||||||
|
}
|
||||||
|
55% {
|
||||||
|
width: 175px;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line1 {
|
||||||
|
top: 48.5px;
|
||||||
|
animation: line1Anim 3.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.line2 {
|
||||||
|
top: 73.5px;
|
||||||
|
animation: line2Anim 4.1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.line3 {
|
||||||
|
top: 98.5px;
|
||||||
|
animation: line3Anim 2.8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.line4 {
|
||||||
|
top: 123.5px;
|
||||||
|
animation: line4Anim 3.7s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.line5 {
|
||||||
|
top: 148.5px;
|
||||||
|
animation: line5Anim 3.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.line6 {
|
||||||
|
top: 170.5px;
|
||||||
|
animation: line6Anim 4.3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/* ----- Public API ------------------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Optional icon rendered at the top-left corner.
|
||||||
|
* It should be a lit `TemplateResult`, typically an inline SVG.
|
||||||
|
*/
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor icon: TemplateResult | null = null;
|
||||||
|
|
||||||
|
/* ----- Render --------------------------------------------------------------------------- */
|
||||||
|
override render() {
|
||||||
|
return html`
|
||||||
|
${this.icon ? html`<div class="icon">${this.icon}</div>` : nothing}
|
||||||
|
<div class="line line1"></div>
|
||||||
|
<div class="line line2"></div>
|
||||||
|
<div class="line line3"></div>
|
||||||
|
<div class="line line4"></div>
|
||||||
|
<div class="line line5"></div>
|
||||||
|
<div class="line line6"></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'artifact-skeleton': ArtifactSkeleton;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ import { LoadingIcon } from '@blocksuite/affine/components/icons';
|
|||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import type { ColorScheme } from '@blocksuite/affine/model';
|
import type { ColorScheme } from '@blocksuite/affine/model';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
import { type NotificationService } from '@blocksuite/affine-shared/services';
|
|
||||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||||
import type { Signal } from '@preact/signals-core';
|
import type { Signal } from '@preact/signals-core';
|
||||||
import {
|
import {
|
||||||
@@ -42,18 +41,23 @@ export abstract class ArtifactTool<
|
|||||||
background-color: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
background-color: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artifact-skeleton-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
artifact-skeleton {
|
||||||
|
margin-top: -24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
/** Tool data coming from ChatGPT (tool-call / tool-result). */
|
/** Tool data coming from ChatGPT (tool-call / tool-result). */
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor data!: TData;
|
accessor data!: TData;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor width: Signal<number | undefined> | undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor notificationService!: NotificationService;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor theme!: Signal<ColorScheme>;
|
accessor theme!: Signal<ColorScheme>;
|
||||||
|
|
||||||
@@ -64,14 +68,15 @@ export abstract class ArtifactTool<
|
|||||||
*/
|
*/
|
||||||
protected abstract getCardMeta(): {
|
protected abstract getCardMeta(): {
|
||||||
title: string;
|
title: string;
|
||||||
/** Page / file icon shown when not loading */
|
|
||||||
icon: TemplateResult | HTMLElement | string | null;
|
|
||||||
/** Whether the spinner should be displayed */
|
|
||||||
loading: boolean;
|
|
||||||
/** Extra css class appended to card root */
|
/** Extra css class appended to card root */
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon shown in the card (when not loading) and in the loading skeleton.
|
||||||
|
*/
|
||||||
|
protected abstract getIcon(): TemplateResult | HTMLElement | string | null;
|
||||||
|
|
||||||
/** Banner shown on the right side of the card (can be undefined). */
|
/** Banner shown on the right side of the card (can be undefined). */
|
||||||
protected abstract getBanner(
|
protected abstract getBanner(
|
||||||
theme: ColorScheme
|
theme: ColorScheme
|
||||||
@@ -90,11 +95,14 @@ export abstract class ArtifactTool<
|
|||||||
|
|
||||||
/** Open or refresh the preview panel. */
|
/** Open or refresh the preview panel. */
|
||||||
private openOrUpdatePreviewPanel() {
|
private openOrUpdatePreviewPanel() {
|
||||||
renderPreviewPanel(
|
const content = this.isLoading()
|
||||||
this,
|
? this.renderLoadingSkeleton()
|
||||||
this.getPreviewContent(),
|
: this.getPreviewContent();
|
||||||
this.getPreviewControls()
|
renderPreviewPanel(this, content, this.getPreviewControls());
|
||||||
);
|
}
|
||||||
|
|
||||||
|
protected isLoading(): boolean {
|
||||||
|
return this.data.type !== 'tool-result';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected refreshPreviewPanel() {
|
protected refreshPreviewPanel() {
|
||||||
@@ -108,18 +116,23 @@ export abstract class ArtifactTool<
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected renderLoadingSkeleton() {
|
||||||
|
const icon = this.getIcon();
|
||||||
|
return html`<div class="artifact-skeleton-container">
|
||||||
|
<artifact-skeleton .icon=${icon}></artifact-skeleton>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly onCardClick = (_e: Event) => {
|
private readonly onCardClick = (_e: Event) => {
|
||||||
this.openOrUpdatePreviewPanel();
|
this.openOrUpdatePreviewPanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
protected renderCard() {
|
protected renderCard() {
|
||||||
const { title, icon, loading, className } = this.getCardMeta();
|
const { title, className } = this.getCardMeta();
|
||||||
|
|
||||||
const resolvedIcon = loading
|
const resolvedIcon = this.isLoading()
|
||||||
? LoadingIcon({
|
? LoadingIcon({ size: '20px' })
|
||||||
size: '20px',
|
: this.getIcon();
|
||||||
})
|
|
||||||
: icon;
|
|
||||||
|
|
||||||
const banner = this.getBanner(this.theme.value);
|
const banner = this.getBanner(this.theme.value);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
|||||||
import { ColorScheme } from '@blocksuite/affine/model';
|
import { ColorScheme } from '@blocksuite/affine/model';
|
||||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { type BlockStdScope } from '@blocksuite/affine/std';
|
import { type BlockStdScope } from '@blocksuite/affine/std';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import {
|
import {
|
||||||
CodeBlockIcon,
|
CodeBlockIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
@@ -437,6 +438,9 @@ export class CodeArtifactTool extends ArtifactTool<
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor std: BlockStdScope | undefined;
|
accessor std: BlockStdScope | undefined;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private accessor mode: 'preview' | 'code' = 'code';
|
private accessor mode: 'preview' | 'code' = 'code';
|
||||||
|
|
||||||
@@ -447,25 +451,19 @@ export class CodeArtifactTool extends ArtifactTool<
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected getCardMeta() {
|
protected getCardMeta() {
|
||||||
const loading = this.data.type === 'tool-call';
|
|
||||||
return {
|
return {
|
||||||
title: this.data.args.title,
|
title: this.data.args.title,
|
||||||
icon: CodeBlockIcon({ width: '20', height: '20' }),
|
|
||||||
loading,
|
|
||||||
className: 'code-artifact-result',
|
className: 'code-artifact-result',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override getIcon() {
|
||||||
|
return CodeBlockIcon();
|
||||||
|
}
|
||||||
|
|
||||||
protected override getPreviewContent() {
|
protected override getPreviewContent() {
|
||||||
if (this.data.type !== 'tool-result' || !this.data.result) {
|
if (this.data.type !== 'tool-result' || !this.data.result) {
|
||||||
// loading state
|
return html``;
|
||||||
return html`<div class="code-artifact-preview">
|
|
||||||
<div
|
|
||||||
style="display:flex;justify-content:center;align-items:center;height:100%"
|
|
||||||
>
|
|
||||||
${CodeBlockIcon({ width: '24', height: '24' })}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = this.data.result;
|
const result = this.data.result;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
||||||
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace';
|
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace';
|
||||||
import { getEmbedLinkedDocIcons } from '@blocksuite/affine/blocks/embed-doc';
|
import { getEmbedLinkedDocIcons } from '@blocksuite/affine/blocks/embed-doc';
|
||||||
import { LoadingIcon } from '@blocksuite/affine/components/icons';
|
|
||||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
||||||
import type { ColorScheme } from '@blocksuite/affine/model';
|
import type { ColorScheme } from '@blocksuite/affine/model';
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
||||||
|
import type { NotificationService } from '@blocksuite/affine-shared/services';
|
||||||
import { CopyIcon, PageIcon, ToolIcon } from '@blocksuite/icons/lit';
|
import { CopyIcon, PageIcon, ToolIcon } from '@blocksuite/icons/lit';
|
||||||
import type { BlockStdScope } from '@blocksuite/std';
|
import type { BlockStdScope } from '@blocksuite/std';
|
||||||
import { css, html } from 'lit';
|
import { css, html } from 'lit';
|
||||||
@@ -88,6 +88,9 @@ export class DocComposeTool extends ArtifactTool<
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor std: BlockStdScope | undefined;
|
accessor std: BlockStdScope | undefined;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor notificationService!: NotificationService;
|
||||||
|
|
||||||
protected getBanner(theme: ColorScheme) {
|
protected getBanner(theme: ColorScheme) {
|
||||||
const { LinkedDocEmptyBanner } = getEmbedLinkedDocIcons(
|
const { LinkedDocEmptyBanner } = getEmbedLinkedDocIcons(
|
||||||
theme,
|
theme,
|
||||||
@@ -98,15 +101,16 @@ export class DocComposeTool extends ArtifactTool<
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected getCardMeta() {
|
protected getCardMeta() {
|
||||||
const composing = this.data.type === 'tool-call';
|
|
||||||
return {
|
return {
|
||||||
title: this.data.args.title,
|
title: this.data.args.title,
|
||||||
icon: PageIcon(),
|
|
||||||
loading: composing,
|
|
||||||
className: 'doc-compose-result',
|
className: 'doc-compose-result',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override getIcon() {
|
||||||
|
return PageIcon();
|
||||||
|
}
|
||||||
|
|
||||||
protected override getPreviewContent() {
|
protected override getPreviewContent() {
|
||||||
if (!this.std) return html``;
|
if (!this.std) return html``;
|
||||||
const resultData = this.data;
|
const resultData = this.data;
|
||||||
@@ -126,11 +130,7 @@ export class DocComposeTool extends ArtifactTool<
|
|||||||
theme: this.theme,
|
theme: this.theme,
|
||||||
}}
|
}}
|
||||||
></text-renderer>`
|
></text-renderer>`
|
||||||
: html`<div class="doc-compose-result-preview-loading">
|
: html``}
|
||||||
${LoadingIcon({
|
|
||||||
size: '32px',
|
|
||||||
})}
|
|
||||||
</div>`}
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { ChatMessageAction } from './chat-panel/message/action';
|
|||||||
import { ChatMessageAssistant } from './chat-panel/message/assistant';
|
import { ChatMessageAssistant } from './chat-panel/message/assistant';
|
||||||
import { ChatMessageUser } from './chat-panel/message/user';
|
import { ChatMessageUser } from './chat-panel/message/user';
|
||||||
import { ChatPanelSplitView } from './chat-panel/split-view';
|
import { ChatPanelSplitView } from './chat-panel/split-view';
|
||||||
|
import { ArtifactSkeleton } from './components/ai-artifact-skeleton';
|
||||||
import { AIChatAddContext } from './components/ai-chat-add-context';
|
import { AIChatAddContext } from './components/ai-chat-add-context';
|
||||||
import { ChatPanelAddPopover } from './components/ai-chat-chips/add-popover';
|
import { ChatPanelAddPopover } from './components/ai-chat-chips/add-popover';
|
||||||
import { ChatPanelCandidatesPopover } from './components/ai-chat-chips/candidates-popover';
|
import { ChatPanelCandidatesPopover } from './components/ai-chat-chips/candidates-popover';
|
||||||
@@ -243,4 +244,5 @@ export function registerAIEffects() {
|
|||||||
|
|
||||||
customElements.define('transcription-block', LitTranscriptionBlock);
|
customElements.define('transcription-block', LitTranscriptionBlock);
|
||||||
customElements.define('chat-panel-split-view', ChatPanelSplitView);
|
customElements.define('chat-panel-split-view', ChatPanelSplitView);
|
||||||
|
customElements.define('artifact-skeleton', ArtifactSkeleton);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user