diff --git a/packages/common/env/package.json b/packages/common/env/package.json index 0372b0fb00..2409536813 100644 --- a/packages/common/env/package.json +++ b/packages/common/env/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "devDependencies": { - "@blocksuite/affine": "0.19.0", + "@blocksuite/affine": "0.19.1", "vitest": "2.1.8" }, "exports": { diff --git a/packages/common/infra/package.json b/packages/common/infra/package.json index 4f30467962..e76b615da2 100644 --- a/packages/common/infra/package.json +++ b/packages/common/infra/package.json @@ -15,7 +15,7 @@ "@affine/debug": "workspace:*", "@affine/env": "workspace:*", "@affine/templates": "workspace:*", - "@blocksuite/affine": "0.19.0", + "@blocksuite/affine": "0.19.1", "@datastructures-js/binary-search-tree": "^5.3.2", "eventemitter2": "^6.4.9", "foxact": "^0.2.43", diff --git a/packages/frontend/apps/android/package.json b/packages/frontend/apps/android/package.json index 28b40b97a6..4ffa15a965 100644 --- a/packages/frontend/apps/android/package.json +++ b/packages/frontend/apps/android/package.json @@ -13,7 +13,7 @@ "@affine/component": "workspace:*", "@affine/core": "workspace:*", "@affine/i18n": "workspace:*", - "@blocksuite/affine": "0.19.0", + "@blocksuite/affine": "0.19.1", "@blocksuite/icons": "2.1.75", "@capacitor/android": "^6.2.0", "@capacitor/core": "^6.2.0", diff --git a/packages/frontend/apps/electron/package.json b/packages/frontend/apps/electron/package.json index b5200d78ea..3dea0f3d7d 100644 --- a/packages/frontend/apps/electron/package.json +++ b/packages/frontend/apps/electron/package.json @@ -30,7 +30,7 @@ "@affine/i18n": "workspace:*", "@affine/native": "workspace:*", "@affine/nbstore": "workspace:*", - "@blocksuite/affine": "0.19.0", + "@blocksuite/affine": "0.19.1", "@electron-forge/cli": "^7.6.0", "@electron-forge/core": "^7.6.0", "@electron-forge/core-utils": "^7.6.0", diff --git a/packages/frontend/apps/ios/package.json b/packages/frontend/apps/ios/package.json index 8298e44a41..2e9f1d1f27 100644 --- a/packages/frontend/apps/ios/package.json +++ b/packages/frontend/apps/ios/package.json @@ -15,7 +15,7 @@ "@affine/component": "workspace:*", "@affine/core": "workspace:*", "@affine/i18n": "workspace:*", - "@blocksuite/affine": "0.19.0", + "@blocksuite/affine": "0.19.1", "@blocksuite/icons": "2.1.75", "@capacitor/app": "^6.0.2", "@capacitor/browser": "^6.0.4", diff --git a/packages/frontend/apps/mobile/package.json b/packages/frontend/apps/mobile/package.json index 2c6af6469f..1eb86a5674 100644 --- a/packages/frontend/apps/mobile/package.json +++ b/packages/frontend/apps/mobile/package.json @@ -13,7 +13,7 @@ "@affine/component": "workspace:*", "@affine/core": "workspace:*", "@affine/i18n": "workspace:*", - "@blocksuite/affine": "0.19.0", + "@blocksuite/affine": "0.19.1", "@blocksuite/icons": "2.1.75", "@sentry/react": "^8.44.0", "react": "^19.0.0", diff --git a/packages/frontend/component/package.json b/packages/frontend/component/package.json index fee9b3b2e2..2876861986 100644 --- a/packages/frontend/component/package.json +++ b/packages/frontend/component/package.json @@ -66,7 +66,7 @@ "zod": "^3.24.1" }, "devDependencies": { - "@blocksuite/affine": "0.19.0", + "@blocksuite/affine": "0.19.1", "@blocksuite/icons": "2.1.75", "@chromatic-com/storybook": "^3.2.2", "@storybook/addon-essentials": "^8.4.7", diff --git a/packages/frontend/core/package.json b/packages/frontend/core/package.json index dd4c868eaa..b68f699e5b 100644 --- a/packages/frontend/core/package.json +++ b/packages/frontend/core/package.json @@ -16,7 +16,7 @@ "@affine/i18n": "workspace:*", "@affine/templates": "workspace:*", "@affine/track": "workspace:*", - "@blocksuite/affine": "0.19.0", + "@blocksuite/affine": "0.19.1", "@blocksuite/icons": "2.1.75", "@capacitor/app": "^6.0.2", "@capacitor/browser": "^6.0.4", diff --git a/packages/frontend/core/src/blocksuite/presets/ai/_common/components/ask-ai-toolbar.ts b/packages/frontend/core/src/blocksuite/presets/ai/_common/components/ask-ai-toolbar.ts index d07956d79f..1da93ff7ce 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/_common/components/ask-ai-toolbar.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/_common/components/ask-ai-toolbar.ts @@ -9,8 +9,8 @@ import { flip, offset } from '@floating-ui/dom'; import { css, html, LitElement } from 'lit'; import { property } from 'lit/decorators.js'; -import { getAIPanel } from '../../ai-panel'; import { AIProvider } from '../../provider'; +import { getAIPanelWidget } from '../../utils/ai-widgets'; import { extractContext } from '../../utils/extract'; export class AskAIToolbarButton extends WithDisposable(LitElement) { @@ -33,7 +33,7 @@ export class AskAIToolbarButton extends WithDisposable(LitElement) { accessor actionGroups!: AIItemGroupConfig[]; private readonly _onItemClick = () => { - const aiPanel = getAIPanel(this.host); + const aiPanel = getAIPanelWidget(this.host); aiPanel.restoreSelection(); this._clearAbortController(); }; @@ -45,7 +45,7 @@ export class AskAIToolbarButton extends WithDisposable(LitElement) { private readonly _openAIPanel = () => { this._clearAbortController(); - const aiPanel = getAIPanel(this.host); + const aiPanel = getAIPanelWidget(this.host); this._abortController = new AbortController(); this._panelRoot = createLitPortal({ template: html` @@ -69,7 +69,7 @@ export class AskAIToolbarButton extends WithDisposable(LitElement) { private readonly _generateAnswer: AffineAIPanelWidgetConfig['generateAnswer'] = ({ finish, input }) => { finish('success'); - const aiPanel = getAIPanel(this.host); + const aiPanel = getAIPanelWidget(this.host); aiPanel.discard(); AIProvider.slots.requestOpenWithChat.emit({ host: this.host }); extractContext(this.host) @@ -80,7 +80,7 @@ export class AskAIToolbarButton extends WithDisposable(LitElement) { }; private readonly _onClick = () => { - const aiPanel = getAIPanel(this.host); + const aiPanel = getAIPanelWidget(this.host); if (!aiPanel.config) return; aiPanel.config.generateAnswer = this._generateAnswer; aiPanel.config.inputCallback = text => { diff --git a/packages/frontend/core/src/blocksuite/presets/ai/_common/config.ts b/packages/frontend/core/src/blocksuite/presets/ai/_common/config.ts index 6136e7f6a7..9a9e65ad0d 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/_common/config.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/_common/config.ts @@ -1,32 +1,19 @@ -import type { - Chain, - EditorHost, - InitCommandCtx, -} from '@blocksuite/affine/block-std'; +import type { Chain, InitCommandCtx } from '@blocksuite/affine/block-std'; import { type AIItemGroupConfig, type AISubItemConfig, - EDGELESS_ELEMENT_TOOLBAR_WIDGET, - type EdgelessElementToolbarWidget, matchFlavours, } from '@blocksuite/affine/blocks'; -import type { TemplateResult } from 'lit'; import { actionToHandler } from '../actions/doc-handler'; -import { actionToHandler as edgelessActionToHandler } from '../actions/edgeless-handler'; import { imageFilterStyles, imageProcessingTypes, textTones, translateLangs, } from '../actions/types'; -import { getAIPanel } from '../ai-panel'; import { AIProvider } from '../provider'; -import { - getSelectedImagesAsBlobs, - getSelectedTextContent, - getSelections, -} from '../utils/selection-utils'; +import { getAIPanelWidget } from '../utils/ai-widgets'; import { AIDoneIcon, AIImageIcon, @@ -71,7 +58,7 @@ export function createImageFilterSubItem( return imageFilterStyles.map(style => { return { type: style, - handler: edgelessHandler( + handler: actionToHandler( 'filterImage', AIImageIconWithAnimation, { @@ -89,7 +76,7 @@ export function createImageProcessingSubItem( return imageProcessingTypes.map(type => { return { type, - handler: edgelessHandler( + handler: actionToHandler( 'processImage', AIImageIconWithAnimation, { @@ -224,59 +211,6 @@ const DraftAIGroup: AIItemGroupConfig = { ], }; -// actions that initiated from a note in edgeless mode -// 1. when running in doc mode, call requestRunInEdgeless (let affine to show toast) -// 2. when running in edgeless mode -// a. get selected in the note and let the edgeless action to handle it -// b. insert the result using the note shape -function edgelessHandler( - id: T, - generatingIcon: TemplateResult<1>, - variants?: Omit< - Parameters[0], - keyof BlockSuitePresets.AITextActionOptions - >, - trackerOptions?: BlockSuitePresets.TrackerOptions -) { - return (host: EditorHost) => { - if (host.doc.root?.id === undefined) return; - const edgeless = ( - host.view.getWidget( - EDGELESS_ELEMENT_TOOLBAR_WIDGET, - host.doc.root.id - ) as EdgelessElementToolbarWidget - )?.edgeless; - - if (!edgeless) { - AIProvider.slots.requestRunInEdgeless.emit({ host }); - } else { - const selectedElements = edgeless.service.selection.selectedElements; - if (!selectedElements.length) return; - - return edgelessActionToHandler( - id, - generatingIcon, - variants, - async () => { - const selections = getSelections(host); - const [markdown, attachments] = await Promise.all([ - getSelectedTextContent(host), - getSelectedImagesAsBlobs(host), - ]); - // for now if there are more than one selected blocks, we will not omit the attachments - const sendAttachments = - selections?.selectedBlocks?.length === 1 && attachments.length > 0; - return { - attachments: sendAttachments ? attachments : undefined, - input: sendAttachments ? '' : markdown, - }; - }, - trackerOptions - )(host); - } - }; -} - const ReviewWIthAIGroup: AIItemGroupConfig = { name: 'review with ai', items: [ @@ -353,7 +287,7 @@ const GenerateWithAIGroup: AIItemGroupConfig = { name: 'Generate an image', icon: AIImageIcon, showWhen: textBlockShowWhen, - handler: edgelessHandler('createImage', AIImageIconWithAnimation), + handler: actionToHandler('createImage', AIImageIconWithAnimation), }, { name: 'Generate outline', @@ -365,13 +299,13 @@ const GenerateWithAIGroup: AIItemGroupConfig = { name: 'Brainstorm ideas with mind map', icon: AIMindMapIcon, showWhen: textBlockShowWhen, - handler: edgelessHandler('brainstormMindmap', AIPenIconWithAnimation), + handler: actionToHandler('brainstormMindmap', AIPenIconWithAnimation), }, { name: 'Generate presentation', icon: AIPresentationIcon, showWhen: textBlockShowWhen, - handler: edgelessHandler('createSlides', AIPresentationIconWithAnimation), + handler: actionToHandler('createSlides', AIPresentationIconWithAnimation), beta: true, }, { @@ -379,7 +313,7 @@ const GenerateWithAIGroup: AIItemGroupConfig = { icon: MakeItRealIcon, beta: true, showWhen: textBlockShowWhen, - handler: edgelessHandler('makeItReal', MakeItRealIconWithAnimation), + handler: actionToHandler('makeItReal', MakeItRealIconWithAnimation), }, { name: 'Find actions', @@ -398,7 +332,7 @@ const OthersAIGroup: AIItemGroupConfig = { name: 'Continue with AI', icon: CommentIcon, handler: host => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); AIProvider.slots.requestOpenWithChat.emit({ host, autoSelect: true, @@ -411,7 +345,7 @@ const OthersAIGroup: AIItemGroupConfig = { name: 'Open AI Chat', icon: ChatWithAIIcon, handler: host => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); AIProvider.slots.requestOpenWithChat.emit({ host, appendCard: true, @@ -422,7 +356,7 @@ const OthersAIGroup: AIItemGroupConfig = { ], }; -export const AIItemGroups: AIItemGroupConfig[] = [ +export const pageAIGroups: AIItemGroupConfig[] = [ ReviewWIthAIGroup, EditAIGroup, GenerateWithAIGroup, @@ -455,7 +389,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] { name: 'Generate an image', icon: AIImageIcon, showWhen: () => true, - handler: edgelessHandler( + handler: actionToHandler( 'createImage', AIImageIconWithAnimation, undefined, diff --git a/packages/frontend/core/src/blocksuite/presets/ai/actions/answer-renderer.ts b/packages/frontend/core/src/blocksuite/presets/ai/actions/answer-renderer.ts new file mode 100644 index 0000000000..b838bda125 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/presets/ai/actions/answer-renderer.ts @@ -0,0 +1,58 @@ +import type { EditorHost } from '@blocksuite/affine/block-std'; +import type { + AffineAIPanelWidget, + MindmapElementModel, +} from '@blocksuite/affine/blocks'; + +import { createTextRenderer } from '../../_common'; +import { + createMindmapExecuteRenderer, + createMindmapRenderer, +} from '../messages/mindmap'; +import { createSlidesRenderer } from '../messages/slides-renderer'; +import { createIframeRenderer, createImageRenderer } from '../messages/wrapper'; +import type { AIContext } from '../utils/context'; +import { isMindmapChild, isMindMapRoot } from '../utils/edgeless'; +import { IMAGE_ACTIONS } from './consts'; +import { responseToExpandMindmap } from './edgeless-response'; + +type AnswerRenderer = NonNullable< + AffineAIPanelWidget['config'] +>['answerRenderer']; + +export function actionToAnswerRenderer< + T extends keyof BlockSuitePresets.AIActions, +>(id: T, host: EditorHost, ctx: AIContext): AnswerRenderer { + if (id === 'brainstormMindmap') { + const selectedElements = ctx.get().selectedElements; + if ( + selectedElements && + (isMindMapRoot(selectedElements[0]) || + isMindmapChild(selectedElements[0])) + ) { + const mindmap = selectedElements[0].group as MindmapElementModel; + + return createMindmapRenderer(host, ctx, mindmap.style); + } + + return createMindmapRenderer(host, ctx); + } + + if (id === 'expandMindmap') { + return createMindmapExecuteRenderer(host, ctx, responseToExpandMindmap); + } + + if (id === 'createSlides') { + return createSlidesRenderer(host, ctx); + } + + if (id === 'makeItReal') { + return createIframeRenderer(host, { height: 300 }); + } + + if (IMAGE_ACTIONS.includes(id)) { + return createImageRenderer(host, { height: 300 }); + } + + return createTextRenderer(host, { maxHeight: 320 }); +} diff --git a/packages/frontend/core/src/blocksuite/presets/ai/actions/consts.ts b/packages/frontend/core/src/blocksuite/presets/ai/actions/consts.ts index f69ca9e0cc..c061278a5b 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/actions/consts.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/actions/consts.ts @@ -9,6 +9,16 @@ export const EXCLUDING_COPY_ACTIONS = [ 'processImage', ]; +export const EXCLUDING_REPLACE_ACTIONS = [ + 'brainstormMindmap', + 'expandMindmap', + 'makeItReal', + 'createSlides', + 'createImage', + 'filterImage', + 'processImage', +]; + export const EXCLUDING_INSERT_ACTIONS = ['generateCaption']; export const IMAGE_ACTIONS = ['createImage', 'processImage', 'filterImage']; diff --git a/packages/frontend/core/src/blocksuite/presets/ai/actions/doc-handler.ts b/packages/frontend/core/src/blocksuite/presets/ai/actions/doc-handler.ts index 3c5cabca1f..14f38ecb7e 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/actions/doc-handler.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/actions/doc-handler.ts @@ -7,22 +7,23 @@ import type { import { assertExists } from '@blocksuite/affine/global/utils'; import type { TemplateResult } from 'lit'; -import { createTextRenderer } from '../../_common'; import { buildCopyConfig, buildErrorConfig, buildFinishConfig, buildGeneratingConfig, - getAIPanel, } from '../ai-panel'; import { AIProvider } from '../provider'; import { reportResponse } from '../utils/action-reporter'; +import { getAIPanelWidget } from '../utils/ai-widgets'; +import { AIContext } from '../utils/context'; import { getSelectedImagesAsBlobs, getSelectedTextContent, getSelections, selectAboveBlocks, } from '../utils/selection-utils'; +import { actionToAnswerRenderer } from './answer-renderer'; export function bindTextStream( stream: BlockSuitePresets.TextStream, @@ -174,8 +175,10 @@ function updateAIPanelConfig( variants, trackerOptions )(host); - config.answerRenderer = createTextRenderer(host, { maxHeight: 320 }); - config.finishStateConfig = buildFinishConfig(aiPanel, id); + + const ctx = new AIContext(); + config.answerRenderer = actionToAnswerRenderer(id, host, ctx); + config.finishStateConfig = buildFinishConfig(aiPanel, id, ctx); config.generatingStateConfig = buildGeneratingConfig(generatingIcon); config.errorStateConfig = buildErrorConfig(aiPanel); config.copy = buildCopyConfig(aiPanel); @@ -194,7 +197,7 @@ export function actionToHandler( trackerOptions?: BlockSuitePresets.TrackerOptions ) { return (host: EditorHost) => { - const aiPanel = getAIPanel(host); + const aiPanel = getAIPanelWidget(host); updateAIPanelConfig(aiPanel, id, generatingIcon, variants, trackerOptions); const { selectedBlocks: blocks } = getSelections(aiPanel.host); if (!blocks || blocks.length === 0) return; @@ -205,7 +208,7 @@ export function actionToHandler( } export function handleInlineAskAIAction(host: EditorHost) { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); const selection = host.selection.find('text'); const lastBlockPath = selection ? (selection.to?.blockId ?? selection.blockId) diff --git a/packages/frontend/core/src/blocksuite/presets/ai/actions/edgeless-handler.ts b/packages/frontend/core/src/blocksuite/presets/ai/actions/edgeless-handler.ts index 988eb7c745..808d29f5e5 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/actions/edgeless-handler.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/actions/edgeless-handler.ts @@ -3,7 +3,6 @@ import type { AffineAIPanelWidget, AIError, EdgelessCopilotWidget, - MindmapElementModel, } from '@blocksuite/affine/blocks'; import { BlocksUtils, @@ -20,16 +19,11 @@ import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; import { AIChatBlockModel } from '@toeverything/infra'; import type { TemplateResult } from 'lit'; -import { createTextRenderer, getContentFromSlice } from '../../_common'; -import { getAIPanel } from '../ai-panel'; -import { - createMindmapExecuteRenderer, - createMindmapRenderer, -} from '../messages/mindmap'; -import { createSlidesRenderer } from '../messages/slides-renderer'; -import { createIframeRenderer, createImageRenderer } from '../messages/wrapper'; +import { getContentFromSlice } from '../../_common'; import { AIProvider } from '../provider'; import { reportResponse } from '../utils/action-reporter'; +import { getAIPanelWidget } from '../utils/ai-widgets'; +import { AIContext } from '../utils/context'; import { getEdgelessCopilotWidget, isMindmapChild, @@ -41,63 +35,15 @@ import { getSelectedNoteAnchor, getSelections, } from '../utils/selection-utils'; -import { EXCLUDING_COPY_ACTIONS, IMAGE_ACTIONS } from './consts'; +import { actionToAnswerRenderer } from './answer-renderer'; +import { EXCLUDING_COPY_ACTIONS } from './consts'; import { bindTextStream } from './doc-handler'; import { actionToErrorResponse, actionToGenerating, actionToResponse, getElementToolbar, - responses, } from './edgeless-response'; -import type { CtxRecord } from './types'; - -type AnswerRenderer = NonNullable< - AffineAIPanelWidget['config'] ->['answerRenderer']; - -function actionToRenderer( - id: T, - host: EditorHost, - ctx: CtxRecord -): AnswerRenderer { - if (id === 'brainstormMindmap') { - const selectedElements = ctx.get()[ - 'selectedElements' - ] as BlockSuite.EdgelessModel[]; - - if ( - isMindMapRoot(selectedElements[0]) || - isMindmapChild(selectedElements[0]) - ) { - const mindmap = selectedElements[0].group as MindmapElementModel; - - return createMindmapRenderer(host, ctx, mindmap.style); - } - - return createMindmapRenderer(host, ctx); - } - - if (id === 'expandMindmap') { - return createMindmapExecuteRenderer(host, ctx, ctx => { - responses['expandMindmap']?.(host, ctx); - }); - } - - if (id === 'createSlides') { - return createSlidesRenderer(host, ctx); - } - - if (id === 'makeItReal') { - return createIframeRenderer(host, { height: 300 }); - } - - if (IMAGE_ACTIONS.includes(id)) { - return createImageRenderer(host, { height: 300 }); - } - - return createTextRenderer(host, { maxHeight: 320 }); -} async function getContentFromEmbedSyncedDocModel( host: EditorHost, @@ -212,7 +158,7 @@ function actionToStream( >, extract?: ( host: EditorHost, - ctx: CtxRecord + ctx: AIContext ) => Promise<{ content?: string; attachments?: (string | Blob)[]; @@ -226,7 +172,7 @@ function actionToStream( if (!action || typeof action !== 'function') return; if (extract && typeof extract === 'function') { - return (host: EditorHost, ctx: CtxRecord): BlockSuitePresets.TextStream => { + return (host: EditorHost, ctx: AIContext): BlockSuitePresets.TextStream => { let stream: BlockSuitePresets.TextStream | undefined; const control = trackerOptions?.control || 'format-bar'; const where = trackerOptions?.where || 'ai-panel'; @@ -270,7 +216,7 @@ function actionToStream( let stream: BlockSuitePresets.TextStream | undefined; return { async *[Symbol.asyncIterator]() { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); const models = getCopilotSelectedElems(host); const markdown = await getTextFromSelected(panel.host); @@ -304,7 +250,7 @@ function actionToGeneration( >, extract?: ( host: EditorHost, - ctx: CtxRecord + ctx: AIContext ) => Promise<{ content?: string; attachments?: (string | Blob)[]; @@ -312,7 +258,7 @@ function actionToGeneration( } | void>, trackerOptions?: BlockSuitePresets.TrackerOptions ) { - return (host: EditorHost, ctx: CtxRecord) => { + return (host: EditorHost, ctx: AIContext) => { return ({ input, signal, @@ -352,14 +298,14 @@ function updateEdgelessAIPanelConfig< edgelessCopilot: EdgelessCopilotWidget, id: T, generatingIcon: TemplateResult<1>, - ctx: CtxRecord, + ctx: AIContext, variants?: Omit< Parameters[0], keyof BlockSuitePresets.AITextActionOptions >, customInput?: ( host: EditorHost, - ctx: CtxRecord + ctx: AIContext ) => Promise<{ input?: string; content?: string; @@ -371,7 +317,7 @@ function updateEdgelessAIPanelConfig< const host = aiPanel.host; const { config } = aiPanel; assertExists(config); - config.answerRenderer = actionToRenderer(id, host, ctx); + config.answerRenderer = actionToAnswerRenderer(id, host, ctx); config.generateAnswer = actionToGeneration( id, variants, @@ -420,7 +366,7 @@ export function actionToHandler( >, customInput?: ( host: EditorHost, - ctx: CtxRecord + ctx: AIContext ) => Promise<{ input?: string; content?: string; @@ -430,22 +376,11 @@ export function actionToHandler( trackerOptions?: BlockSuitePresets.TrackerOptions ) { return (host: EditorHost) => { - const aiPanel = getAIPanel(host); + const aiPanel = getAIPanelWidget(host); const edgelessCopilot = getEdgelessCopilotWidget(host); - let internal: Record = {}; const selectedElements = getCopilotSelectedElems(host); const { selectedBlocks } = getSelections(host); - const ctx = { - get() { - return { - ...internal, - selectedElements, - }; - }, - set(data: Record) { - internal = data; - }, - }; + const ctx = new AIContext({ selectedElements }); edgelessCopilot.hideCopilotPanel(); edgelessCopilot.lockToolbar(true); diff --git a/packages/frontend/core/src/blocksuite/presets/ai/actions/edgeless-response.ts b/packages/frontend/core/src/blocksuite/presets/ai/actions/edgeless-response.ts index cb6efd0f1e..fb7ff25c01 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/actions/edgeless-response.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/actions/edgeless-response.ts @@ -31,9 +31,10 @@ import { styleMap } from 'lit/directives/style-map.js'; import { insertFromMarkdown } from '../../_common'; import { AIPenIcon, ChatWithAIIcon } from '../_common/icons'; -import { getAIPanel } from '../ai-panel'; import { AIProvider } from '../provider'; import { reportResponse } from '../utils/action-reporter'; +import { getAIPanelWidget } from '../utils/ai-widgets'; +import type { AIContext } from '../utils/context'; import { getEdgelessCopilotWidget, getService, @@ -47,8 +48,8 @@ import { getEdgelessService, getSurfaceElementFromEditor, } from '../utils/selection-utils'; +import { createTemplateJob } from '../utils/template-job'; import { EXCLUDING_INSERT_ACTIONS, generatingStages } from './consts'; -import type { CtxRecord } from './types'; type FinishConfig = Exclude< AffineAIPanelWidget['config'], @@ -106,14 +107,17 @@ export function retry(panel: AffineAIPanelWidget): AIItemConfig { const extraConditions: Record boolean> = { createSlides: data => !!data.contents, }; -export function createInsertResp( +export function createInsertItems( id: T, - handler: (host: EditorHost, ctx: CtxRecord) => void, host: EditorHost, - ctx: CtxRecord, - buttonText: string = 'Insert below' + ctx: AIContext, + variants?: Omit< + Parameters[0], + keyof BlockSuitePresets.AITextActionOptions + > ): AIItemConfig[] { const extraCondition = extraConditions[id] || ((_: any) => true); + const buttonText = getButtonText[id]?.(variants) ?? 'Insert below'; return [ { name: `${buttonText} - Loading...`, @@ -121,7 +125,7 @@ export function createInsertResp( ${LightLoadingIcon} `, showWhen: () => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); const data = ctx.get(); return ( !EXCLUDING_INSERT_ACTIONS.includes(id) && @@ -135,7 +139,7 @@ export function createInsertResp( name: buttonText, icon: InsertBelowIcon, showWhen: () => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); const data = ctx.get(); return ( !EXCLUDING_INSERT_ACTIONS.includes(id) && @@ -146,14 +150,41 @@ export function createInsertResp( }, handler: () => { reportResponse('result:insert'); - handler(host, ctx); - const panel = getAIPanel(host); + edgelessResponseHandler(id, host, ctx).catch(console.error); + const panel = getAIPanelWidget(host); panel.hide(); }, }, ]; } +async function edgelessResponseHandler< + T extends keyof BlockSuitePresets.AIActions, +>(id: T, host: EditorHost, ctx: AIContext) { + switch (id) { + case 'expandMindmap': + responseToExpandMindmap(host, ctx); + break; + case 'brainstormMindmap': + responseToBrainstormMindmap(host, ctx); + break; + case 'makeItReal': + responseToMakeItReal(host, ctx); + break; + case 'createSlides': + await responseToCreateSlides(host, ctx); + break; + case 'createImage': + case 'filterImage': + case 'processImage': + responseToCreateImage(host); + break; + default: + defaultHandler(host); + break; + } +} + export function asCaption( id: T, host: EditorHost @@ -162,12 +193,12 @@ export function asCaption( name: 'Use as caption', icon: AIPenIcon, showWhen: () => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); return id === 'generateCaption' && !!panel.answer; }, handler: () => { reportResponse('result:use-as-caption'); - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); const caption = panel.answer; if (!caption) return; @@ -183,11 +214,6 @@ export function asCaption( }; } -type MindMapNode = { - text: string; - children: MindMapNode[]; -}; - function insertBelow( host: EditorHost, markdown: string, @@ -256,7 +282,7 @@ function createBlockAndInsert( * @param host EditorHost */ const defaultHandler = (host: EditorHost) => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); const selectedElements = getCopilotSelectedElems(host); assertExists(panel.answer); @@ -282,8 +308,8 @@ const defaultHandler = (host: EditorHost) => { * Should make the inserting image size same with the input image if there is an input image. * @param host */ -const imageHandler = (host: EditorHost) => { - const aiPanel = getAIPanel(host); +function responseToCreateImage(host: EditorHost) { + const aiPanel = getAIPanelWidget(host); // `DataURL` or `URL` const data = aiPanel.answer; if (!data) return; @@ -334,206 +360,181 @@ const imageHandler = (host: EditorHost) => { }); }) .catch(console.error); -}; +} -export const responses: { - [key in keyof Partial]: ( - host: EditorHost, - ctx: CtxRecord - ) => void; -} = { - expandMindmap: (host, ctx) => { - const [surface] = host.doc.getBlockByFlavour( - 'affine:surface' - ) as SurfaceBlockModel[]; +export function responseToExpandMindmap(host: EditorHost, ctx: AIContext) { + const [surface] = host.doc.getBlockByFlavour( + 'affine:surface' + ) as SurfaceBlockModel[]; - const elements = ctx.get()[ - 'selectedElements' - ] as BlockSuite.EdgelessModel[]; - const data = ctx.get() as { - node: MindMapNode; - }; + const elements = ctx.get().selectedElements; + const mindmapNode = ctx.get().node; - queueMicrotask(() => { - getAIPanel(host).hide(); + queueMicrotask(() => { + getAIPanelWidget(host).hide(); + }); + + if (!mindmapNode || !elements) return; + + const mindmap = elements[0].group as MindmapElementModel; + if (mindmapNode.children) { + mindmapNode.children.forEach(childTree => { + MindmapUtils.addTree(mindmap, elements[0].id, childTree); }); - const mindmap = elements[0].group as MindmapElementModel; + const subtree = mindmap.getNode(elements[0].id); - if (!data?.node) return; + if (!subtree) return; - if (data.node.children) { - data.node.children.forEach(childTree => { - MindmapUtils.addTree(mindmap, elements[0].id, childTree); - }); + surface.doc.transact(() => { + const updateNodeSize = (node: typeof subtree) => { + fitContent(node.element as ShapeElementModel); - const subtree = mindmap.getNode(elements[0].id); - - if (!subtree) return; - - surface.doc.transact(() => { - const updateNodeSize = (node: typeof subtree) => { - fitContent(node.element as ShapeElementModel); - - node.children.forEach(child => { - updateNodeSize(child); - }); - }; - - updateNodeSize(subtree); - }); - - setTimeout(() => { - const edgelessService = getEdgelessService(host); - - edgelessService.selection.set({ - elements: [subtree.element.id], - editing: false, + node.children.forEach(child => { + updateNodeSize(child); }); - }); - } - }, - brainstormMindmap: (host, ctx) => { - const aiPanel = getAIPanel(host); - const edgelessService = getEdgelessService(host); - const edgelessCopilot = getEdgelessCopilotWidget(host); - const selectionRect = edgelessCopilot.selectionModelRect; - const [surface] = host.doc.getBlockByFlavour( - 'affine:surface' - ) as SurfaceBlockModel[]; - const elements = ctx.get()[ - 'selectedElements' - ] as BlockSuite.EdgelessModel[]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const data = ctx.get() as any; - let newGenerated = true; + }; - // This means regenerate - if (isMindMapRoot(elements[0])) { - const mindmap = elements[0].group as MindmapElementModel; - const xywh = mindmap.tree.element.xywh; - - surface.deleteElement(mindmap.id); - - if (data.node) { - data.node.xywh = xywh; - newGenerated = false; - } - } - - edgelessCopilot.hideCopilotPanel(); - aiPanel.hide(); - - const mindmapId = surface.addElement({ - type: 'mindmap', - children: data.node, - style: data.style, - }); - const mindmap = surface.getElementById(mindmapId) as MindmapElementModel; - - host.doc.transact(() => { - mindmap.childElements.forEach(shape => { - fitContent(shape as ShapeElementModel); - }); + updateNodeSize(subtree); }); - const telemetryService = host.std.getOptional(TelemetryProvider); - telemetryService?.track('CanvasElementAdded', { - control: 'ai', - page: 'whiteboard editor', - module: 'toolbar', - segment: 'toolbar', - type: 'mindmap', - }); - - queueMicrotask(() => { - if (newGenerated && selectionRect) { - mindmap.moveTo([ - selectionRect.x, - selectionRect.y, - selectionRect.width, - selectionRect.height, - ]); - } - }); - - // This is a workaround to make sure mindmap and other microtask are done setTimeout(() => { - edgelessService.viewport.setViewportByBound( - mindmap.elementBound, - [20, 20, 20, 20], - true - ); + const edgelessService = getEdgelessService(host); edgelessService.selection.set({ - elements: [mindmap.tree.element.id], + elements: [subtree.element.id], editing: false, }); }); - }, - makeItReal: (host, ctx) => { - const aiPanel = getAIPanel(host); - let html = aiPanel.answer; - if (!html) return; - html = preprocessHtml(html); + } +} - const edgelessCopilot = getEdgelessCopilotWidget(host); - const [surface] = host.doc.getBlockByFlavour( - 'affine:surface' - ) as SurfaceBlockModel[]; +function responseToBrainstormMindmap(host: EditorHost, ctx: AIContext) { + const aiPanel = getAIPanelWidget(host); + const edgelessService = getEdgelessService(host); + const edgelessCopilot = getEdgelessCopilotWidget(host); + const selectionRect = edgelessCopilot.selectionModelRect; + const [surface] = host.doc.getBlockByFlavour( + 'affine:surface' + ) as SurfaceBlockModel[]; - const data = ctx.get(); - const bounds = edgelessCopilot.determineInsertionBounds( - (data['width'] as number) || 800, - (data['height'] as number) || 600 + const { node, style, selectedElements } = ctx.get(); + if (!node) return; + const elements = selectedElements; + // This means regenerate + if (elements && isMindMapRoot(elements[0])) { + const mindmap = elements[0].group as MindmapElementModel; + const xywh = mindmap.tree.element.xywh; + surface.deleteElement(mindmap.id); + node.xywh = xywh; + } else { + node.xywh = `[${selectionRect.x + selectionRect.width + 100},${selectionRect.y},0,0]`; + } + + edgelessCopilot.hideCopilotPanel(); + aiPanel.hide(); + + const mindmapId = surface.addElement({ + type: 'mindmap', + children: node, + style: style, + }); + const mindmap = surface.getElementById(mindmapId) as MindmapElementModel; + + host.doc.transact(() => { + mindmap.childElements.forEach(shape => { + fitContent(shape as ShapeElementModel); + }); + }); + + const telemetryService = host.std.getOptional(TelemetryProvider); + telemetryService?.track('CanvasElementAdded', { + control: 'ai', + page: 'whiteboard editor', + module: 'toolbar', + segment: 'toolbar', + type: 'mindmap', + }); + + // This is a workaround to make sure mindmap and other microtask are done + setTimeout(() => { + edgelessService.viewport.setViewportByBound( + mindmap.elementBound, + [20, 20, 20, 20], + true ); - edgelessCopilot.hideCopilotPanel(); - aiPanel.hide(); - - const edgelessRoot = getEdgelessRootFromEditor(host); - - host.doc.transact(() => { - edgelessRoot.doc.addBlock( - 'affine:embed-html', - { - html, - design: 'ai:makeItReal', // as tag - xywh: bounds.serialize(), - }, - surface.id - ); + edgelessService.selection.set({ + elements: [mindmap.tree.element.id], + editing: false, }); - }, - createSlides: (host, ctx) => { - const data = ctx.get(); - const contents = data.contents as unknown[]; - if (!contents) return; - const images = data.images as { url: string; id: string }[][]; - const service = host.std.getService('affine:page'); - if (!service) return; + }); +} - (async function () { - for (let i = 0; i < contents.length - 1; i++) { - const image = images[i]; - const content = contents[i]; - const job = service.createTemplateJob('template'); - await Promise.all( - image.map(({ id, url }) => - fetch(url) - .then(res => res.blob()) - .then(blob => job.job.assets.set(id, blob)) - ) - ); - await job.insertTemplate(content); - getSurfaceElementFromEditor(host).refresh(); - } - })().catch(console.error); - }, - createImage: imageHandler, - processImage: imageHandler, - filterImage: imageHandler, -}; +function responseToMakeItReal(host: EditorHost, ctx: AIContext) { + const aiPanel = getAIPanelWidget(host); + let html = aiPanel.answer; + if (!html) return; + html = preprocessHtml(html); + + const edgelessCopilot = getEdgelessCopilotWidget(host); + const [surface] = host.doc.getBlockByFlavour( + 'affine:surface' + ) as SurfaceBlockModel[]; + + const data = ctx.get(); + const bounds = edgelessCopilot.determineInsertionBounds( + data.width || 800, + data.height || 600 + ); + + edgelessCopilot.hideCopilotPanel(); + aiPanel.hide(); + + const edgelessRoot = getEdgelessRootFromEditor(host); + + host.doc.transact(() => { + edgelessRoot.doc.addBlock( + 'affine:embed-html', + { + html, + design: 'ai:makeItReal', // as tag + xywh: bounds.serialize(), + }, + surface.id + ); + }); +} + +async function responseToCreateSlides(host: EditorHost, ctx: AIContext) { + const data = ctx.get(); + const { contents = [], images = [] } = data; + if (contents.length === 0) return; + + const service = host.std.getService('affine:page'); + if (!service) return; + + try { + for (let i = 0; i < contents.length; i++) { + const image = images[i] || []; + const content = contents[i]; + const job = createTemplateJob(host); + + const imagePromises = image.map(async ({ id, url }) => { + const response = await fetch(url); + const blob = await response.blob(); + job.job.assets.set(id, blob); + }); + + await Promise.all(imagePromises); + await job.insertTemplate(content); + } + + getSurfaceElementFromEditor(host).refresh(); + } catch (error) { + console.error('Error creating slides:', error); + } +} const getButtonText: { [key in keyof Partial]: ( @@ -548,27 +549,10 @@ const getButtonText: { }, }; -export function getInsertAndReplaceHandler< - T extends keyof BlockSuitePresets.AIActions, ->( - id: T, - host: EditorHost, - ctx: CtxRecord, - variants?: Omit< - Parameters[0], - keyof BlockSuitePresets.AITextActionOptions - > -) { - const handler = responses[id] ?? defaultHandler; - const buttonText = getButtonText[id]?.(variants) ?? undefined; - - return createInsertResp(id, handler, host, ctx, buttonText); -} - export function actionToResponse( id: T, host: EditorHost, - ctx: CtxRecord, + ctx: AIContext, variants?: Omit< Parameters[0], keyof BlockSuitePresets.AITextActionOptions @@ -584,7 +568,7 @@ export function actionToResponse( icon: ChatWithAIIcon, handler: () => { reportResponse('result:continue-in-chat'); - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); AIProvider.slots.requestOpenWithChat.emit({ host, appendCard: true, @@ -592,10 +576,10 @@ export function actionToResponse( panel.hide(); }, }, - ...getInsertAndReplaceHandler(id, host, ctx, variants), + ...createInsertItems(id, host, ctx, variants), asCaption(id, host), - retry(getAIPanel(host)), - discard(getAIPanel(host), getEdgelessCopilotWidget(host)), + retry(getAIPanelWidget(host)), + discard(getAIPanelWidget(host), getEdgelessCopilotWidget(host)), ], }, ], @@ -619,7 +603,7 @@ export function actionToErrorResponse< panel: AffineAIPanelWidget, id: T, host: EditorHost, - ctx: CtxRecord, + ctx: AIContext, variants?: Omit< Parameters[0], keyof BlockSuitePresets.AITextActionOptions @@ -640,13 +624,13 @@ export function actionToErrorResponse< responses: [ { name: 'Response', - items: getInsertAndReplaceHandler(id, host, ctx, variants), + items: createInsertItems(id, host, ctx, variants), }, { name: '', items: [ - retry(getAIPanel(host)), - discard(getAIPanel(host), getEdgelessCopilotWidget(host)), + retry(getAIPanelWidget(host)), + discard(getAIPanelWidget(host), getEdgelessCopilotWidget(host)), ], }, ], diff --git a/packages/frontend/core/src/blocksuite/presets/ai/actions/page-response.ts b/packages/frontend/core/src/blocksuite/presets/ai/actions/page-response.ts new file mode 100644 index 0000000000..36bb750358 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/presets/ai/actions/page-response.ts @@ -0,0 +1,309 @@ +import type { EditorHost } from '@blocksuite/affine/block-std'; +import { + GfxBlockElementModel, + type GfxModel, + LayerManager, +} from '@blocksuite/affine/block-std/gfx'; +import type { + MindmapElementModel, + ShapeElementModel, +} from '@blocksuite/affine/blocks'; +import { + fitContent, + getSurfaceBlock, + SurfaceBlockModel, + TelemetryProvider, + uploadBlobForImage, +} from '@blocksuite/affine/blocks'; +import { Bound, getCommonBound } from '@blocksuite/affine/global/utils'; +import { type BlockProps, DocCollection, Text } from '@blocksuite/affine/store'; + +import { getAIPanelWidget } from '../utils/ai-widgets'; +import type { AffineNode, AIContext } from '../utils/context'; +import { insertAbove, insertBelow, replace } from '../utils/editor-actions'; +import { preprocessHtml } from '../utils/html'; +import { fetchImageToFile } from '../utils/image'; +import { getSelections } from '../utils/selection-utils'; +import { createTemplateJob } from '../utils/template-job'; + +const PADDING = 100; + +type Place = 'after' | 'before'; + +export async function pageResponseHandler< + T extends keyof BlockSuitePresets.AIActions, +>(id: T, host: EditorHost, ctx: AIContext, place: Place = 'after') { + switch (id) { + case 'brainstormMindmap': + responseToBrainstormMindmap(host, ctx, place); + break; + case 'makeItReal': + responseToMakeItReal(host, ctx, place); + break; + case 'createSlides': + await responseToCreateSlides(host, ctx, place); + break; + case 'createImage': + case 'filterImage': + case 'processImage': + responseToCreateImage(host, place); + break; + default: + await (place === 'after' + ? insertMarkdownBelow(host) + : insertMarkdownAbove(host)); + break; + } +} + +function responseToBrainstormMindmap( + host: EditorHost, + ctx: AIContext, + place: Place +) { + const surface = getSurfaceBlock(host.doc); + if (!surface) return; + + host.doc.transact(() => { + const { node, style } = ctx.get(); + if (!node) return; + const bound = getEdgelessContentBound(host); + if (bound) { + node.xywh = `[${bound.x + bound.w + PADDING * 2},${bound.y},0,0]`; + } + const mindmapId = surface.addElement({ + type: 'mindmap', + children: node, + style: style, + }); + const mindmap = surface.getElementById(mindmapId) as MindmapElementModel; + mindmap.childElements.forEach(shape => { + fitContent(shape as ShapeElementModel); + }); + // wait for mindmap xywh update + setTimeout(() => { + const frameBound = expandBound(mindmap.elementBound, PADDING); + addSurfaceRefBlock(host, frameBound, place); + }, 0); + }); + + const telemetryService = host.std.getOptional(TelemetryProvider); + telemetryService?.track('CanvasElementAdded', { + control: 'ai', + page: 'doc editor', + module: 'toolbar', + segment: 'toolbar', + type: 'mindmap', + }); +} + +function responseToMakeItReal(host: EditorHost, ctx: AIContext, place: Place) { + const surface = getSurfaceBlock(host.doc); + const aiPanel = getAIPanelWidget(host); + if (!aiPanel.answer || !surface) return; + + const { width, height } = ctx.get(); + const bound = getEdgelessContentBound(host); + const x = bound ? bound.x + bound.w + PADDING * 2 : 0; + const y = bound ? bound.y : 0; + const htmlBound = new Bound(x, y, width || 800, height || 600); + const html = preprocessHtml(aiPanel.answer); + host.doc.transact(() => { + host.doc.addBlock( + 'affine:embed-html', + { + html, + design: 'ai:makeItReal', // as tag + xywh: htmlBound.serialize(), + }, + surface.id + ); + const frameBound = expandBound(htmlBound, PADDING); + addSurfaceRefBlock(host, frameBound, place); + }); +} + +async function responseToCreateSlides( + host: EditorHost, + ctx: AIContext, + place: Place +) { + const { contents, images = [] } = ctx.get(); + const surface = getSurfaceBlock(host.doc); + if (!contents || !surface) return; + + try { + const frameIds: string[] = []; + for (let i = 0; i < contents.length; i++) { + const image = images[i]; + const content = contents[i]; + const job = createTemplateJob(host); + await Promise.all( + image.map(({ id, url }) => + fetch(url) + .then(res => res.blob()) + .then(blob => job.job.assets.set(id, blob)) + ) + ); + await job.insertTemplate(content); + const frame = findFrameObject(content.blocks); + frame && frameIds.push(frame.id); + } + const props = frameIds.map(id => ({ + flavour: 'affine:surface-ref', + refFlavour: 'affine:frame', + reference: id, + })); + addSiblingBlocks(host, props, place); + } catch (error) { + console.error('Error creating slides:', error); + } +} + +export function responseToCreateImage(host: EditorHost, place: Place) { + const aiPanel = getAIPanelWidget(host); + const { answer } = aiPanel; + if (!answer) return; + const filename = 'image'; + const imageProxy = host.std.clipboard.configs.get('imageProxy'); + + fetchImageToFile(answer, filename, imageProxy) + .then(file => { + if (!file) return; + host.doc.transact(() => { + const props = { + flavour: 'affine:image', + size: file.size, + }; + const blockId = addSiblingBlocks(host, [props], place)?.[0]; + blockId && uploadBlobForImage(host, blockId, file).catch(console.error); + }); + }) + .catch(console.error); +} + +export async function replaceWithMarkdown(host: EditorHost) { + const aiPanel = getAIPanelWidget(host); + const { answer } = aiPanel; + const selection = getSelection(host); + if (!answer || !selection) return; + + const { textSelection, firstBlock, selectedModels } = selection; + await replace(host, answer, firstBlock, selectedModels, textSelection); +} + +async function insertMarkdownBelow(host: EditorHost) { + const aiPanel = getAIPanelWidget(host); + const { answer } = aiPanel; + const selection = getSelection(host); + if (!answer || !selection) return; + + const { lastBlock } = selection; + await insertBelow(host, answer, lastBlock); +} + +async function insertMarkdownAbove(host: EditorHost) { + const aiPanel = getAIPanelWidget(host); + const { answer } = aiPanel; + const selection = getSelection(host); + if (!answer || !selection) return; + + const { firstBlock } = selection; + await insertAbove(host, answer, firstBlock); +} + +function getSelection(host: EditorHost) { + const textSelection = host.selection.find('text'); + const mode = textSelection ? 'flat' : 'highest'; + const { selectedBlocks } = getSelections(host, mode); + if (!selectedBlocks) return; + const length = selectedBlocks.length; + const firstBlock = selectedBlocks[0]; + const lastBlock = selectedBlocks[length - 1]; + const selectedModels = selectedBlocks.map(block => block.model); + return { + textSelection, + selectedModels, + firstBlock, + lastBlock, + }; +} + +function getEdgelessContentBound(host: EditorHost) { + const surface = getSurfaceBlock(host.doc); + if (!surface) return; + + const elements = ( + host.doc + .getBlocks() + .filter( + model => + model instanceof GfxBlockElementModel && + (model.parent instanceof SurfaceBlockModel || + model.parent?.role === 'root') + ) as GfxModel[] + ).concat(surface.elementModels ?? []); + const bounds = elements.map(element => Bound.deserialize(element.xywh)); + const bound = getCommonBound(bounds); + return bound; +} + +function expandBound(bound: Bound, margin: number) { + const x = bound.x - margin; + const y = bound.y - margin; + const w = bound.w + margin * 2; + const h = bound.h + margin * 2; + return new Bound(x, y, w, h); +} + +function addSurfaceRefBlock(host: EditorHost, bound: Bound, place: Place) { + const surface = getSurfaceBlock(host.doc); + if (!surface) return; + const frame = host.doc.addBlock( + 'affine:frame', + { + title: new Text(new DocCollection.Y.Text('Frame')), + xywh: bound.serialize(), + index: LayerManager.INITIAL_INDEX, + }, + surface + ); + const props = { + flavour: 'affine:surface-ref', + refFlavour: 'affine:frame', + reference: frame, + }; + return addSiblingBlocks(host, [props], place); +} + +function addSiblingBlocks( + host: EditorHost, + props: Array>, + place: Place +) { + const { selectedModels } = getSelection(host) || {}; + if (!selectedModels) return; + const targetModel = + place === 'before' + ? selectedModels[0] + : selectedModels[selectedModels.length - 1]; + + return host.doc.addSiblingBlocks(targetModel, props, place); +} + +function findFrameObject(obj: AffineNode): AffineNode | null { + if (obj.flavour === 'affine:frame') { + return obj; + } + + if (obj.children) { + for (const child of obj.children) { + const result = findFrameObject(child); + if (result) { + return result; + } + } + } + + return null; +} diff --git a/packages/frontend/core/src/blocksuite/presets/ai/actions/types.ts b/packages/frontend/core/src/blocksuite/presets/ai/actions/types.ts index 463f3a5442..5faa4d5fad 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/actions/types.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/actions/types.ts @@ -36,11 +36,6 @@ export const imageProcessingTypes = [ 'Convert to sticker', ] as const; -export type CtxRecord = { - get(): Record; - set(data: Record): void; -}; - declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace BlockSuitePresets { diff --git a/packages/frontend/core/src/blocksuite/presets/ai/ai-panel.ts b/packages/frontend/core/src/blocksuite/presets/ai/ai-panel.ts index fa9836d9d7..9e77c66c13 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/ai-panel.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/ai-panel.ts @@ -1,7 +1,6 @@ import type { EditorHost } from '@blocksuite/affine/block-std'; import { - AFFINE_AI_PANEL_WIDGET, - AffineAIPanelWidget, + type AffineAIPanelWidget, type AffineAIPanelWidgetConfig, type AIItemConfig, ImageBlockModel, @@ -24,35 +23,22 @@ import { ReplaceIcon, RetryIcon, } from './_common/icons'; -import { INSERT_ABOVE_ACTIONS } from './actions/consts'; +import { + EXCLUDING_REPLACE_ACTIONS, + INSERT_ABOVE_ACTIONS, +} from './actions/consts'; +import { + pageResponseHandler, + replaceWithMarkdown, +} from './actions/page-response'; import { AIProvider } from './provider'; import { reportResponse } from './utils/action-reporter'; +import { getAIPanelWidget } from './utils/ai-widgets'; +import { AIContext } from './utils/context'; import { findNoteBlockModel, getService } from './utils/edgeless'; -import { - copyTextAnswer, - insertAbove, - insertBelow, - replace, -} from './utils/editor-actions'; +import { copyTextAnswer } from './utils/editor-actions'; import { getSelections } from './utils/selection-utils'; -function getSelection(host: EditorHost) { - const textSelection = host.selection.find('text'); - const mode = textSelection ? 'flat' : 'highest'; - const { selectedBlocks } = getSelections(host, mode); - assertExists(selectedBlocks); - const length = selectedBlocks.length; - const firstBlock = selectedBlocks[0]; - const lastBlock = selectedBlocks[length - 1]; - const selectedModels = selectedBlocks.map(block => block.model); - return { - textSelection, - selectedModels, - firstBlock, - lastBlock, - }; -} - function asCaption( host: EditorHost, id?: T @@ -61,12 +47,12 @@ function asCaption( name: 'Use as caption', icon: AIPenIcon, showWhen: () => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); return id === 'generateCaption' && !!panel.answer; }, handler: () => { reportResponse('result:use-as-caption'); - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); const caption = panel.answer; if (!caption) return; @@ -87,7 +73,7 @@ function createNewNote(host: EditorHost): AIItemConfig { name: 'Create new note', icon: CreateIcon, showWhen: () => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); return !!panel.answer && isInsideEdgelessEditor(host); }, handler: () => { @@ -103,7 +89,7 @@ function createNewNote(host: EditorHost): AIItemConfig { const bound = Bound.deserialize(noteModel.xywh); const newBound = new Bound(bound.x - bound.w - 20, bound.y, bound.w, 72); const doc = host.doc; - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); const service = getService(host); doc.transact(() => { assertExists(doc.root); @@ -147,43 +133,11 @@ function createNewNote(host: EditorHost): AIItemConfig { }; } -async function replaceWithAnswer(panel: AffineAIPanelWidget) { - const { host } = panel; - const selection = getSelection(host); - if (!selection || !panel.answer) return; - - const { textSelection, firstBlock, selectedModels } = selection; - await replace(host, panel.answer, firstBlock, selectedModels, textSelection); - - panel.hide(); -} - -async function insertAnswerBelow(panel: AffineAIPanelWidget) { - const { host } = panel; - const selection = getSelection(host); - - if (!selection || !panel.answer) { - return; - } - - const { lastBlock } = selection; - await insertBelow(host, panel.answer, lastBlock); - panel.hide(); -} - -async function insertAnswerAbove(panel: AffineAIPanelWidget) { - const { host } = panel; - const selection = getSelection(host); - if (!selection || !panel.answer) return; - - const { firstBlock } = selection; - await insertAbove(host, panel.answer, firstBlock); - panel.hide(); -} - -export function buildTextResponseConfig< - T extends keyof BlockSuitePresets.AIActions, ->(panel: AffineAIPanelWidget, id?: T) { +function buildPageResponseConfig( + panel: AffineAIPanelWidget, + id: T, + ctx: AIContext +) { const host = panel.host; return [ @@ -197,7 +151,8 @@ export function buildTextResponseConfig< !!panel.answer && (!id || !INSERT_ABOVE_ACTIONS.includes(id)), handler: () => { reportResponse('result:insert'); - insertAnswerBelow(panel).catch(console.error); + pageResponseHandler(id, host, ctx, 'after').catch(console.error); + panel.hide(); }, }, { @@ -207,17 +162,20 @@ export function buildTextResponseConfig< !!panel.answer && !!id && INSERT_ABOVE_ACTIONS.includes(id), handler: () => { reportResponse('result:insert'); - insertAnswerAbove(panel).catch(console.error); + pageResponseHandler(id, host, ctx, 'before').catch(console.error); + panel.hide(); }, }, asCaption(host, id), { name: 'Replace selection', icon: ReplaceIcon, - showWhen: () => !!panel.answer, + showWhen: () => + !!panel.answer && !EXCLUDING_REPLACE_ACTIONS.includes(id), handler: () => { reportResponse('result:replace'); - replaceWithAnswer(panel).catch(console.error); + replaceWithMarkdown(host).catch(console.error); + panel.hide(); }, }, createNewNote(host), @@ -258,46 +216,8 @@ export function buildTextResponseConfig< ]; } -export function buildErrorResponseConfig< - T extends keyof BlockSuitePresets.AIActions, ->(panel: AffineAIPanelWidget, id?: T) { - const host = panel.host; - +export function buildErrorResponseConfig(panel: AffineAIPanelWidget) { return [ - { - name: 'Response', - items: [ - { - name: 'Replace selection', - icon: ReplaceIcon, - showWhen: () => !!panel.answer, - handler: () => { - replaceWithAnswer(panel).catch(console.error); - }, - }, - { - name: 'Insert below', - icon: InsertBelowIcon, - showWhen: () => - !!panel.answer && (!id || !INSERT_ABOVE_ACTIONS.includes(id)), - handler: () => { - insertAnswerBelow(panel).catch(console.error); - }, - }, - { - name: 'Insert above', - icon: InsertTopIcon, - showWhen: () => - !!panel.answer && !!id && INSERT_ABOVE_ACTIONS.includes(id), - handler: () => { - reportResponse('result:insert'); - insertAnswerAbove(panel).catch(console.error); - }, - }, - asCaption(host, id), - createNewNote(host), - ], - }, { name: '', items: [ @@ -325,18 +245,16 @@ export function buildErrorResponseConfig< export function buildFinishConfig( panel: AffineAIPanelWidget, - id?: T + id: T, + ctx: AIContext ) { return { - responses: buildTextResponseConfig(panel, id), + responses: buildPageResponseConfig(panel, id, ctx), actions: [], }; } -export function buildErrorConfig( - panel: AffineAIPanelWidget, - id?: T -) { +export function buildErrorConfig(panel: AffineAIPanelWidget) { return { upgrade: () => { AIProvider.slots.requestUpgradePlan.emit({ host: panel.host }); @@ -349,7 +267,7 @@ export function buildErrorConfig( cancel: () => { panel.hide(); }, - responses: buildErrorResponseConfig(panel, id), + responses: buildErrorResponseConfig(panel), }; } @@ -371,22 +289,12 @@ export function buildCopyConfig(panel: AffineAIPanelWidget) { export function buildAIPanelConfig( panel: AffineAIPanelWidget ): AffineAIPanelWidgetConfig { + const ctx = new AIContext(); return { answerRenderer: createTextRenderer(panel.host, { maxHeight: 320 }), - finishStateConfig: buildFinishConfig(panel), + finishStateConfig: buildFinishConfig(panel, 'chat', ctx), generatingStateConfig: buildGeneratingConfig(), errorStateConfig: buildErrorConfig(panel), copy: buildCopyConfig(panel), }; } - -export const getAIPanel = (host: EditorHost): AffineAIPanelWidget => { - const rootBlockId = host.doc.root?.id; - assertExists(rootBlockId); - const aiPanel = host.view.getWidget(AFFINE_AI_PANEL_WIDGET, rootBlockId); - assertExists(aiPanel); - if (!(aiPanel instanceof AffineAIPanelWidget)) { - throw new Error('AI panel not found'); - } - return aiPanel; -}; diff --git a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-cards.ts b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-cards.ts index a2bf9136b7..a547ed3971 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-cards.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-cards.ts @@ -57,6 +57,11 @@ const cardsStyles = css` font-size: 14px; font-weight: 400; color: var(--affine-text-secondary-color); + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + overflow: hidden; + text-overflow: ellipsis; } } `; diff --git a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts index 804a868aaf..0510671eb0 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts @@ -398,15 +398,13 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) { } scrollToEnd() { - this.updateComplete - .then(() => { - if (!this.messagesContainer) return; - this.messagesContainer.scrollTo({ - top: this.messagesContainer.scrollHeight, - behavior: 'smooth', - }); - }) - .catch(console.error); + requestAnimationFrame(() => { + if (!this.messagesContainer) return; + this.messagesContainer.scrollTo({ + top: this.messagesContainer.scrollHeight, + behavior: 'smooth', + }); + }); } retry = async () => { diff --git a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/index.ts b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/index.ts index 430698a603..4bef699d94 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/index.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/index.ts @@ -159,7 +159,7 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) { }; private readonly _scrollToEnd = () => { - requestAnimationFrame(() => this._chatMessages.value?.scrollToEnd()); + this._chatMessages.value?.scrollToEnd(); }; private readonly _cleanupHistories = async () => { @@ -200,7 +200,15 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) { _changedProperties.has('chatContextValue') && this.chatContextValue.status !== 'idle' ) { - setTimeout(this._scrollToEnd, 500); + if (this.chatContextValue.status === 'transmitting') { + this._scrollToEnd(); + } else if ( + this.chatContextValue.status === 'loading' || + this.chatContextValue.status === 'error' || + this.chatContextValue.status === 'success' + ) { + setTimeout(this._scrollToEnd, 500); + } } } diff --git a/packages/frontend/core/src/blocksuite/presets/ai/entries/edgeless/actions-config.ts b/packages/frontend/core/src/blocksuite/presets/ai/entries/edgeless/actions-config.ts index f8e8ca3dd0..da7d03b3eb 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/entries/edgeless/actions-config.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/entries/edgeless/actions-config.ts @@ -45,8 +45,8 @@ import { textTones, translateLangs, } from '../../actions/types'; -import { getAIPanel } from '../../ai-panel'; import { AIProvider } from '../../provider'; +import { getAIPanelWidget } from '../../utils/ai-widgets'; import { mindMapToMarkdown } from '../../utils/edgeless'; import { canvasToBlob, randomSeed } from '../../utils/image'; import { @@ -105,7 +105,7 @@ const othersGroup: AIItemGroupConfig = { icon: CommentIcon, showWhen: () => true, handler: host => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); AIProvider.slots.requestOpenWithChat.emit({ host, mode: 'edgeless', @@ -120,7 +120,7 @@ const othersGroup: AIItemGroupConfig = { icon: ChatWithAIIcon, showWhen: () => true, handler: host => { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); AIProvider.slots.requestOpenWithChat.emit({ host, mode: 'edgeless', @@ -286,7 +286,7 @@ const generateGroup: AIItemGroupConfig = { const selectedElements = getCopilotSelectedElems(host); const len = selectedElements.length; - const aiPanel = getAIPanel(host); + const aiPanel = getAIPanelWidget(host); // text to image // from user input if (len === 0) { @@ -297,7 +297,7 @@ const generateGroup: AIItemGroupConfig = { }; } - let content = (ctx.get()['content'] as string) || ''; + let content = ctx.get().content || ''; // from user input if (content.length === 0) { @@ -419,7 +419,7 @@ const generateGroup: AIItemGroupConfig = { // from user input if (selectedElements.length === 0) { - const aiPanel = getAIPanel(host); + const aiPanel = getAIPanelWidget(host); const content = aiPanel.inputText?.trim(); if (!content) return; return { @@ -438,7 +438,7 @@ const generateGroup: AIItemGroupConfig = { if (f + i + n + s + e === 0) { return; } - let content = (ctx.get()['content'] as string) || ''; + let content = ctx.get().content || ''; // single note, text if ( @@ -455,7 +455,7 @@ const generateGroup: AIItemGroupConfig = { // from user input if (content.length === 0) { - const aiPanel = getAIPanel(host); + const aiPanel = getAIPanelWidget(host); content = aiPanel.inputText?.trim() || ''; } @@ -521,7 +521,7 @@ const generateGroup: AIItemGroupConfig = { ], }; -export const edgelessActionGroups = [ +export const edgelessAIGroups: AIItemGroupConfig[] = [ reviewGroup, editGroup, generateGroup, diff --git a/packages/frontend/core/src/blocksuite/presets/ai/entries/edgeless/index.ts b/packages/frontend/core/src/blocksuite/presets/ai/entries/edgeless/index.ts index cbd7ca12ad..b42159e788 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/entries/edgeless/index.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/entries/edgeless/index.ts @@ -9,16 +9,16 @@ import { EdgelessCopilotToolbarEntry } from '@blocksuite/affine/blocks'; import { noop } from '@blocksuite/affine/global/utils'; import { html } from 'lit'; -import { getAIPanel } from '../../ai-panel'; import { AIProvider } from '../../provider'; +import { getAIPanelWidget } from '../../utils/ai-widgets'; import { getEdgelessCopilotWidget } from '../../utils/edgeless'; import { extractContext } from '../../utils/extract'; -import { edgelessActionGroups } from './actions-config'; +import { edgelessAIGroups } from './actions-config'; noop(EdgelessCopilotToolbarEntry); export function setupEdgelessCopilot(widget: EdgelessCopilotWidget) { - widget.groups = edgelessActionGroups; + widget.groups = edgelessAIGroups; } export function setupEdgelessElementToolbarAIEntry( @@ -30,7 +30,7 @@ export function setupEdgelessElementToolbarAIEntry( }, render: (edgeless: EdgelessRootBlockComponent) => { const chain = edgeless.service.std.command.chain(); - const filteredGroups = edgelessActionGroups.reduce((pre, group) => { + const filteredGroups = edgelessAIGroups.reduce((pre, group) => { const filtered = group.items.filter(item => item.showWhen?.(chain, 'edgeless' as DocMode, edgeless.host) ); @@ -43,7 +43,7 @@ export function setupEdgelessElementToolbarAIEntry( if (filteredGroups.every(group => group.items.length === 0)) return null; const handler = () => { - const aiPanel = getAIPanel(edgeless.host); + const aiPanel = getAIPanelWidget(edgeless.host); if (aiPanel.config) { aiPanel.config.generateAnswer = ({ finish, input }) => { finish('success'); @@ -70,7 +70,7 @@ export function setupEdgelessElementToolbarAIEntry( return html``; }, diff --git a/packages/frontend/core/src/blocksuite/presets/ai/entries/format-bar/setup-format-bar.ts b/packages/frontend/core/src/blocksuite/presets/ai/entries/format-bar/setup-format-bar.ts index 05c4695c91..e606710564 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/entries/format-bar/setup-format-bar.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/entries/format-bar/setup-format-bar.ts @@ -6,7 +6,7 @@ import { } from '@blocksuite/affine/blocks'; import { html, type TemplateResult } from 'lit'; -import { AIItemGroups } from '../../_common/config'; +import { pageAIGroups } from '../../_common/config'; export function setupFormatBarAIEntry(formatBar: AffineFormatBarWidget) { toolbarDefaultConfig(formatBar); @@ -18,7 +18,7 @@ export function setupFormatBarAIEntry(formatBar: AffineFormatBarWidget) { return html` `; }, diff --git a/packages/frontend/core/src/blocksuite/presets/ai/entries/slash-menu/setup-slash-menu.ts b/packages/frontend/core/src/blocksuite/presets/ai/entries/slash-menu/setup-slash-menu.ts index f309b8dc01..33ed79950b 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/entries/slash-menu/setup-slash-menu.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/entries/slash-menu/setup-slash-menu.ts @@ -14,12 +14,12 @@ import { import { assertExists } from '@blocksuite/affine/global/utils'; import { html } from 'lit'; -import { AIItemGroups } from '../../_common/config'; +import { pageAIGroups } from '../../_common/config'; import { handleInlineAskAIAction } from '../../actions/doc-handler'; import { AIProvider } from '../../provider'; export function setupSlashMenuAIEntry(slashMenu: AffineSlashMenuWidget) { - const AIItems = AIItemGroups.map(group => group.items).flat(); + const AIItems = pageAIGroups.map(group => group.items).flat(); const iconWrapper = (icon: AIItemConfig['icon']) => { return html`
diff --git a/packages/frontend/core/src/blocksuite/presets/ai/messages/mindmap.ts b/packages/frontend/core/src/blocksuite/presets/ai/messages/mindmap.ts index 73cc59fc72..526d016507 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/messages/mindmap.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/messages/mindmap.ts @@ -10,7 +10,8 @@ import { import { noop } from '@blocksuite/affine/global/utils'; import { html, nothing } from 'lit'; -import { getAIPanel } from '../ai-panel'; +import { getAIPanelWidget } from '../utils/ai-widgets'; +import type { AIContext } from '../utils/context'; noop(MiniMindmapPreview); @@ -19,15 +20,12 @@ export const createMindmapRenderer: ( /** * Used to store data for later use during rendering. */ - ctx: { - get: () => Record; - set: (data: Record) => void; - }, + ctx: AIContext, style?: MindmapStyle ) => AffineAIPanelWidgetConfig['answerRenderer'] = (host, ctx, style) => { return (answer, state) => { if (state === 'generating') { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); panel.generatingElement?.updateLoadingProgress(2); } @@ -55,18 +53,12 @@ export const createMindmapExecuteRenderer: ( /** * Used to store data for later use during rendering. */ - ctx: { - get: () => Record; - set: (data: Record) => void; - }, - handler: (ctx: { - get: () => Record; - set: (data: Record) => void; - }) => void + ctx: AIContext, + handler: (host: EditorHost, ctx: AIContext) => void ) => AffineAIPanelWidgetConfig['answerRenderer'] = (host, ctx, handler) => { return (answer, state) => { if (state !== 'finished') { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); panel.generatingElement?.updateLoadingProgress(2); return nothing; } @@ -75,7 +67,7 @@ export const createMindmapExecuteRenderer: ( node: markdownToMindmap(answer, host.doc), }); - handler(ctx); + handler(host, ctx); return nothing; }; diff --git a/packages/frontend/core/src/blocksuite/presets/ai/messages/slides-renderer.ts b/packages/frontend/core/src/blocksuite/presets/ai/messages/slides-renderer.ts index 24b980318f..a6d680e1ac 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/messages/slides-renderer.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/messages/slides-renderer.ts @@ -11,19 +11,17 @@ import { css, html, LitElement, nothing } from 'lit'; import { property, query } from 'lit/decorators.js'; import { createRef, type Ref, ref } from 'lit/directives/ref.js'; -import { getAIPanel } from '../ai-panel'; import { PPTBuilder } from '../slides/index'; +import { getAIPanelWidget } from '../utils/ai-widgets'; +import type { AIContext } from '../utils/context'; export const createSlidesRenderer: ( host: EditorHost, - ctx: { - get: () => Record; - set: (data: Record) => void; - } + ctx: AIContext ) => AffineAIPanelWidgetConfig['answerRenderer'] = (host, ctx) => { return (answer, state) => { if (state === 'generating') { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); panel.generatingElement?.updateLoadingProgress(2); return nothing; } @@ -77,7 +75,7 @@ export class AISlidesRenderer extends WithDisposable(LitElement) { requestAnimationFrame(() => { if (!this._editorHost) return; PPTBuilder(this._editorHost) - .process(this.text) + ?.process(this.text) .then(res => { if (res && this.ctx) { this.ctx.set({ @@ -85,7 +83,7 @@ export class AISlidesRenderer extends WithDisposable(LitElement) { images: res.images, }); // refresh loading menu item - getAIPanel(this.host) + getAIPanelWidget(this.host) .shadowRoot?.querySelector('ai-panel-answer') ?.requestUpdate(); } diff --git a/packages/frontend/core/src/blocksuite/presets/ai/messages/wrapper.ts b/packages/frontend/core/src/blocksuite/presets/ai/messages/wrapper.ts index fb955c3138..16115d4d4f 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/messages/wrapper.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/messages/wrapper.ts @@ -3,7 +3,7 @@ import type { AffineAIPanelWidgetConfig } from '@blocksuite/affine/blocks'; import { css, html, LitElement, nothing } from 'lit'; import { property } from 'lit/decorators.js'; -import { getAIPanel } from '../ai-panel'; +import { getAIPanelWidget } from '../utils/ai-widgets'; import { preprocessHtml } from '../utils/html'; type AIAnswerWrapperOptions = { @@ -62,7 +62,7 @@ export const createIframeRenderer: ( ) => AffineAIPanelWidgetConfig['answerRenderer'] = (host, options) => { return (answer, state) => { if (state === 'generating') { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); panel.generatingElement?.updateLoadingProgress(2); return nothing; } @@ -91,7 +91,7 @@ export const createImageRenderer: ( ) => AffineAIPanelWidgetConfig['answerRenderer'] = (host, options) => { return (answer, state) => { if (state === 'generating') { - const panel = getAIPanel(host); + const panel = getAIPanelWidget(host); panel.generatingElement?.updateLoadingProgress(2); return nothing; } diff --git a/packages/frontend/core/src/blocksuite/presets/ai/provider.ts b/packages/frontend/core/src/blocksuite/presets/ai/provider.ts index 132f36b6b4..5d817fdf57 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/provider.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/provider.ts @@ -119,8 +119,6 @@ export class AIProvider { }>(), requestLogin: new Slot<{ host: EditorHost }>(), requestUpgradePlan: new Slot<{ host: EditorHost }>(), - // when an action is requested to run in edgeless mode (show a toast in affine) - requestRunInEdgeless: new Slot<{ host: EditorHost }>(), // stream of AI actions triggered by users actions: new Slot<{ action: keyof BlockSuitePresets.AIActions; @@ -311,7 +309,6 @@ export class AIProvider { options: BlockSuitePresets.AIForkChatSessionOptions ) => string | Promise; } else { - // eslint-disable-next-line @typescript-eslint/no-explicit-any AIProvider.instance.provideAction(id as any, action as any); } } diff --git a/packages/frontend/core/src/blocksuite/presets/ai/slides/index.ts b/packages/frontend/core/src/blocksuite/presets/ai/slides/index.ts index af25eb1d3f..759f657bc6 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/slides/index.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/slides/index.ts @@ -4,6 +4,7 @@ import type { BlockSnapshot } from '@blocksuite/affine/store'; import { markdownToSnapshot } from '../../_common'; import { getSurfaceElementFromEditor } from '../utils/selection-utils'; +import { createTemplateJob } from '../utils/template-job'; import { basicTheme, type PPTDoc, @@ -16,6 +17,7 @@ export const PPTBuilder = (host: EditorHost) => { const docs: PPTDoc[] = []; const contents: unknown[] = []; const allImages: TemplateImage[][] = []; + if (!service) return; const addDoc = async (block: BlockSnapshot) => { const sections = block.children.map(v => { @@ -35,8 +37,7 @@ export const PPTBuilder = (host: EditorHost) => { }; docs.push(doc); - if (doc.isCover || !service) return; - const job = service.createTemplateJob('template'); + const job = createTemplateJob(host); const { images, content } = await basicTheme(doc); contents.push(content); allImages.push(images); @@ -56,7 +57,6 @@ export const PPTBuilder = (host: EditorHost) => { return { process: async (text: string) => { try { - if (!service) return; const snapshot = await markdownToSnapshot(text, host); const block = snapshot.snapshot.content[0]; diff --git a/packages/frontend/core/src/blocksuite/presets/ai/slides/template.ts b/packages/frontend/core/src/blocksuite/presets/ai/slides/template.ts index 9f369191df..a727675c95 100644 --- a/packages/frontend/core/src/blocksuite/presets/ai/slides/template.ts +++ b/packages/frontend/core/src/blocksuite/presets/ai/slides/template.ts @@ -3,7 +3,6 @@ import { nanoid } from '@blocksuite/affine/store'; import { AIProvider } from '../provider'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any const replaceText = (text: Record, template: any) => { if (template != null && typeof template === 'object') { if (Array.isArray(template)) { @@ -55,7 +54,7 @@ const getImageUrlByKeyword = const getImages = async ( images: Record Promise | string>, - // eslint-disable-next-line @typescript-eslint/no-explicit-any + template: any ): Promise => { const imgs: Record< @@ -66,7 +65,7 @@ const getImages = async ( height: number; } > = {}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const run = (data: any) => { if (data != null && typeof data === 'object') { if (Array.isArray(data)) { @@ -93,19 +92,21 @@ const getImages = async ( Object.entries(imgs).map(async ([name, data]) => { const getImage = images[name]; if (!getImage) { - return; + return null; + } + try { + const url = await getImage(data.width, data.height); + return { + id: data.id, + url, + } as TemplateImage; + } catch (error) { + console.error('Error getting image:', error); + return null; } - const url = await getImage(data.width, data.height); - return { - id: data.id, - url, - } satisfies TemplateImage; }) ); - const notNull = (v?: TemplateImage): v is TemplateImage => { - return v != null; - }; - return list.filter(notNull); + return list.filter(v => !!v); }; export type PPTSection = { diff --git a/packages/frontend/core/src/blocksuite/presets/ai/utils/ai-widgets.ts b/packages/frontend/core/src/blocksuite/presets/ai/utils/ai-widgets.ts new file mode 100644 index 0000000000..a69d31cd63 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/presets/ai/utils/ai-widgets.ts @@ -0,0 +1,17 @@ +import type { EditorHost } from '@blocksuite/affine/block-std'; +import { + AFFINE_AI_PANEL_WIDGET, + AffineAIPanelWidget, +} from '@blocksuite/affine/blocks'; +import { assertExists } from '@blocksuite/affine/global/utils'; + +export const getAIPanelWidget = (host: EditorHost): AffineAIPanelWidget => { + const rootBlockId = host.doc.root?.id; + assertExists(rootBlockId); + const aiPanel = host.view.getWidget(AFFINE_AI_PANEL_WIDGET, rootBlockId); + assertExists(aiPanel); + if (!(aiPanel instanceof AffineAIPanelWidget)) { + throw new Error('AI panel not found'); + } + return aiPanel; +}; diff --git a/packages/frontend/core/src/blocksuite/presets/ai/utils/context.ts b/packages/frontend/core/src/blocksuite/presets/ai/utils/context.ts new file mode 100644 index 0000000000..3a5559dd32 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/presets/ai/utils/context.ts @@ -0,0 +1,50 @@ +import type { MindmapStyle } from '@blocksuite/affine/blocks'; +import type { SerializedXYWH } from '@blocksuite/affine/global/utils'; + +import type { TemplateImage } from '../slides/template'; + +export interface ContextValue { + selectedElements?: BlockSuite.EdgelessModel[]; + content?: string; + // make it real + width?: number; + height?: number; + // mindmap + node?: MindMapNode | null; + style?: MindmapStyle; + centerPosition?: SerializedXYWH; + // slides + contents?: Array<{ blocks: AffineNode }>; + images?: TemplateImage[][]; +} + +export interface AffineNode { + id: string; + flavour: string; + children: AffineNode[]; +} + +type MindMapNode = { + xywh?: SerializedXYWH; + text: string; + children: MindMapNode[]; +}; + +export class AIContext { + private _value: ContextValue; + + constructor(initData: ContextValue = {}) { + this._value = initData; + } + + get = () => { + return this._value; + }; + + set = (data: ContextValue) => { + this._value = { + ...this._value, + ...data, + }; + }; +} diff --git a/packages/frontend/core/src/blocksuite/presets/ai/utils/template-job.ts b/packages/frontend/core/src/blocksuite/presets/ai/utils/template-job.ts new file mode 100644 index 0000000000..b8e0ed1715 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/presets/ai/utils/template-job.ts @@ -0,0 +1,45 @@ +import type { EditorHost } from '@blocksuite/affine/block-std'; +import { LayerManager } from '@blocksuite/affine/block-std/gfx'; +import { + getSurfaceBlock, + TemplateJob, + TemplateMiddlewares, +} from '@blocksuite/affine/blocks'; +import { + assertExists, + Bound, + getCommonBound, +} from '@blocksuite/affine/global/utils'; + +export function createTemplateJob(host: EditorHost) { + const surface = getSurfaceBlock(host.doc); + assertExists(surface); + + const middlewares: ((job: TemplateJob) => void)[] = []; + const layer = new LayerManager(host.doc, surface, { + watch: false, + }); + const bounds = [...layer.blocks, ...layer.canvasElements].map(i => + Bound.deserialize(i.xywh) + ); + const currentContentBound = getCommonBound(bounds); + + if (currentContentBound) { + currentContentBound.x += currentContentBound.w + 100; + middlewares.push( + TemplateMiddlewares.createInsertPlaceMiddleware(currentContentBound) + ); + } + + const idxGenerator = layer.createIndexGenerator(); + middlewares.push( + TemplateMiddlewares.createRegenerateIndexMiddleware(() => idxGenerator()) + ); + middlewares.push(TemplateMiddlewares.replaceIdMiddleware); + + return TemplateJob.create({ + model: surface, + type: 'template', + middlewares, + }); +} diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx index aac3d7b9d4..3b2dd82e63 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx @@ -1,4 +1,4 @@ -import { notify, Scrollable } from '@affine/component'; +import { Scrollable } from '@affine/component'; import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton'; import type { ChatPanel } from '@affine/core/blocksuite/presets/ai'; import { AIProvider } from '@affine/core/blocksuite/presets/ai'; @@ -10,7 +10,6 @@ import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite- import { EditorService } from '@affine/core/modules/editor'; import { RecentDocsService } from '@affine/core/modules/quicksearch'; import { ViewService } from '@affine/core/modules/workbench/services/view'; -import { useI18n } from '@affine/i18n'; import { RefNodeSlotsProvider } from '@blocksuite/affine/blocks'; import { DisposableGroup } from '@blocksuite/affine/global/utils'; import { type AffineEditorContainer } from '@blocksuite/affine/presets'; @@ -102,7 +101,6 @@ const DetailPageImpl = memo(function DetailPageImpl() { // TODO(@eyhn): remove jotai here const [_, setActiveBlockSuiteEditor] = useActiveBlocksuiteEditor(); - const t = useI18n(); const enableAI = featureFlagService.flags.enable_ai.value; useEffect(() => { @@ -203,22 +201,6 @@ const DetailPageImpl = memo(function DetailPageImpl() { } } - disposable.add( - AIProvider.slots.requestRunInEdgeless.on(({ host }) => { - if (host === editorHost) { - notify.warning({ - title: t['com.affine.ai.action.edgeless-only.dialog-title'](), - action: { - label: t['Switch'](), - onClick: () => { - editor.setMode('edgeless'); - }, - }, - }); - } - }) - ); - const unbind = editor.bindEditorContainer( editorContainer, (editorContainer as any).docTitle, // set from proxy @@ -230,7 +212,7 @@ const DetailPageImpl = memo(function DetailPageImpl() { disposable.dispose(); }; }, - [editor, openPage, docCollection.id, jumpToPageBlock, t] + [editor, openPage, docCollection.id, jumpToPageBlock] ); const [hasScrollTop, setHasScrollTop] = useState(false); diff --git a/tests/affine-cloud-copilot/e2e/copilot.spec.ts b/tests/affine-cloud-copilot/e2e/copilot.spec.ts index e1497ceb55..5b0f6c48a9 100644 --- a/tests/affine-cloud-copilot/e2e/copilot.spec.ts +++ b/tests/affine-cloud-copilot/e2e/copilot.spec.ts @@ -368,6 +368,18 @@ test.describe('chat with block', () => { return answer.innerText(); }; + const collectCanvasAnswer = async (page: Page, tagName: string) => { + // wait ai response + await page.waitForSelector( + 'affine-ai-panel-widget .response-list-container', + { timeout: 5 * ONE_MINUTE } + ); + const answer = await page.waitForSelector( + `affine-ai-panel-widget ai-panel-answer ${tagName}` + ); + return answer; + }; + const collectImageAnswer = async (page: Page, timeout = TEN_SECONDS) => { // wait ai response await page.waitForSelector( @@ -431,6 +443,10 @@ test.describe('chat with block', () => { 'Generate headings', 'Generate outline', 'Find actions', + 'Generate an image', + 'Brainstorm ideas with mind map', + 'Generate presentation', + 'Make it real', // draft with ai 'Write an article about this', 'Write a tweet about this', @@ -446,7 +462,31 @@ test.describe('chat with block', () => { `.ai-item-${option.replaceAll(' ', '-').toLowerCase()}` ) .then(i => i.click()); - expect(await collectTextAnswer(page)).toBeTruthy(); + + if (option === 'Generate an image') { + const mindmap = await collectCanvasAnswer(page, '.ai-answer-image'); + expect(mindmap).toBeTruthy(); + } else if (option === 'Brainstorm ideas with mind map') { + const mindmap = await collectCanvasAnswer( + page, + 'mini-mindmap-preview' + ); + expect(mindmap).toBeTruthy(); + } else if (option === 'Generate presentation') { + const presentation = await collectCanvasAnswer( + page, + 'ai-slides-renderer' + ); + expect(presentation).toBeTruthy(); + } else if (option === 'Make it real') { + const makeItReal = await collectCanvasAnswer( + page, + '.ai-answer-iframe' + ); + expect(makeItReal).toBeTruthy(); + } else { + expect(await collectTextAnswer(page)).toBeTruthy(); + } }); } }); diff --git a/tools/cli/package.json b/tools/cli/package.json index 4fc85af4ab..394f194236 100644 --- a/tools/cli/package.json +++ b/tools/cli/package.json @@ -6,7 +6,7 @@ "@affine/env": "workspace:*", "@affine/templates": "workspace:*", "@aws-sdk/client-s3": "^3.709.0", - "@blocksuite/affine": "0.19.0", + "@blocksuite/affine": "0.19.1", "@clack/core": "^0.3.5", "@clack/prompts": "^0.8.2", "@magic-works/i18n-codegen": "^0.6.1", diff --git a/yarn.lock b/yarn.lock index 3862e04288..36d1f95da2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -215,7 +215,7 @@ __metadata: "@affine/component": "workspace:*" "@affine/core": "workspace:*" "@affine/i18n": "workspace:*" - "@blocksuite/affine": "npm:0.19.0" + "@blocksuite/affine": "npm:0.19.1" "@blocksuite/icons": "npm:2.1.75" "@capacitor/android": "npm:^6.2.0" "@capacitor/cli": "npm:^6.2.0" @@ -262,7 +262,7 @@ __metadata: "@affine/env": "workspace:*" "@affine/templates": "workspace:*" "@aws-sdk/client-s3": "npm:^3.709.0" - "@blocksuite/affine": "npm:0.19.0" + "@blocksuite/affine": "npm:0.19.1" "@clack/core": "npm:^0.3.5" "@clack/prompts": "npm:^0.8.2" "@magic-works/i18n-codegen": "npm:^0.6.1" @@ -319,7 +319,7 @@ __metadata: "@affine/i18n": "workspace:*" "@atlaskit/pragmatic-drag-and-drop": "patch:@atlaskit/pragmatic-drag-and-drop@npm%3A1.4.0#~/.yarn/patches/@atlaskit-pragmatic-drag-and-drop-npm-1.4.0-75c45f52d3.patch" "@atlaskit/pragmatic-drag-and-drop-hitbox": "npm:^1.0.3" - "@blocksuite/affine": "npm:0.19.0" + "@blocksuite/affine": "npm:0.19.1" "@blocksuite/icons": "npm:2.1.75" "@chromatic-com/storybook": "npm:^3.2.2" "@emotion/react": "npm:^11.14.0" @@ -406,7 +406,7 @@ __metadata: "@affine/i18n": "workspace:*" "@affine/templates": "workspace:*" "@affine/track": "workspace:*" - "@blocksuite/affine": "npm:0.19.0" + "@blocksuite/affine": "npm:0.19.1" "@blocksuite/icons": "npm:2.1.75" "@capacitor/app": "npm:^6.0.2" "@capacitor/browser": "npm:^6.0.4" @@ -519,7 +519,7 @@ __metadata: "@affine/i18n": "workspace:*" "@affine/native": "workspace:*" "@affine/nbstore": "workspace:*" - "@blocksuite/affine": "npm:0.19.0" + "@blocksuite/affine": "npm:0.19.1" "@electron-forge/cli": "npm:^7.6.0" "@electron-forge/core": "npm:^7.6.0" "@electron-forge/core-utils": "npm:^7.6.0" @@ -579,7 +579,7 @@ __metadata: version: 0.0.0-use.local resolution: "@affine/env@workspace:packages/common/env" dependencies: - "@blocksuite/affine": "npm:0.19.0" + "@blocksuite/affine": "npm:0.19.1" vitest: "npm:2.1.8" zod: "npm:^3.24.1" peerDependencies: @@ -628,7 +628,7 @@ __metadata: "@affine/component": "workspace:*" "@affine/core": "workspace:*" "@affine/i18n": "workspace:*" - "@blocksuite/affine": "npm:0.19.0" + "@blocksuite/affine": "npm:0.19.1" "@blocksuite/icons": "npm:2.1.75" "@capacitor/app": "npm:^6.0.2" "@capacitor/browser": "npm:^6.0.4" @@ -655,7 +655,7 @@ __metadata: "@affine/component": "workspace:*" "@affine/core": "workspace:*" "@affine/i18n": "workspace:*" - "@blocksuite/affine": "npm:0.19.0" + "@blocksuite/affine": "npm:0.19.1" "@blocksuite/icons": "npm:2.1.75" "@sentry/react": "npm:^8.44.0" "@types/react": "npm:^19.0.1" @@ -3263,19 +3263,19 @@ __metadata: languageName: node linkType: hard -"@blocksuite/affine-block-embed@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/affine-block-embed@npm:0.19.0" +"@blocksuite/affine-block-embed@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/affine-block-embed@npm:0.19.1" dependencies: - "@blocksuite/affine-block-surface": "npm:0.19.0" - "@blocksuite/affine-components": "npm:0.19.0" - "@blocksuite/affine-model": "npm:0.19.0" - "@blocksuite/affine-shared": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" + "@blocksuite/affine-block-surface": "npm:0.19.1" + "@blocksuite/affine-components": "npm:0.19.1" + "@blocksuite/affine-model": "npm:0.19.1" + "@blocksuite/affine-shared": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" "@blocksuite/icons": "npm:^2.1.75" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" "@floating-ui/dom": "npm:^1.6.10" "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" @@ -3283,21 +3283,21 @@ __metadata: lit: "npm:^3.2.0" minimatch: "npm:^10.0.1" zod: "npm:^3.23.8" - checksum: 10/f60180d09267bae0a6dff3ef790e2fc82f3442bd6aa3832e565b48288d36ea770f4c25263c5a032ebdfc2a6f70f20d4548ea790a78bf678a5c75c004bf530c5e + checksum: 10/730ca715738f1b29b2a2561610e9d86a2cc2498e94ab9ae0a00e1acf8c8457158bb27e7d9122cc8788c2d8e61c912d39896775e90868f0640fa25bc2c2d6f2dc languageName: node linkType: hard -"@blocksuite/affine-block-list@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/affine-block-list@npm:0.19.0" +"@blocksuite/affine-block-list@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/affine-block-list@npm:0.19.1" dependencies: - "@blocksuite/affine-components": "npm:0.19.0" - "@blocksuite/affine-model": "npm:0.19.0" - "@blocksuite/affine-shared": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/affine-components": "npm:0.19.1" + "@blocksuite/affine-model": "npm:0.19.1" + "@blocksuite/affine-shared": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" "@floating-ui/dom": "npm:^1.6.10" "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" @@ -3306,21 +3306,21 @@ __metadata: lit: "npm:^3.2.0" minimatch: "npm:^10.0.1" zod: "npm:^3.23.8" - checksum: 10/fb57e7da4e03d7a3b9b04bc91bfa3a0b38ce71a8d915309dd820070d30d1ae33bb05c84a3d455d858f772566b0cb568c376b5fb478d8ec09baf5b0f0a66ce9cc + checksum: 10/752c00e86403557c2ec80e39f5c60af67543c52f22578872c650a343d9525dac7d63e35c0a56bc3d405fdbed50f03143936ec2076b716e5b2b90be18a0a4341b languageName: node linkType: hard -"@blocksuite/affine-block-paragraph@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/affine-block-paragraph@npm:0.19.0" +"@blocksuite/affine-block-paragraph@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/affine-block-paragraph@npm:0.19.1" dependencies: - "@blocksuite/affine-components": "npm:0.19.0" - "@blocksuite/affine-model": "npm:0.19.0" - "@blocksuite/affine-shared": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/affine-components": "npm:0.19.1" + "@blocksuite/affine-model": "npm:0.19.1" + "@blocksuite/affine-shared": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" "@floating-ui/dom": "npm:^1.6.10" "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" @@ -3329,21 +3329,21 @@ __metadata: lit: "npm:^3.2.0" minimatch: "npm:^10.0.1" zod: "npm:^3.23.8" - checksum: 10/d7e2bd3e6df92da184b4cfa4db985f2fbc9f82d0a42ca50c36c2d2628799576bba2d08f7f784ec9d1d4a0bea144ab90bd3d2114c76c097d673651fc90d7bda5b + checksum: 10/971760676b9463dc2d93ee6475dfd3b8a634ebd14e27ac7b999e42afc6b069b6743dcfcb803d9b4e6c20b1d64cabae35635b71f710fa7a416544b322fbbf77f7 languageName: node linkType: hard -"@blocksuite/affine-block-surface@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/affine-block-surface@npm:0.19.0" +"@blocksuite/affine-block-surface@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/affine-block-surface@npm:0.19.1" dependencies: - "@blocksuite/affine-components": "npm:0.19.0" - "@blocksuite/affine-model": "npm:0.19.0" - "@blocksuite/affine-shared": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/affine-components": "npm:0.19.1" + "@blocksuite/affine-model": "npm:0.19.1" + "@blocksuite/affine-shared": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" "@toeverything/theme": "npm:^1.1.1" @@ -3352,21 +3352,21 @@ __metadata: lodash.chunk: "npm:^4.2.0" nanoid: "npm:^5.0.7" zod: "npm:^3.23.8" - checksum: 10/94ba8a4c9253518a3b75e32929493c4bba074ccc31acf7471bbb49216a82db9ceecd61a81e61dc445d4775b312a51d68859054b7a04282c11aaaaa5e02d58ccd + checksum: 10/6ec358f345e3f362bf4aa5799b4df18338afd2e101f8cf12189c717d9b7a531de65026be558e21b80717c41dded87c872f572abbbeabfb6594bfa5e17791e34f languageName: node linkType: hard -"@blocksuite/affine-components@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/affine-components@npm:0.19.0" +"@blocksuite/affine-components@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/affine-components@npm:0.19.1" dependencies: - "@blocksuite/affine-model": "npm:0.19.0" - "@blocksuite/affine-shared": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" + "@blocksuite/affine-model": "npm:0.19.1" + "@blocksuite/affine-shared": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" "@blocksuite/icons": "npm:^2.1.75" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" "@floating-ui/dom": "npm:^1.6.10" "@lit/context": "npm:^1.1.2" "@lottiefiles/dotlottie-wc": "npm:^0.4.0" @@ -3379,33 +3379,33 @@ __metadata: lodash.clonedeep: "npm:^4.5.0" shiki: "npm:^1.12.0" zod: "npm:^3.23.8" - checksum: 10/a9941e5f467ecd130c3a98e05ffb0d35f1aa9ec9c54cc2810c8a3d90e69ba227c3dd737a4339971ba5ce630fba1d91fc23262bff3943f82d4ec105c9c726370e + checksum: 10/dde2e4b8d79bc7fce1add519b3dd4eed35b187c8f90cb609880349d100f9262694124fd673c9e9606eb1c3912632cf69dcded46ee10c79241141a9d30ebf4f3e languageName: node linkType: hard -"@blocksuite/affine-model@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/affine-model@npm:0.19.0" +"@blocksuite/affine-model@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/affine-model@npm:0.19.1" dependencies: - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" fractional-indexing: "npm:^3.2.0" zod: "npm:^3.23.8" - checksum: 10/f71a128e21a7a2926c9b972447e5cba436ef79d562fc8d3d829cb4de22ca51f6d417a30a9aa1053b20197b32adbbe2c7e6b9ced06ed49d395fde92975db4257f + checksum: 10/f306c7c44e31c4f0e63a706f801775dc492246daf65247827a7553dbed3d15916cdea5d94f55ff519947de18d074e901f52d01bf1dde7e16774c58b102d1bc49 languageName: node linkType: hard -"@blocksuite/affine-shared@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/affine-shared@npm:0.19.0" +"@blocksuite/affine-shared@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/affine-shared@npm:0.19.1" dependencies: - "@blocksuite/affine-model": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/affine-model": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" "@floating-ui/dom": "npm:^1.6.10" "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" @@ -3417,46 +3417,46 @@ __metadata: lodash.mergewith: "npm:^4.6.2" minimatch: "npm:^10.0.1" zod: "npm:^3.23.8" - checksum: 10/f413128946b800bdc3f2a066b22045b1a5c26ffd76fcf0aecd0fddfcb8aaf8ce197d40057ffa51f4e6bfbf74f85becca300894a0b1372b14fc50a01e1e75fd59 + checksum: 10/f84e0f5d4fba7de1f6eba99195326072036a5662f5b73930322031c56fa472d68cbe1e20450de50e30fb3b9e7d688af1ef63b595f730c8fa08c7c385c8bacba3 languageName: node linkType: hard -"@blocksuite/affine-widget-scroll-anchoring@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/affine-widget-scroll-anchoring@npm:0.19.0" +"@blocksuite/affine-widget-scroll-anchoring@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/affine-widget-scroll-anchoring@npm:0.19.1" dependencies: - "@blocksuite/affine-model": "npm:0.19.0" - "@blocksuite/affine-shared": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" + "@blocksuite/affine-model": "npm:0.19.1" + "@blocksuite/affine-shared": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" "@preact/signals-core": "npm:^1.8.0" "@toeverything/theme": "npm:^1.1.1" lit: "npm:^3.2.0" - checksum: 10/f69a480e532057499cc2c2a733bcd8d9465d651c5623e6b2852f19c220758aaaaf6fa5efa981d3aaf064c640bb8f88093cf6f869b8c9ebf361d8f2d0188b541f + checksum: 10/87f5a9c9a3d370f210ff631b310821c78ce57c6cb4f7ddd7d76e7f2da0e7c15a34b8dc27ea05d046b39159e0eb46055a284346c73c74df5d144a969d753e7177 languageName: node linkType: hard -"@blocksuite/affine@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/affine@npm:0.19.0" +"@blocksuite/affine@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/affine@npm:0.19.1" dependencies: - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/blocks": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/presets": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" - checksum: 10/e3aad22d3a308a7016b0b95d5e9271b0582d4c4fa634487015b51269b2039077d8f87fab56b480480839bdab0bda832ad5ab42631f809f05f4d843a36ee7b9d6 + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/blocks": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/presets": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" + checksum: 10/6414914385610553cbda8859420b11a24d3e420b97c548b77d6aed865a91350d9eacd2e3502084a7fa40d7d3affb7cc0e9d03036c359e6b8666f1f44ea1c155a languageName: node linkType: hard -"@blocksuite/block-std@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/block-std@npm:0.19.0" +"@blocksuite/block-std@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/block-std@npm:0.19.1" dependencies: - "@blocksuite/global": "npm:0.19.0" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/global": "npm:0.19.1" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" "@types/hast": "npm:^3.0.4" @@ -3468,28 +3468,28 @@ __metadata: unified: "npm:^11.0.5" w3c-keyname: "npm:^2.2.8" zod: "npm:^3.23.8" - checksum: 10/8870dffc946961a7f8332714776c984107d2e4328f1f64a5195b7b4e2d8af222173f5f197de4845240ccc9cf8df942e4afd2b71d89239c20cd363f79a0d950ef + checksum: 10/3cedc263249a3efc01b06ca6ce8c6a8dd345bbd4584537e8ccee1c45900961e92b893df823720e5b834e46860556ac78cc3b3616b00b2c852f440cccbd1b8368 languageName: node linkType: hard -"@blocksuite/blocks@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/blocks@npm:0.19.0" +"@blocksuite/blocks@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/blocks@npm:0.19.1" dependencies: - "@blocksuite/affine-block-embed": "npm:0.19.0" - "@blocksuite/affine-block-list": "npm:0.19.0" - "@blocksuite/affine-block-paragraph": "npm:0.19.0" - "@blocksuite/affine-block-surface": "npm:0.19.0" - "@blocksuite/affine-components": "npm:0.19.0" - "@blocksuite/affine-model": "npm:0.19.0" - "@blocksuite/affine-shared": "npm:0.19.0" - "@blocksuite/affine-widget-scroll-anchoring": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/data-view": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" + "@blocksuite/affine-block-embed": "npm:0.19.1" + "@blocksuite/affine-block-list": "npm:0.19.1" + "@blocksuite/affine-block-paragraph": "npm:0.19.1" + "@blocksuite/affine-block-surface": "npm:0.19.1" + "@blocksuite/affine-components": "npm:0.19.1" + "@blocksuite/affine-model": "npm:0.19.1" + "@blocksuite/affine-shared": "npm:0.19.1" + "@blocksuite/affine-widget-scroll-anchoring": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/data-view": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" "@blocksuite/icons": "npm:^2.1.75" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" "@floating-ui/dom": "npm:^1.6.10" "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" @@ -3527,20 +3527,20 @@ __metadata: simple-xml-to-json: "npm:^1.2.2" unified: "npm:^11.0.5" zod: "npm:^3.23.8" - checksum: 10/f9df45732416218a6234785f7b981554d34a62d4c017c50abf57aa57c21f21d128b905be0c296aa9aa5a199a10474ae77d6921ac439574a8aa41e85d553ade93 + checksum: 10/f89fdce1ea320528595dbf99a5ecb4fbc8684d0b1da27bb67dc62d91409ea3cd5f97e07ed481b716fc02dcfd9918d1ebbf2137ba05e6a80bd36ce506a033f146 languageName: node linkType: hard -"@blocksuite/data-view@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/data-view@npm:0.19.0" +"@blocksuite/data-view@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/data-view@npm:0.19.1" dependencies: - "@blocksuite/affine-components": "npm:0.19.0" - "@blocksuite/affine-shared": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" + "@blocksuite/affine-components": "npm:0.19.1" + "@blocksuite/affine-shared": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" "@blocksuite/icons": "npm:^2.1.75" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/store": "npm:0.19.1" "@emotion/hash": "npm:^0.9.2" "@floating-ui/dom": "npm:^1.6.10" "@lit/context": "npm:^1.1.2" @@ -3549,19 +3549,19 @@ __metadata: date-fns: "npm:^4.0.0" lit: "npm:^3.2.0" zod: "npm:^3.23.8" - checksum: 10/793cc9f0b5a3542bee95226b191fdbca519dafcef1a863fe7e85f7907700078cbc8691e60be6088746d2d8c61fb425d8168a462a35f454b4036724646e864837 + checksum: 10/ee2b511cc10ed95efeae07126004128127fa4dea8dac58e2f583e5d56dd3f77c02831fa51ae6746a4a912e5d89a38b2b03e19a2a8ba76d95e8fdd7c4cf29911c languageName: node linkType: hard -"@blocksuite/global@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/global@npm:0.19.0" +"@blocksuite/global@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/global@npm:0.19.1" dependencies: "@preact/signals-core": "npm:^1.8.0" lib0: "npm:^0.2.97" lit: "npm:^3.2.0" zod: "npm:^3.23.8" - checksum: 10/b90a00f3873848a9554cfe338f67e2e7d234a0db3270671f1ea8f08d355a2e54f1eb697094cb796eb34decbf13b14df054c878dc120f94f6e65a6a4a0da5154f + checksum: 10/65edbe50f29fb741266b84203c460297eaacbf633e4d6ff5b6d234ce13fa3e09920bcc3a924a70ef16a6bde63e89680b31cb6a157deb785b9113c3c902ee16e4 languageName: node linkType: hard @@ -3581,49 +3581,49 @@ __metadata: languageName: node linkType: hard -"@blocksuite/inline@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/inline@npm:0.19.0" +"@blocksuite/inline@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/inline@npm:0.19.1" dependencies: - "@blocksuite/global": "npm:0.19.0" + "@blocksuite/global": "npm:0.19.1" "@preact/signals-core": "npm:^1.8.0" zod: "npm:^3.23.8" peerDependencies: lit: ^3.2.0 yjs: ^13.6.18 - checksum: 10/4b28b3fc7e06bbacc74f7d2bc9394009c07bbdc75b52d1af6d8e72e6523403b2b4edc6dec31f063bc0cd7468d08670a6eb938222ab0110d385df018f20ae61b1 + checksum: 10/39b7a2c1088a573e41b73118ac7e891a2ba055247133567210b8e71dd27b54146955a2a43680179c8e4ebd00dbb0486fcdd88fe2a4267892d0ebac55573c262d languageName: node linkType: hard -"@blocksuite/presets@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/presets@npm:0.19.0" +"@blocksuite/presets@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/presets@npm:0.19.1" dependencies: - "@blocksuite/affine-block-surface": "npm:0.19.0" - "@blocksuite/affine-model": "npm:0.19.0" - "@blocksuite/affine-shared": "npm:0.19.0" - "@blocksuite/block-std": "npm:0.19.0" - "@blocksuite/blocks": "npm:0.19.0" - "@blocksuite/global": "npm:0.19.0" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/store": "npm:0.19.0" + "@blocksuite/affine-block-surface": "npm:0.19.1" + "@blocksuite/affine-model": "npm:0.19.1" + "@blocksuite/affine-shared": "npm:0.19.1" + "@blocksuite/block-std": "npm:0.19.1" + "@blocksuite/blocks": "npm:0.19.1" + "@blocksuite/global": "npm:0.19.1" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/store": "npm:0.19.1" "@floating-ui/dom": "npm:^1.6.10" "@lottiefiles/dotlottie-wc": "npm:^0.4.0" "@preact/signals-core": "npm:^1.8.0" "@toeverything/theme": "npm:^1.1.1" lit: "npm:^3.2.0" zod: "npm:^3.23.8" - checksum: 10/f9d3de1f5a1f2c1f5ed8206efe7d88059eec9a5520be4e2044b75ffc15d5b5b29f9a28a720f33866fa64c17af5303493a85909fe8e1bd826afa73cd3ca4f40fd + checksum: 10/08250d42105e7c499ef3c8a35f9e721389129e4a51ce2c9667414edfe9c10da7b048ab0a3cd4b623da1d30be96c11d6bd2091328c4efa1a4a521adb861324f93 languageName: node linkType: hard -"@blocksuite/store@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/store@npm:0.19.0" +"@blocksuite/store@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/store@npm:0.19.1" dependencies: - "@blocksuite/global": "npm:0.19.0" - "@blocksuite/inline": "npm:0.19.0" - "@blocksuite/sync": "npm:0.19.0" + "@blocksuite/global": "npm:0.19.1" + "@blocksuite/inline": "npm:0.19.1" + "@blocksuite/sync": "npm:0.19.1" "@preact/signals-core": "npm:^1.8.0" "@types/flexsearch": "npm:^0.7.6" "@types/lodash.ismatch": "npm:^4.4.9" @@ -3639,21 +3639,21 @@ __metadata: zod: "npm:^3.23.8" peerDependencies: yjs: ^13.6.18 - checksum: 10/ff6daaa737e779311b61436a6caf422bd2558c46a6d11880844db331862a82a20f29546499f2fd2c464ed9b2962ca4b4ee3207d483029f00e31961f1fc784e49 + checksum: 10/33c6f5ef863488ef899b34eccb42a41bc49186bef746e8fdd82373b86ef902b1ff94593149a3b5c00e5c7405e5a25af6fa93fd2c6f7f3c776c4184bb1483a266 languageName: node linkType: hard -"@blocksuite/sync@npm:0.19.0": - version: 0.19.0 - resolution: "@blocksuite/sync@npm:0.19.0" +"@blocksuite/sync@npm:0.19.1": + version: 0.19.1 + resolution: "@blocksuite/sync@npm:0.19.1" dependencies: - "@blocksuite/global": "npm:0.19.0" + "@blocksuite/global": "npm:0.19.1" idb: "npm:^8.0.0" idb-keyval: "npm:^6.2.1" y-protocols: "npm:^1.0.6" peerDependencies: yjs: ^13.6.15 - checksum: 10/a989e6c1ed1f640d003e6691062ac5422b1d8277a0946c057f4ca4c77a69c9d58260d4accba1d5b1aff9f6b2c252852bcf1ea1a0b413ca89bdf39063c1685571 + checksum: 10/bf777974625ecf8cfe377b5ce8a82ff7550aa77ecd88d9dcbe6f75a924a581b214a8f5181f867fedda606ed59e6db8290c53db0c9f6455412bac1e7174361277 languageName: node linkType: hard @@ -13776,7 +13776,7 @@ __metadata: "@affine/debug": "workspace:*" "@affine/env": "workspace:*" "@affine/templates": "workspace:*" - "@blocksuite/affine": "npm:0.19.0" + "@blocksuite/affine": "npm:0.19.1" "@datastructures-js/binary-search-tree": "npm:^5.3.2" "@emotion/react": "npm:^11.14.0" "@swc/core": "npm:^1.10.1"