Files
EasyFlow/easyflow-ui-admin/packages/tinyflow-ui/src/Tinyflow.ts
陈子默 72df00f25b feat: 全新智能体功能
- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
2026-05-25 11:42:48 +08:00

208 lines
5.7 KiB
TypeScript

import type {useSvelteFlow} from '@xyflow/svelte';
import {componentName} from './consts';
import {store} from './store/stores.svelte';
import type {TinyflowData, TinyflowOptions, TinyflowTheme} from './types';
type FlowInstance = ReturnType<typeof useSvelteFlow>;
export class Tinyflow {
private options!: TinyflowOptions;
private rootEl!: Element;
private svelteFlowInstance!: FlowInstance;
private tinyflowEl!: HTMLElement & {
options: TinyflowOptions;
onInit: (svelteFlowInstance: FlowInstance) => void;
};
constructor(options: TinyflowOptions) {
if (
typeof options.element !== 'string' &&
!(options.element instanceof Element)
) {
throw new Error('element must be a string or Element');
}
this._setOptions(options);
this._init();
}
private _init() {
if (typeof this.options.element === 'string') {
this.rootEl = document.querySelector(this.options.element)!;
if (!this.rootEl) {
throw new Error(
`element not found by document.querySelector('${this.options.element}')`,
);
}
} else if (this.options.element instanceof Element) {
this.rootEl = this.options.element;
} else {
throw new Error('element must be a string or Element');
}
this.tinyflowEl = this._createTinyflowElement();
this.rootEl.appendChild(this.tinyflowEl);
}
private _setOptions(options: TinyflowOptions) {
this.options = {
theme: options.theme || 'light',
...options,
};
}
private _getFlowInstance() {
if (!this.svelteFlowInstance) {
console.warn('Tinyflow instance is not initialized');
return null;
}
return this.svelteFlowInstance;
}
private _applyThemeClass(targetEl: Element, theme?: TinyflowTheme) {
targetEl.classList.remove('tf-theme-light', 'tf-theme-dark');
targetEl.classList.add(
theme === 'dark' ? 'tf-theme-dark' : 'tf-theme-light',
);
}
private _createTinyflowElement() {
const tinyflowEl = document.createElement(componentName) as HTMLElement & {
options: TinyflowOptions;
onInit: (svelteFlowInstance: FlowInstance) => void;
};
tinyflowEl.style.display = 'block';
tinyflowEl.style.width = '100%';
tinyflowEl.style.height = '100%';
this._applyThemeClass(tinyflowEl, this.options.theme);
tinyflowEl.options = this.options;
tinyflowEl.onInit = (svelteFlowInstance: FlowInstance) => {
this.svelteFlowInstance = svelteFlowInstance;
};
return tinyflowEl;
}
getOptions() {
return this.options;
}
getData() {
const flow = this._getFlowInstance();
if (!flow) {
return null;
}
return flow.toObject();
}
updateData(data: TinyflowData, options?: { preserveViewport?: boolean }) {
const flow = this._getFlowInstance();
if (!flow) {
return false;
}
const currentViewport = flow.getViewport();
const currentNodes = flow.getNodes();
const currentNodePositions = new Map(
currentNodes.map((node) => [node.id, node.position]),
);
const nextNodes =
options?.preserveViewport === true
? (data.nodes || currentNodes).map((node) => {
const currentPosition = currentNodePositions.get(node.id);
return currentPosition
? { ...node, position: { ...currentPosition } }
: node;
})
: data.nodes || currentNodes;
store.setNodes(nextNodes);
store.setEdges(data.edges || flow.getEdges());
if (data.viewport && options?.preserveViewport !== true) {
flow.setViewport(data.viewport, { duration: 0 });
} else {
flow.setViewport(currentViewport, { duration: 0 });
}
return true;
}
async focusNode(
nodeId: string,
options?: { duration?: number; zoom?: number },
) {
const flow = this._getFlowInstance();
if (!flow) {
return false;
}
const targetNode = flow.getNode(nodeId);
if (!targetNode) {
return false;
}
// Keep only the target node selected so the canvas has a clear visual focus.
flow.getNodes().forEach((node) => {
flow.updateNode(node.id, { selected: node.id === nodeId });
});
const internalNode = flow.getInternalNode(nodeId) as any;
const absolutePosition = internalNode?.internals?.positionAbsolute ||
(targetNode as any)?.positionAbsolute ||
targetNode.position || { x: 0, y: 0 };
const width =
internalNode?.measured?.width ||
(targetNode as any)?.measured?.width ||
(targetNode as any)?.width ||
260;
const height =
internalNode?.measured?.height ||
(targetNode as any)?.measured?.height ||
(targetNode as any)?.height ||
120;
const centerX = absolutePosition.x + width / 2;
const centerY = absolutePosition.y + height / 2;
const nextZoom = options?.zoom ?? Math.max(flow.getZoom(), 1);
await flow.setCenter(centerX, centerY, {
zoom: nextZoom,
duration: options?.duration ?? 280,
});
return true;
}
async fitView(options?: { duration?: number; padding?: number }) {
const flow = this._getFlowInstance();
if (!flow) {
return false;
}
await flow.fitView({
duration: options?.duration ?? 220,
padding: options?.padding ?? 0.2,
});
return true;
}
setTheme(theme: TinyflowTheme) {
this.options.theme = theme;
if (this.tinyflowEl) {
this._applyThemeClass(this.tinyflowEl, theme);
}
}
setData(data: TinyflowData) {
this.options.data = data;
this.tinyflowEl = this._createTinyflowElement();
this.destroy();
this.rootEl.appendChild(this.tinyflowEl);
}
destroy() {
while (this.rootEl.firstChild) {
this.rootEl.removeChild(this.rootEl.firstChild);
}
}
}