fix(core): in edgeless mode, an error occurs when asking AI questions without selecting any content (#12437)

### TL;DR

fix: in edgeless mode, an error occurs when asking AI questions without selecting any content

> CLOSE AI-133

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added support for asking AI input in edgeless mode when no content is selected.
  - Enhanced AI panel behavior with improved input handling and chat message sending in edgeless mode.
- **Bug Fixes**
  - Improved handling of AI chat input visibility and context extraction in edgeless mode.
- **Tests**
  - Introduced new end-to-end tests to verify chat interactions with AI in edgeless mode.
  - Centralized editor content removal logic in test utilities for better maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
yoyoyohamapi
2025-05-23 06:18:28 +00:00
parent e2e00688a9
commit fd3a2756f8
7 changed files with 83 additions and 22 deletions

View File

@@ -6,14 +6,9 @@ import {
type ShapeElementModel,
} from '@blocksuite/affine/model';
import { matchModels } from '@blocksuite/affine/shared/utils';
import type { BlockComponent, EditorHost } from '@blocksuite/affine/std';
import type { BlockComponent } from '@blocksuite/affine/std';
import type { GfxModel } from '@blocksuite/affine/std/gfx';
import {
AFFINE_EDGELESS_COPILOT_WIDGET,
type EdgelessCopilotWidget,
} from '../widgets/edgeless-copilot';
export function mindMapToMarkdown(mindmap: MindmapElementModel) {
let markdownStr = '';
@@ -45,17 +40,7 @@ export function isMindmapChild(ele: GfxModel) {
return ele?.group instanceof MindmapElementModel && !isMindMapRoot(ele);
}
export function getEdgelessCopilotWidget(
host: EditorHost
): EdgelessCopilotWidget {
const rootBlockId = host.store.root?.id as string;
const copilotWidget = host.view.getWidget(
AFFINE_EDGELESS_COPILOT_WIDGET,
rootBlockId
) as EdgelessCopilotWidget;
return copilotWidget;
}
export { getEdgelessCopilotWidget } from './get-edgeless-copilot-widget';
export function findNoteBlockModel(blockElement: BlockComponent) {
let curBlock = blockElement;

View File

@@ -0,0 +1,16 @@
import type { EditorHost } from '@blocksuite/affine/std';
import type { EdgelessCopilotWidget } from '../widgets/edgeless-copilot';
import { AFFINE_EDGELESS_COPILOT_WIDGET } from '../widgets/edgeless-copilot/constant';
export function getEdgelessCopilotWidget(
host: EditorHost
): EdgelessCopilotWidget {
const rootBlockId = host.store.root?.id as string;
const copilotWidget = host.view.getWidget(
AFFINE_EDGELESS_COPILOT_WIDGET,
rootBlockId
) as EdgelessCopilotWidget;
return copilotWidget;
}

View File

@@ -29,7 +29,7 @@ import {
import { getContentFromSlice } from '../../utils';
import type { CopilotTool } from '../tool/copilot-tool';
import { getEdgelessCopilotWidget } from './edgeless';
import { getEdgelessCopilotWidget } from './get-edgeless-copilot-widget';
export async function selectedToCanvas(host: EditorHost) {
const gfx = host.std.get(GfxControllerIdentifier);

View File

@@ -0,0 +1 @@
export const AFFINE_EDGELESS_COPILOT_WIDGET = 'affine-edgeless-copilot-widget';

View File

@@ -27,13 +27,14 @@ import { styleMap } from 'lit/directives/style-map.js';
import { literal, unsafeStatic } from 'lit/static-html.js';
import type { AIItemGroupConfig } from '../../components/ai-item/types.js';
import { AIProvider } from '../../provider/index.js';
import { extractSelectedContent } from '../../utils/extract.js';
import {
AFFINE_AI_PANEL_WIDGET,
AffineAIPanelWidget,
} from '../ai-panel/ai-panel.js';
import { EdgelessCopilotPanel } from '../edgeless-copilot-panel/index.js';
export const AFFINE_EDGELESS_COPILOT_WIDGET = 'affine-edgeless-copilot-widget';
import { AFFINE_EDGELESS_COPILOT_WIDGET } from './constant.js';
export class EdgelessCopilotWidget extends WidgetComponent<RootBlockModel> {
static override styles = css`
@@ -95,6 +96,31 @@ export class EdgelessCopilotWidget extends WidgetComponent<RootBlockModel> {
if (input instanceof AffineAIPanelWidget) {
input.setState('input', referenceElement);
const aiPanel = input;
// TODO: @xiaojun refactor these scattered config overrides
if (aiPanel.config && !aiPanel.config.generateAnswer) {
aiPanel.config.generateAnswer = ({ finish, input }) => {
finish('success');
aiPanel.hide();
extractSelectedContent(this.host)
.then(context => {
AIProvider.slots.requestSendWithChat.next({
input,
context,
host: this.host,
});
})
.catch(console.error);
};
aiPanel.config.inputCallback = text => {
const panel = this.shadowRoot?.querySelector(
'edgeless-copilot-panel'
);
if (panel instanceof HTMLElement) {
panel.style.visibility = text ? 'hidden' : 'visible';
}
};
}
requestAnimationFrame(() => {
this._createCopilotPanel();
this._updateCopilotPanel(input);
@@ -314,3 +340,5 @@ declare global {
[AFFINE_EDGELESS_COPILOT_WIDGET]: EdgelessCopilotWidget;
}
}
export * from './constant';