feat: 全新智能体功能

- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
This commit is contained in:
2026-05-25 11:42:48 +08:00
parent 6c3d98eaac
commit 72df00f25b
168 changed files with 22045 additions and 400 deletions

View File

@@ -1,6 +1,7 @@
import type { useSvelteFlow } from '@xyflow/svelte';
import { componentName } from './consts';
import type { TinyflowData, TinyflowOptions, TinyflowTheme } from './types';
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>;
@@ -93,6 +94,37 @@ export class Tinyflow {
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 },

View File

@@ -65,6 +65,14 @@
const readonly = options.readonly === true;
let canvasLocked = $state(readonly);
const hideBottomDock = options.hideBottomDock === true;
const hideEdgePanel = options.hideEdgePanel === true;
const hideMiniMap = options.hideMiniMap === true;
const hideNodePicker = options.hideNodePicker === true;
const nodesDraggable = options.nodesDraggable ?? !readonly;
const nodesConnectable = options.nodesConnectable ?? !readonly;
const elementsSelectable = options.elementsSelectable ?? !readonly;
const dropEnabled = options.dropEnabled ?? !readonly;
const connectionEnabled = nodesConnectable && !readonly;
const availableNodes = getAvailableNodes(options);
const onRunTest = options.onRunTest;
@@ -779,22 +787,22 @@
bind:nodes={store.getNodes, store.setNodes}
bind:edges={store.getEdges, store.setEdges}
bind:viewport={store.getViewport, store.setViewport}
nodesDraggable={!canvasLocked}
nodesConnectable={!canvasLocked}
elementsSelectable={!canvasLocked}
nodesDraggable={nodesDraggable && !canvasLocked}
nodesConnectable={nodesConnectable && !canvasLocked}
elementsSelectable={elementsSelectable && !canvasLocked}
panOnDrag={readonly ? true : !canvasLocked}
zoomOnScroll={readonly ? true : !canvasLocked}
zoomOnDoubleClick={readonly ? true : !canvasLocked}
ondrop={readonly ? undefined : onDrop}
ondragover={readonly ? undefined : onDragOver}
ondrop={dropEnabled ? onDrop : undefined}
ondragover={dropEnabled ? onDragOver : undefined}
isValidConnection={isValidConnection}
onconnectend={readonly ? undefined : onconnectend}
onconnectstart={readonly ? undefined : onconnectstart}
onconnect={readonly ? undefined : onconnect}
onconnectend={connectionEnabled ? onconnectend : undefined}
onconnectstart={connectionEnabled ? onconnectstart : undefined}
onconnect={connectionEnabled ? onconnect : undefined}
connectionRadius={50}
connectionLineComponent={FlowConnectionLine}
onedgeclick={(e) => {
if (readonly) {
if (readonly || hideEdgePanel) {
return;
}
showEdgePanel = true;
@@ -803,7 +811,7 @@
onbeforeconnect={(edge: any) => normalizeEdgeBeforeConnect(edge)}
ondelete={readonly ? undefined : onDelete}
onclick={(e) => {
if (readonly) {
if (readonly || hideEdgePanel) {
return;
}
const el = e.target as HTMLElement;
@@ -825,7 +833,9 @@
}}
>
<Background />
<MiniMap />
{#if !hideMiniMap}
<MiniMap />
{/if}
{#if showEdgePanel}
<Panel>
@@ -889,7 +899,7 @@
</Panel>
{/if}
</SvelteFlow>
{#if nodePickerVisible}
{#if nodePickerVisible && !hideNodePicker}
{#if pendingConnectionLine}
<svg class="node-picker-connection-line" width="100%" height="100%">
<FlowMarkerDefs id="tf-flow-inline-arrow-closed" />

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import {EdgeLabel, type EdgeProps, getBezierPath} from '@xyflow/svelte';
import FlowLinePath from './FlowLinePath.svelte';
import {getOptions} from '../utils/NodeUtils';
let {
sourceX,
@@ -16,6 +17,15 @@
labelStyle
}: EdgeProps = $props();
const options = getOptions();
const resolvedMarkerStart = $derived(options.hideEdgeMarkers === true ? undefined : markerStart);
const resolvedMarkerEnd = $derived(options.hideEdgeMarkers === true ? undefined : markerEnd);
const resolvedInteractionWidth = $derived(
typeof options.edgeInteractionWidth === 'number'
? options.edgeInteractionWidth
: interactionWidth
);
const edgeAnimated = $derived(options.edgeAnimated === false ? false : true);
const bezierPathResult = $derived.by(() =>
getBezierPath({
sourceX,
@@ -32,13 +42,13 @@
const labelY = $derived(bezierPathResult[2]);
</script>
<FlowLinePath path={path} {markerStart} {markerEnd} animated={true} />
<FlowLinePath path={path} markerStart={resolvedMarkerStart} markerEnd={resolvedMarkerEnd} animated={edgeAnimated} />
{#if interactionWidth > 0}
{#if resolvedInteractionWidth > 0}
<path
d={path}
stroke-opacity={0}
stroke-width={interactionWidth}
stroke-width={resolvedInteractionWidth}
fill="none"
class="svelte-flow__edge-interaction"
></path>

View File

@@ -8,8 +8,7 @@
useUpdateNodeInternals
} from '@xyflow/svelte';
import {Button, Collapse, FloatingTrigger, Input, Textarea} from '../base';
import {type Snippet} from 'svelte';
import {onDestroy, onMount} from 'svelte';
import {onDestroy, onMount, type Snippet} from 'svelte';
import {useDeleteNode} from '../utils/useDeleteNode.svelte';
import {useCopyNode} from '../utils/useCopyNode.svelte';
import {getOptions} from '../utils/NodeUtils';
@@ -70,6 +69,13 @@
const { copyNode } = useCopyNode();
const options = getOptions();
const toolbarHidden = options.hideNodeToolbar === true;
const nodeSettingHidden = options.hideNodeSetting === true;
const handlesHidden = options.hideNodeHandles === true;
const toolbarDeleteEnabled = $derived(allowDelete && !toolbarHidden);
const toolbarCopyEnabled = $derived(allowCopy && !toolbarHidden);
const toolbarExecuteEnabled = $derived(allowExecute && !toolbarHidden);
const toolbarSettingEnabled = $derived(allowSetting && !toolbarHidden && !nodeSettingHidden);
const executeNode = () => {
options.onNodeExecute?.(getNode(id)!);
@@ -111,10 +117,10 @@
</script>
{#if allowExecute || allowCopy || allowDelete}
{#if toolbarExecuteEnabled || toolbarCopyEnabled || toolbarDeleteEnabled || toolbarSettingEnabled}
<NodeToolbar position={Position.Top} align="start">
<div class="tf-node-toolbar">
{#if allowDelete}
{#if toolbarDeleteEnabled}
<Button class="tf-node-toolbar-item" onclick={()=>{ deleteNode(id) }}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path
@@ -122,7 +128,7 @@
</svg>
</Button>
{/if}
{#if allowCopy}
{#if toolbarCopyEnabled}
<Button class="tf-node-toolbar-item" onclick={()=>{copyNode(id)}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path
@@ -130,7 +136,7 @@
</svg>
</Button>
{/if}
{#if allowExecute}
{#if toolbarExecuteEnabled}
<Button class="tf-node-toolbar-item" onclick={executeNode}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path
@@ -138,7 +144,7 @@
</svg>
</Button>
{/if}
{#if allowSetting}
{#if toolbarSettingEnabled}
<FloatingTrigger placement="bottom">
<Button class="tf-node-toolbar-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
@@ -294,10 +300,10 @@
</div>
</div>
{#if showTargetHandle}
{#if showTargetHandle && !handlesHidden}
<Handle type="target" position={Position.Left} style=" left: -12px;top: 20px" />
{/if}
{#if showSourceHandle}
{#if showSourceHandle && !handlesHidden}
<Handle type="source" position={Position.Right} style="right: -12px;top: 20px" />
{/if}
{@render handle?.()}

View File

@@ -2,7 +2,7 @@
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type Node, type NodeProps, useNodesData, useSvelteFlow} from '@xyflow/svelte';
import {type Node, useNodesData, useSvelteFlow} from '@xyflow/svelte';
import {Button, Chosen, Heading, Input, Select, Textarea} from '../base';
import RefParameterList from '../core/RefParameterList.svelte';
import {getCurrentNodeId} from '#components/utils/NodeUtils';
@@ -76,7 +76,7 @@
let container = $state<HTMLElement | null>(null);
$effect(() => {
// 注意:由于 $effect 的 state 自动追踪问题,需要 data.expand 方在 if 里的最前面
if (data.expand && container) {
if ((data.expand || customNode.presentation === 'plain') && container) {
container.append(externalElement);
}
});
@@ -105,12 +105,14 @@
</script>
{#if customNode.presentation === 'plain'}
<div bind:this={container} style={customNode.rootStyle||""} class={customNode.rootClass}></div>
{:else}
<NodeWrapper data={{...data, description: customNode.description}} {...getRestProps()}>
<NodeWrapper data={{...data, description: customNode.description}} {...getRestProps()}>
{#snippet icon()}
{@html customNode.icon}
{/snippet}
{#snippet icon()}
{@html customNode.icon}
{/snippet}
{#if customNode.parametersEnable !== false}
<div class="heading">
@@ -251,7 +253,8 @@
<OutputDefList />
{/if}
</NodeWrapper>
</NodeWrapper>
{/if}
<style>
.heading {

View File

@@ -1,5 +1,5 @@
import type { Snippet } from 'svelte';
import type { Node, useSvelteFlow } from '@xyflow/svelte';
import type {Snippet} from 'svelte';
import type {Node, useSvelteFlow} from '@xyflow/svelte';
export type TinyflowData = Partial<
ReturnType<ReturnType<typeof useSvelteFlow>['toObject']>
@@ -50,7 +50,9 @@ export type CustomNodeForm = {
defaultValue?: string | number | boolean;
attrs?: Record<string, any>;
options?: SelectItem[];
resolveValue?: (data: Record<string, any>) => string | number | boolean | undefined;
resolveValue?: (
data: Record<string, any>,
) => string | number | boolean | undefined;
resolveOptions?: (data: Record<string, any>) => SelectItem[];
onValueChange?: (
value: string | number | boolean | undefined,
@@ -76,6 +78,7 @@ export type CustomNode = {
icon?: string;
sortNo?: number;
group?: 'base' | 'tools';
presentation?: 'default' | 'plain';
renderFirst?: boolean;
rootClass?: string;
rootStyle?: string;
@@ -100,6 +103,19 @@ export type TinyflowOptions = {
data?: TinyflowData | string;
readonly?: boolean;
hideBottomDock?: boolean;
hideEdgePanel?: boolean;
hideMiniMap?: boolean;
hideNodeHandles?: boolean;
hideNodeToolbar?: boolean;
hideNodePicker?: boolean;
hideNodeSetting?: boolean;
hideEdgeMarkers?: boolean;
edgeAnimated?: boolean;
edgeInteractionWidth?: number;
nodesDraggable?: boolean;
nodesConnectable?: boolean;
elementsSelectable?: boolean;
dropEnabled?: boolean;
provider?: {
llm?: () => SelectItem[] | Promise<SelectItem[]>;
knowledge?: () => SelectItem[] | Promise<SelectItem[]>;