feat: block slides insert before image load finished (#7948)

This commit is contained in:
darkskygit 2024-08-22 17:46:06 +00:00
parent 5e555b3807
commit b57388fd85
No known key found for this signature in database
GPG Key ID: 97B7D036B1566E9D
3 changed files with 50 additions and 100 deletions

View File

@ -18,11 +18,13 @@ import {
fitContent,
ImageBlockModel,
InsertBelowIcon,
LightLoadingIcon,
NoteDisplayMode,
ResetIcon,
} from '@blocksuite/blocks';
import { assertExists, Bound } from '@blocksuite/global/utils';
import type { TemplateResult } from 'lit';
import { html, type TemplateResult } from 'lit';
import { styleMap } from 'lit/directives/style-map.js';
import { AIPenIcon, ChatWithAIIcon } from '../_common/icons';
import { insertFromMarkdown } from '../_common/markdown-utils';
@ -98,27 +100,55 @@ export function retry(panel: AffineAIPanelWidget): AIItemConfig {
};
}
const extraConditions: Record<string, (data: any) => boolean> = {
createSlides: data => !!data.contents,
};
export function createInsertResp<T extends keyof BlockSuitePresets.AIActions>(
id: T,
handler: (host: EditorHost, ctx: CtxRecord) => void,
host: EditorHost,
ctx: CtxRecord,
buttonText: string = 'Insert below'
): AIItemConfig {
return {
name: buttonText,
icon: InsertBelowIcon,
showWhen: () => {
const panel = getAIPanel(host);
return !EXCLUDING_INSERT_ACTIONS.includes(id) && !!panel.answer;
): AIItemConfig[] {
const extraCondition = extraConditions[id] || ((_: any) => true);
return [
{
name: `${buttonText} - Loading...`,
icon: html`<div style=${styleMap({ height: '20px', width: '20px' })}>
${LightLoadingIcon}
</div>`,
showWhen: () => {
const panel = getAIPanel(host);
const data = ctx.get();
return (
!EXCLUDING_INSERT_ACTIONS.includes(id) &&
!!panel.answer &&
// required data for insert
!extraCondition(data)
);
},
},
handler: () => {
reportResponse('result:insert');
handler(host, ctx);
const panel = getAIPanel(host);
panel.hide();
{
name: buttonText,
icon: InsertBelowIcon,
showWhen: () => {
const panel = getAIPanel(host);
const data = ctx.get();
return (
!EXCLUDING_INSERT_ACTIONS.includes(id) &&
!!panel.answer &&
// required data for insert
!!extraCondition(data)
);
},
handler: () => {
reportResponse('result:insert');
handler(host, ctx);
const panel = getAIPanel(host);
panel.hide();
},
},
};
];
}
export function asCaption<T extends keyof BlockSuitePresets.AIActions>(
@ -555,7 +585,7 @@ export function actionToResponse<T extends keyof BlockSuitePresets.AIActions>(
panel.hide();
},
},
getInsertAndReplaceHandler(id, host, ctx, variants),
...getInsertAndReplaceHandler(id, host, ctx, variants),
asCaption(id, host),
retry(getAIPanel(host)),
discard(getAIPanel(host), getEdgelessCopilotWidget(host)),
@ -603,7 +633,7 @@ export function actionToErrorResponse<
responses: [
{
name: 'Response',
items: [getInsertAndReplaceHandler(id, host, ctx, variants)],
items: getInsertAndReplaceHandler(id, host, ctx, variants),
},
{
name: '',

View File

@ -1,84 +0,0 @@
import type { EditorHost } from '@blocksuite/block-std';
import type { EdgelessRootService } from '@blocksuite/blocks';
import type { BlockSnapshot } from '@blocksuite/store';
import { markdownToSnapshot } from '../_common/markdown-utils';
import { getSurfaceElementFromEditor } from '../_common/selection-utils';
import { basicTheme } from '../slides/template';
type PPTSection = {
title: string;
content: string;
keywords: string;
};
type PPTDoc = {
isCover: boolean;
title: string;
sections: PPTSection[];
};
export const PPTBuilder = (host: EditorHost) => {
const service = host.spec.getService<EdgelessRootService>('affine:page');
const docs: PPTDoc[] = [];
let done = false;
const addDoc = async (block: BlockSnapshot) => {
const sections = block.children.map(v => {
const title = getText(v);
const keywords = getText(v.children[0]);
const content = getText(v.children[1]);
return {
title,
keywords,
content,
} satisfies PPTSection;
});
const doc: PPTDoc = {
isCover: docs.length === 0,
title: getText(block),
sections,
};
docs.push(doc);
if (doc.sections.length !== 3 || doc.isCover) return;
if (done) return;
done = true;
const job = service.createTemplateJob('template');
const { images, content } = await basicTheme(doc);
if (images.length) {
await Promise.all(
images.map(({ id, url }) =>
fetch(url)
.then(res => res.blob())
.then(blob => job.job.assets.set(id, blob))
)
);
}
await job.insertTemplate(content);
getSurfaceElementFromEditor(host).refresh();
};
return {
process: async (text: string) => {
const snapshot = await markdownToSnapshot(text, host);
const block = snapshot.snapshot.content[0];
for (const child of block.children) {
await addDoc(child);
const { centerX, centerY, zoom } = service.getFitToScreenData();
service.viewport.setViewport(zoom, [centerX, centerY]);
}
},
done: async (text: string) => {
const snapshot = await markdownToSnapshot(text, host);
const block = snapshot.snapshot.content[0];
await addDoc(block.children[block.children.length - 1]);
},
};
};
const getText = (block: BlockSnapshot) => {
// @ts-expect-error allow
return block.props.text?.delta?.[0]?.insert ?? '';
};

View File

@ -85,6 +85,10 @@ export class AISlidesRenderer extends WithDisposable(LitElement) {
contents: res.contents,
images: res.images,
});
// refresh loading menu item
getAIPanel(this.host)
.shadowRoot?.querySelector('ai-panel-answer')
?.requestUpdate();
}
})
.catch(console.error);