mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 06:47:02 +08: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,
|
ConnectorMode,
|
||||||
type EdgelessRootService,
|
type EdgelessRootService,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
import { Bound } from '@blocksuite/global/utils';
|
|
||||||
import {
|
import {
|
||||||
type AIChatBlockModel,
|
type AIChatBlockModel,
|
||||||
type ChatMessage,
|
type ChatMessage,
|
||||||
@@ -32,6 +31,7 @@ import { AIChatErrorRenderer } from '../messages/error';
|
|||||||
import { AIProvider } from '../provider';
|
import { AIProvider } from '../provider';
|
||||||
import { PeekViewStyles } from './styles';
|
import { PeekViewStyles } from './styles';
|
||||||
import type { ChatContext } from './types';
|
import type { ChatContext } from './types';
|
||||||
|
import { calcChildBound } from './utils';
|
||||||
|
|
||||||
@customElement('ai-chat-block-peek-view')
|
@customElement('ai-chat-block-peek-view')
|
||||||
export class AIChatBlockPeekView extends LitElement {
|
export class AIChatBlockPeekView extends LitElement {
|
||||||
@@ -53,10 +53,6 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
return this.parentModel.messages;
|
return this.parentModel.messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get parentXYWH() {
|
|
||||||
return this.parentModel.xywh;
|
|
||||||
}
|
|
||||||
|
|
||||||
private get parentChatBlockId() {
|
private get parentChatBlockId() {
|
||||||
return this.parentModel.id;
|
return this.parentModel.id;
|
||||||
}
|
}
|
||||||
@@ -156,21 +152,6 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
return;
|
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
|
// Get fork session messages
|
||||||
const messages = await this._constructBranchChatBlockMessages(
|
const messages = await this._constructBranchChatBlockMessages(
|
||||||
doc,
|
doc,
|
||||||
@@ -181,6 +162,7 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const edgelessService = this._rootService as EdgelessRootService;
|
const edgelessService = this._rootService as EdgelessRootService;
|
||||||
|
const bound = calcChildBound(this.parentModel, edgelessService);
|
||||||
const aiChatBlockId = edgelessService.addBlock(
|
const aiChatBlockId = edgelessService.addBlock(
|
||||||
'affine:embed-ai-chat' as keyof BlockSuite.BlockModels,
|
'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