mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
fix: optimize ai chat block position calculation (#7683)
[BS-977](https://linear.app/affine-design/issue/BS-977/新生成的分支chat-block应该采用类似mindmap新节点的的定位方式,避免互相遮盖)
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
||||
ConnectorMode,
|
||||
type EdgelessRootService,
|
||||
} from '@blocksuite/blocks';
|
||||
import { Bound } from '@blocksuite/global/utils';
|
||||
import {
|
||||
type AIChatBlockModel,
|
||||
type ChatMessage,
|
||||
@@ -32,6 +31,7 @@ import { AIChatErrorRenderer } from '../messages/error';
|
||||
import { AIProvider } from '../provider';
|
||||
import { PeekViewStyles } from './styles';
|
||||
import type { ChatContext } from './types';
|
||||
import { calcChildBound } from './utils';
|
||||
|
||||
@customElement('ai-chat-block-peek-view')
|
||||
export class AIChatBlockPeekView extends LitElement {
|
||||
@@ -53,10 +53,6 @@ export class AIChatBlockPeekView extends LitElement {
|
||||
return this.parentModel.messages;
|
||||
}
|
||||
|
||||
private get parentXYWH() {
|
||||
return this.parentModel.xywh;
|
||||
}
|
||||
|
||||
private get parentChatBlockId() {
|
||||
return this.parentModel.id;
|
||||
}
|
||||
@@ -156,21 +152,6 @@ export class AIChatBlockPeekView extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentXYWH = Bound.deserialize(this.parentXYWH);
|
||||
const {
|
||||
x: parentX,
|
||||
y: parentY,
|
||||
w: parentWidth,
|
||||
h: parentHeight,
|
||||
} = parentXYWH;
|
||||
|
||||
// Add AI chat block to the center of the viewport
|
||||
// TODO: optimize the position of the AI chat block
|
||||
const gap = parentWidth;
|
||||
const x = parentX + parentWidth + gap;
|
||||
const y = parentY;
|
||||
const bound = new Bound(x, y, parentWidth, parentHeight);
|
||||
|
||||
// Get fork session messages
|
||||
const messages = await this._constructBranchChatBlockMessages(
|
||||
doc,
|
||||
@@ -181,6 +162,7 @@ export class AIChatBlockPeekView extends LitElement {
|
||||
}
|
||||
|
||||
const edgelessService = this._rootService as EdgelessRootService;
|
||||
const bound = calcChildBound(this.parentModel, edgelessService);
|
||||
const aiChatBlockId = edgelessService.addBlock(
|
||||
'affine:embed-ai-chat' as keyof BlockSuite.BlockModels,
|
||||
{
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import type { EdgelessRootService } from '@blocksuite/blocks';
|
||||
import { Bound } from '@blocksuite/global/utils';
|
||||
import {
|
||||
type AIChatBlockModel,
|
||||
CHAT_BLOCK_HEIGHT,
|
||||
CHAT_BLOCK_WIDTH,
|
||||
} from '@blocksuite/presets';
|
||||
|
||||
/**
|
||||
* Calculates the bounding box for a child block
|
||||
* Based on the parent block's position and the existing connectors.
|
||||
* Place the child block to the right of the parent block as much as possible.
|
||||
* If the parent block already has connected child blocks
|
||||
* Distribute them evenly along the y-axis as much as possible.
|
||||
* @param parentModel - The model of the parent block.
|
||||
* @param service - The EdgelessRootService instance.
|
||||
* @returns The calculated bounding box for the child block.
|
||||
*/
|
||||
export function calcChildBound(
|
||||
parentModel: AIChatBlockModel,
|
||||
service: EdgelessRootService
|
||||
) {
|
||||
const parentXYWH = Bound.deserialize(parentModel.xywh);
|
||||
const { x: parentX, y: parentY, w: parentWidth } = parentXYWH;
|
||||
|
||||
const connectors = service.getConnectors(parentModel.id);
|
||||
const gapX = CHAT_BLOCK_WIDTH;
|
||||
const gapY = 60;
|
||||
const defaultX = parentX + parentWidth + gapX;
|
||||
const defaultY = parentY;
|
||||
|
||||
if (!connectors.length) {
|
||||
return new Bound(defaultX, defaultY, CHAT_BLOCK_WIDTH, CHAT_BLOCK_HEIGHT);
|
||||
} else {
|
||||
// Filter out the connectors which source is the parent block
|
||||
const childConnectors = connectors.filter(
|
||||
connector => connector.source.id === parentModel.id
|
||||
);
|
||||
// Get all the target blocks of the child connectors
|
||||
const targetBlocks = childConnectors
|
||||
.map(connector => connector.target.id)
|
||||
.filter(id => id !== undefined)
|
||||
.map(id => service.getElementById(id))
|
||||
.filter(block => !!block);
|
||||
|
||||
if (targetBlocks.length) {
|
||||
// Sort target blocks by their y position
|
||||
targetBlocks.sort(
|
||||
(a, b) => Bound.deserialize(a.xywh).y - Bound.deserialize(b.xywh).y
|
||||
);
|
||||
|
||||
let x, y;
|
||||
// Calculate the position based on the number of target blocks
|
||||
const middleIndex = Math.floor((targetBlocks.length - 1) / 2);
|
||||
const middleBlock = targetBlocks[middleIndex];
|
||||
const { y: middleY, h: middleHeight } = Bound.deserialize(
|
||||
middleBlock.xywh
|
||||
);
|
||||
const lastBlock = targetBlocks[targetBlocks.length - 1];
|
||||
const {
|
||||
x: lastX,
|
||||
y: lastY,
|
||||
h: lastHeight,
|
||||
} = Bound.deserialize(lastBlock.xywh);
|
||||
|
||||
if (targetBlocks.length % 2 === 0) {
|
||||
// If even number of target blocks
|
||||
// place the new bound above the middle block with same same gap as the last block
|
||||
x = lastX;
|
||||
const gap = lastY - (middleY + middleHeight);
|
||||
y = middleY - gap - CHAT_BLOCK_HEIGHT;
|
||||
} else {
|
||||
// If odd number of target blocks, place the new bound below the last block with a gap
|
||||
x = lastX;
|
||||
y = lastY + lastHeight + gapY;
|
||||
}
|
||||
|
||||
return new Bound(x, y, CHAT_BLOCK_WIDTH, CHAT_BLOCK_HEIGHT);
|
||||
} else {
|
||||
// If no valid target blocks, fallback to default position
|
||||
return new Bound(defaultX, defaultY, CHAT_BLOCK_WIDTH, CHAT_BLOCK_HEIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user