feat: 全新智能体功能
- 基于先进智能体框架,增加智能体编排功能 - 增加智能体聊天,并对接持久化
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
import type {
|
||||
AgentStudioCanvasSize,
|
||||
AgentStudioEdgeView,
|
||||
AgentStudioLayoutSnapshot,
|
||||
AgentStudioNodeData,
|
||||
AgentStudioNodeView,
|
||||
} from '../../components/agent-studio/types';
|
||||
import type {
|
||||
AgentDraftState,
|
||||
AgentKnowledgeBinding,
|
||||
AgentOption,
|
||||
AgentToolBinding,
|
||||
} from '../../types';
|
||||
|
||||
import {computed} from 'vue';
|
||||
|
||||
const BASE_NODE_ID = 'agent-base';
|
||||
const BASE_POSITION = { x: 430, y: 260 };
|
||||
const CAPABILITY_GAP = 104;
|
||||
const CAPABILITY_OFFSET_X = 360;
|
||||
const CAPABILITY_START_Y = 148;
|
||||
const CAPABILITY_NODE_HEIGHT = 78;
|
||||
const CAPABILITY_NODE_WIDTH = 212;
|
||||
const LEFT_SCREEN_PADDING = 48;
|
||||
const TOP_SCREEN_PADDING = 96;
|
||||
const INSPECTOR_RESERVED_WIDTH = 468;
|
||||
const MIN_VISIBLE_WORKSPACE_WIDTH = 520;
|
||||
|
||||
function firstText(...values: unknown[]) {
|
||||
const matched = values.find((value) => String(value || '').trim());
|
||||
return matched ? String(matched).trim() : '';
|
||||
}
|
||||
|
||||
function buildKnowledgeTitle(
|
||||
binding: AgentKnowledgeBinding,
|
||||
options: AgentOption[],
|
||||
) {
|
||||
const matchedOption = options.find(
|
||||
(item) => String(item.value) === String(binding.knowledgeId),
|
||||
);
|
||||
return firstText(
|
||||
binding.resourceSummary?.title,
|
||||
binding.resourceSummary?.name,
|
||||
binding.resourceSummary?.label,
|
||||
binding.resourceSummary?.displayName,
|
||||
binding.resourceSnapshot?.title,
|
||||
binding.resourceSnapshot?.name,
|
||||
binding.resourceSnapshot?.label,
|
||||
binding.resourceSnapshot?.displayName,
|
||||
binding.title,
|
||||
binding.name,
|
||||
binding.knowledgeName,
|
||||
binding.collectionName,
|
||||
matchedOption?.label,
|
||||
matchedOption?.raw?.title,
|
||||
matchedOption?.raw?.name,
|
||||
);
|
||||
}
|
||||
|
||||
function buildToolTitle(binding: AgentToolBinding) {
|
||||
return firstText(
|
||||
binding.resourceSummary?.title,
|
||||
binding.resourceSummary?.name,
|
||||
binding.resourceSnapshot?.title,
|
||||
binding.resourceSnapshot?.name,
|
||||
binding.toolName,
|
||||
);
|
||||
}
|
||||
|
||||
function buildToolDetail(binding: AgentToolBinding, fallback: string) {
|
||||
const toolName = firstText(binding.toolName);
|
||||
const resourceName = buildToolTitle(binding);
|
||||
if (toolName && resourceName && toolName !== resourceName) {
|
||||
return `${resourceName} / ${toolName}`;
|
||||
}
|
||||
return resourceName || toolName || fallback;
|
||||
}
|
||||
|
||||
function toFlowPoint(
|
||||
point: { x: number; y: number },
|
||||
viewport: NonNullable<AgentStudioLayoutSnapshot['viewport']>,
|
||||
) {
|
||||
return {
|
||||
x: (point.x - viewport.x) / viewport.zoom,
|
||||
y: (point.y - viewport.y) / viewport.zoom,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveVisibleLeftX(
|
||||
layout?: AgentStudioLayoutSnapshot,
|
||||
canvasSize?: AgentStudioCanvasSize,
|
||||
) {
|
||||
const viewport = layout?.viewport;
|
||||
if (!viewport || !canvasSize?.width) {
|
||||
return BASE_POSITION.x - CAPABILITY_OFFSET_X;
|
||||
}
|
||||
|
||||
const availableWidth = Math.max(
|
||||
MIN_VISIBLE_WORKSPACE_WIDTH,
|
||||
canvasSize.width - INSPECTOR_RESERVED_WIDTH,
|
||||
);
|
||||
const screenX = Math.min(
|
||||
LEFT_SCREEN_PADDING,
|
||||
Math.max(24, availableWidth - CAPABILITY_NODE_WIDTH - LEFT_SCREEN_PADDING),
|
||||
);
|
||||
return toFlowPoint({ x: screenX, y: 0 }, viewport).x;
|
||||
}
|
||||
|
||||
function resolveVisibleTopY(
|
||||
layout?: AgentStudioLayoutSnapshot,
|
||||
canvasSize?: AgentStudioCanvasSize,
|
||||
) {
|
||||
const viewport = layout?.viewport;
|
||||
if (!viewport || !canvasSize?.height) {
|
||||
return CAPABILITY_START_Y;
|
||||
}
|
||||
return toFlowPoint({ x: 0, y: TOP_SCREEN_PADDING }, viewport).y;
|
||||
}
|
||||
|
||||
function hasVerticalOverlap(
|
||||
position: { x: number; y: number },
|
||||
occupied: Array<{ x: number; y: number }>,
|
||||
) {
|
||||
return occupied.some(
|
||||
(item) =>
|
||||
Math.abs(item.x - position.x) < CAPABILITY_NODE_WIDTH &&
|
||||
Math.abs(item.y - position.y) < CAPABILITY_NODE_HEIGHT,
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveCapabilityNodePosition(params: {
|
||||
canvasSize?: AgentStudioCanvasSize;
|
||||
fallbackIndex: number;
|
||||
layout?: AgentStudioLayoutSnapshot;
|
||||
nodeId: string;
|
||||
occupiedPositions?: Array<{ x: number; y: number }>;
|
||||
}) {
|
||||
const persisted = params.layout?.nodePositions?.[params.nodeId];
|
||||
if (persisted) return persisted;
|
||||
|
||||
const occupied = params.occupiedPositions || [];
|
||||
const x = resolveVisibleLeftX(params.layout, params.canvasSize);
|
||||
let y =
|
||||
resolveVisibleTopY(params.layout, params.canvasSize) +
|
||||
params.fallbackIndex * CAPABILITY_GAP;
|
||||
|
||||
while (hasVerticalOverlap({ x, y }, occupied)) {
|
||||
y += CAPABILITY_GAP;
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
export function useAgentStudioModel(
|
||||
state: AgentDraftState,
|
||||
selectedNodeId: () => string,
|
||||
layout?: AgentStudioLayoutSnapshot,
|
||||
canvasSize?: () => AgentStudioCanvasSize | undefined,
|
||||
knowledgeOptions?: () => AgentOption[],
|
||||
) {
|
||||
return computed(() => {
|
||||
const positionOf = (nodeId: string, fallback: { x: number; y: number }) =>
|
||||
layout?.nodePositions?.[nodeId] || fallback;
|
||||
const size = canvasSize?.();
|
||||
const occupiedPositions: Array<{ x: number; y: number }> = Object.values(
|
||||
layout?.nodePositions || {},
|
||||
);
|
||||
const nodes: AgentStudioNodeView[] = [
|
||||
{
|
||||
id: BASE_NODE_ID,
|
||||
type: 'agentStudioBase',
|
||||
position: positionOf(BASE_NODE_ID, BASE_POSITION),
|
||||
width: 268,
|
||||
height: 104,
|
||||
data: {
|
||||
badge: '基座',
|
||||
detail: firstText(state.agent.description, '等待配置核心提示词'),
|
||||
iconKey: 'base',
|
||||
id: BASE_NODE_ID,
|
||||
kind: 'base',
|
||||
selected: selectedNodeId() === BASE_NODE_ID,
|
||||
title: firstText(state.agent.name, '未命名智能体'),
|
||||
} satisfies AgentStudioNodeData,
|
||||
},
|
||||
];
|
||||
|
||||
const knowledgeNodes = state.knowledgeBindings.map((binding, index) => {
|
||||
const nodeId = `knowledge:${binding.localId}`;
|
||||
const title = buildKnowledgeTitle(binding, knowledgeOptions?.() || []);
|
||||
const position = resolveCapabilityNodePosition({
|
||||
canvasSize: size,
|
||||
fallbackIndex: index,
|
||||
layout,
|
||||
nodeId,
|
||||
occupiedPositions,
|
||||
});
|
||||
occupiedPositions.push(position);
|
||||
return {
|
||||
id: nodeId,
|
||||
type: 'agentStudioCapability',
|
||||
position,
|
||||
width: CAPABILITY_NODE_WIDTH,
|
||||
height: CAPABILITY_NODE_HEIGHT,
|
||||
data: {
|
||||
badge: '知识库',
|
||||
detail: title || '待选择知识库',
|
||||
iconKey: 'knowledge',
|
||||
id: nodeId,
|
||||
kind: 'knowledge',
|
||||
selected: selectedNodeId() === nodeId,
|
||||
title: title || '知识库',
|
||||
} satisfies AgentStudioNodeData,
|
||||
};
|
||||
});
|
||||
const toolNodes = state.toolBindings.map((binding, index) => {
|
||||
const nodeId = `tool:${binding.localId}`;
|
||||
const isWorkflow =
|
||||
String(binding.toolType || '').toUpperCase() === 'WORKFLOW';
|
||||
const fallback = isWorkflow ? '待选择工作流' : '待选择插件工具';
|
||||
const detail = buildToolDetail(binding, fallback);
|
||||
const position = resolveCapabilityNodePosition({
|
||||
canvasSize: size,
|
||||
fallbackIndex: state.knowledgeBindings.length + index,
|
||||
layout,
|
||||
nodeId,
|
||||
occupiedPositions,
|
||||
});
|
||||
occupiedPositions.push(position);
|
||||
return {
|
||||
id: nodeId,
|
||||
type: 'agentStudioCapability',
|
||||
position,
|
||||
width: CAPABILITY_NODE_WIDTH,
|
||||
height: CAPABILITY_NODE_HEIGHT,
|
||||
data: {
|
||||
badge: isWorkflow ? '工作流' : '插件',
|
||||
detail,
|
||||
iconKey: isWorkflow ? 'workflow' : 'plugin',
|
||||
id: nodeId,
|
||||
kind: isWorkflow ? 'workflow' : 'plugin',
|
||||
selected: selectedNodeId() === nodeId,
|
||||
title:
|
||||
detail === fallback ? (isWorkflow ? '工作流' : '插件') : detail,
|
||||
} satisfies AgentStudioNodeData,
|
||||
};
|
||||
});
|
||||
|
||||
const capabilityNodes = [...knowledgeNodes, ...toolNodes];
|
||||
|
||||
const edges: AgentStudioEdgeView[] = capabilityNodes.map((node) => ({
|
||||
id: `edge:${node.id}`,
|
||||
source: BASE_NODE_ID,
|
||||
target: node.id,
|
||||
}));
|
||||
|
||||
nodes.push(...capabilityNodes);
|
||||
return { edges, nodes };
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user