chore: replace all cn comments of fe to en version by volc api (#320)
This commit is contained in:
@@ -64,7 +64,7 @@ export const defaultLabelStyle: LabelStyle = {
|
||||
export const defaultLabelText: LabelText = (datum, element, params) =>
|
||||
`${datum.start}-${datum.end}`;
|
||||
|
||||
// xScale的padding(解决hover后rect边框被截断问题)
|
||||
// padding of xScale (solve the problem of rect border being truncated after hover)
|
||||
export const scrollbarMargin = 10;
|
||||
export const datazoomHeight = 20;
|
||||
export const datazoomDecimals = 0;
|
||||
|
||||
@@ -126,7 +126,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
[_globalLabelStyle],
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 计算需要
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- calculation required
|
||||
const topOffset = datazoomHeight + datazoomPaddingBottom + 8;
|
||||
|
||||
const globalStyle: GlobalStyle = useMemo(
|
||||
@@ -142,7 +142,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
return rowCount * rowHeight;
|
||||
}, [flamethreadData]);
|
||||
|
||||
// 此参数含义: 可视窗口Height / 火焰图Height
|
||||
// Meaning of this parameter: Visual Window Height/Flame Map Height
|
||||
const yScaleRangeFactor = useMemo(() => {
|
||||
const rowCount = uniqWith(
|
||||
flamethreadData,
|
||||
@@ -182,7 +182,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
const tickData = scale.tickData(visibleColumnCount);
|
||||
const dx =
|
||||
tickData.length > 1
|
||||
? // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 计算需要
|
||||
? // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- calculation required
|
||||
(range[1] - range[0]) / (tickData.length - 1) / 2
|
||||
: 0;
|
||||
|
||||
@@ -197,7 +197,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
style: { dx: -dx },
|
||||
formatMethod: (_value: string) => {
|
||||
const value = Number(_value);
|
||||
// 特化逻辑: 隐藏0刻度
|
||||
// Specialized logic: hide 0 ticks
|
||||
if (dx > 0 && value === 0) {
|
||||
return '';
|
||||
}
|
||||
@@ -213,7 +213,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
{
|
||||
type: GrammarMarkType.component,
|
||||
componentType: ComponentEnum.grid,
|
||||
tickCount: visibleColumnCount, // vgrammer库的类型写的不严谨,实际是可用的
|
||||
tickCount: visibleColumnCount, // The types of the vgrammer library are not strictly written, but are actually usable
|
||||
scale: 'xScale',
|
||||
gridType: 'line',
|
||||
gridShape: 'line',
|
||||
@@ -270,7 +270,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
lineWidth,
|
||||
lineDash,
|
||||
visible: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 尺寸计算,无须处理
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- size calculation, no processing required
|
||||
distance: lineWidth / 2,
|
||||
};
|
||||
},
|
||||
@@ -290,7 +290,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
lineWidth,
|
||||
lineDash,
|
||||
visible: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 尺寸计算,无须定义常量
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- size calculation, no need to define constants
|
||||
distance: lineWidth / 2,
|
||||
};
|
||||
},
|
||||
@@ -311,7 +311,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
lineWidth,
|
||||
lineDash,
|
||||
visible: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 尺寸计算,无须定义常量
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- size calculation, no need to define constants
|
||||
distance: lineWidth / 2,
|
||||
};
|
||||
},
|
||||
@@ -337,7 +337,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
},
|
||||
encode: {
|
||||
update: {
|
||||
pickable: false, // vgrammer库的类型写的不严谨
|
||||
pickable: false, // The type of vgrammer library is not strictly written
|
||||
text: labelText ?? defaultLabelText,
|
||||
fill: (datum, element, params) => datum?.labelStyle.fill,
|
||||
},
|
||||
@@ -542,7 +542,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
[selectedKey],
|
||||
);
|
||||
|
||||
// 创建/更新view
|
||||
// Create/update view
|
||||
useLayoutEffect(() => {
|
||||
const initializeYScale = (view: IView) => {
|
||||
const yScale = view?.getScaleById('yScale');
|
||||
@@ -556,12 +556,12 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
|
||||
const registerEvent = (view: IView) => {
|
||||
const rectElm = view?.getMarkById('rect');
|
||||
// rect点击事件
|
||||
// Rect click event
|
||||
rectElm?.addEventListener('click', ((event, element) => {
|
||||
onClick?.(event, element);
|
||||
}) as InteractionEventHandler);
|
||||
|
||||
// rect hover高亮
|
||||
// Rect hover highlight
|
||||
view?.interaction('element-highlight', {
|
||||
selector: 'rect',
|
||||
highlightState: 'hover2',
|
||||
@@ -581,7 +581,7 @@ export const Flamethread: FC<FlamethreadProps> = props => {
|
||||
});
|
||||
}
|
||||
|
||||
// rect hover显示tooltip
|
||||
// Rect hover tooltip
|
||||
if (tooltip) {
|
||||
view?.interaction('tooltip', {
|
||||
selector: 'rect',
|
||||
|
||||
@@ -51,7 +51,7 @@ export interface RectNode {
|
||||
end: number;
|
||||
rectStyle?: RectStyle;
|
||||
labelStyle?: Pick<LabelStyle, 'fill'>;
|
||||
// 其他字段,会透传
|
||||
// Other fields will be passed through
|
||||
extra?: unknown;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ export const CommonEdge = (props: EdgeProps<EdgeData>) => {
|
||||
|
||||
const topologyEdgeStatus = getTopologyItemStatus(data?.tailDynamicSpanNode);
|
||||
|
||||
// vertical类型线段布局时,采用节点位置进行定位,从而使线段起点和终点定位在节点开始位置
|
||||
// When the vertical type line segment is laid out, the node position is used for positioning, so that the start and end points of the line segment are positioned at the beginning of the node
|
||||
const adaptedSourceX = isVerticalEdge(sourcePosition)
|
||||
? data?.layoutInfo?.customSourceX ?? sourceX
|
||||
: sourceX;
|
||||
|
||||
@@ -45,7 +45,7 @@ export const CommonNode = (props: NodeProps<NodeData>) => {
|
||||
data: { name, icon, layoutDirection, dynamicSpanNode },
|
||||
} = props;
|
||||
|
||||
// 特化逻辑:动态tracing中没有workflow_start节点,topo中workflow_start节点默认高亮
|
||||
// Specialization logic: There are no workflow_start nodes in dynamic tracing, workflow_start nodes in topo are highlighted by default
|
||||
const topologyNodeStatus =
|
||||
Number(type) === SpanCategory.WorkflowStart
|
||||
? TopologyEdgeStatus.DYNAMIC
|
||||
|
||||
@@ -111,11 +111,11 @@ export const useGenerateTopology = (
|
||||
|
||||
const [topologicalData, setTopologicalData] = useState<TopologicalData>();
|
||||
|
||||
// 静态topo接口原始数据及计算数据缓存
|
||||
// Static topo interface original data source and computing data cache
|
||||
const staticTopologyDataRef = useRef<Record<string, StaticTopologyDataCache>>(
|
||||
{},
|
||||
);
|
||||
// 某个span最近的上游可查询到topo的span节点缓存
|
||||
// The nearest upstream of a span can be queried to the span node cache of topo
|
||||
const nearestTopologyRootSpanMapRef = useRef<DynamicNodeMap>({});
|
||||
|
||||
const resetStatus = () => {
|
||||
@@ -136,7 +136,7 @@ export const useGenerateTopology = (
|
||||
const traceTree = buildTraceTree(spanData, false);
|
||||
return extractOriginDynamicNodeMap(traceTree, botId || entityId || '');
|
||||
} else {
|
||||
// TraceId类型暂不实现
|
||||
// The TraceId type is not yet implemented
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
@@ -151,7 +151,7 @@ export const useGenerateTopology = (
|
||||
setLoading(true);
|
||||
/**
|
||||
* Step 1
|
||||
* 获取动态tracing tree数据,找到当前选中节点
|
||||
* Get dynamic tracing tree data and find the currently selected node
|
||||
*/
|
||||
const originDynamicData = getOriginDynamicData();
|
||||
const currentSelectedSpanNode =
|
||||
@@ -163,7 +163,7 @@ export const useGenerateTopology = (
|
||||
}
|
||||
|
||||
const getNearestTopologyRootSpanNode = () => {
|
||||
// 命中缓存
|
||||
// hit cache
|
||||
if (nearestTopologyRootSpanMapRef.current[selectedSpanId]) {
|
||||
return nearestTopologyRootSpanMapRef.current[selectedSpanId];
|
||||
}
|
||||
@@ -178,7 +178,7 @@ export const useGenerateTopology = (
|
||||
|
||||
/**
|
||||
* Step 2
|
||||
* 从当前节点开始向上找到最近的可绘制topo的节点,并请求得到静态原始topo数据
|
||||
* Find the nearest drawable topo node starting from the current node and request static raw topo data
|
||||
*/
|
||||
const nearestTopologyRootSpanNode = getNearestTopologyRootSpanNode() as
|
||||
| CSpanAttrInvokeAgent
|
||||
@@ -193,7 +193,7 @@ export const useGenerateTopology = (
|
||||
|
||||
const { type } = nearestTopologyRootSpanNode;
|
||||
|
||||
// 当前支持InvokeAgent和Workflow类型,分别取bot和workflow的id以及version
|
||||
// Currently supports InvokeAgent and Workflow types, taking the id and version of bot and workflow respectively
|
||||
const getStaticTopologyMetaData = (): Partial<
|
||||
Pick<GetTopoInfoReq, 'resource_id' | 'version'>
|
||||
> => {
|
||||
@@ -237,7 +237,7 @@ export const useGenerateTopology = (
|
||||
] as StaticTopologyDataCache | undefined;
|
||||
|
||||
const topoInfo =
|
||||
// 优先从缓存读取
|
||||
// Read from cache first
|
||||
staticTopologyDataCache?.topoInfoMap ??
|
||||
(await fetchStaticTopologyData(processedGetTopoInfoReq));
|
||||
|
||||
@@ -248,16 +248,16 @@ export const useGenerateTopology = (
|
||||
|
||||
/**
|
||||
* Step 3
|
||||
* 过滤出当前静态topo节点中需要展示动态调用链路的节点
|
||||
* Filter out the nodes in the current static topo node that need to display the dynamic call link
|
||||
*/
|
||||
const topoMetaInfo =
|
||||
// 优先从缓存读取
|
||||
// Read from cache first
|
||||
staticTopologyDataCache?.topoMetaInfo ??
|
||||
generateTopologyMetaInfo(topoInfo);
|
||||
|
||||
const upstreamNodeMap = staticTopologyDataCache?.upstreamNodeMap ?? {};
|
||||
|
||||
// 判断当前节点自身是否有topo信息(即当前所需要展示的topo的根节点)
|
||||
// Determine whether the current node itself has topo information (that is, the root node of the topo that needs to be displayed at present)
|
||||
const isSelectedNodeTopologyRoot =
|
||||
currentSelectedSpanNode.id === nearestTopologyRootSpanNode.id;
|
||||
|
||||
@@ -266,8 +266,8 @@ export const useGenerateTopology = (
|
||||
getNodeResourceId(currentSelectedSpanNode, botId || entityId || '')
|
||||
];
|
||||
|
||||
// 如果当前节点为topo根节点,那么展示所有动态节点信息;
|
||||
// 否则,过滤出当前节点在静态topo中的所有上游节点,只对这些上游节点进行展示
|
||||
// If the current node is the topo root node, then all dynamic node information is displayed.
|
||||
// Otherwise, filter out all upstream nodes of the current node in the static topo and display only those upstream nodes
|
||||
const currentDynamicNodeMap = isSelectedNodeTopologyRoot
|
||||
? originDynamicData.dynamicNodeMap
|
||||
: filterObjectByKeys(
|
||||
@@ -288,7 +288,7 @@ export const useGenerateTopology = (
|
||||
|
||||
/**
|
||||
* Step 4
|
||||
* 补齐动态节点信息 & 布局信息到静态topo
|
||||
* Complement dynamic node information & layout information to static topo
|
||||
*/
|
||||
const originalTopologicalData = completeDynamicTopologyInfo(
|
||||
topoInfo,
|
||||
@@ -301,7 +301,7 @@ export const useGenerateTopology = (
|
||||
layoutDirection,
|
||||
);
|
||||
|
||||
// 存入缓存
|
||||
// cache
|
||||
staticTopologyDataRef.current[staticTopologyDataMapKey] = {
|
||||
topoInfoMap: topoInfo,
|
||||
topoMetaInfo,
|
||||
@@ -318,7 +318,7 @@ export const useGenerateTopology = (
|
||||
}, [botId, entityId, spaceId, dataSource, selectedSpanId]);
|
||||
|
||||
useEffect(
|
||||
// 销毁时清空缓存
|
||||
// Clear cache on destruction
|
||||
() => () => {
|
||||
staticTopologyDataRef.current = {
|
||||
topoInfoMap: {},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import ReactFlow, { ReactFlowProvider } from 'reactflow';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@@ -31,15 +31,15 @@ import s from './index.module.less';
|
||||
const TopologyFlowContent = (props: TopologyFlowProps) => {
|
||||
const { style, className, renderHeader, ...restProps } = props;
|
||||
|
||||
// 计算topo数据
|
||||
// Calculate topo data
|
||||
const [loading, topologicalData] = useGenerateTopology({
|
||||
...restProps,
|
||||
});
|
||||
|
||||
// 每次topo数据变更后,计算topo布局信息
|
||||
// After each topo data change, calculate the topo layout information
|
||||
const [topologyFlowDomRef] = useLayoutTopology(topologicalData);
|
||||
|
||||
// 渲染外部自定义header实现(带有业务语义)
|
||||
// Rendering external custom header implementations (with business semantics)
|
||||
const topologyHeader = useMemo(() => {
|
||||
if (!renderHeader || !topologicalData) {
|
||||
return null;
|
||||
@@ -65,7 +65,7 @@ const TopologyFlowContent = (props: TopologyFlowProps) => {
|
||||
{topologyHeader}
|
||||
<div className={s['topology-flow-container-flow']}>
|
||||
<ReactFlow
|
||||
// @ts-expect-error 使用number类型枚举SpanType作为自定义type,可忽略报错
|
||||
// @ts-expect-error Use the number type to enumerate SpanType as a custom type, the error can be ignored
|
||||
nodes={topologicalData.nodes}
|
||||
edges={topologicalData.edges}
|
||||
nodeTypes={CUSTOM_NODES}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import { type Node, type Edge } from 'reactflow';
|
||||
import { type CSSProperties } from 'react';
|
||||
|
||||
@@ -52,7 +52,7 @@ export interface UseGenerateTopologyHookData {
|
||||
selectedSpanId?: string;
|
||||
}
|
||||
|
||||
// @ts-expect-error 使用number类型枚举SpanType作为自定义type,可忽略报错
|
||||
// @ts-expect-error Use the number type to enumerate SpanType as a custom type, the error can be ignored
|
||||
export type TopologicalNode = Node<NodeData, SpanCategory>;
|
||||
|
||||
export type TopologicalEdge = Edge<EdgeData>;
|
||||
|
||||
@@ -92,10 +92,10 @@ export const filterObjectByKeys = <T extends object>(
|
||||
}, {} as T);
|
||||
|
||||
/**
|
||||
* 静态topo数据中resource_id根据span类型的不同和不同的字段映射
|
||||
* @param spanNode 当前span
|
||||
* Static topo data resource_id different and different data field mapping according to span type
|
||||
* @param spanNode current span
|
||||
* @param entityId
|
||||
* @returns 与静态topo所映射的id
|
||||
* @Returns the id mapped to the static topo
|
||||
*/
|
||||
export const getNodeResourceId = (
|
||||
spanNode: SpanNode,
|
||||
@@ -103,7 +103,7 @@ export const getNodeResourceId = (
|
||||
): string => {
|
||||
const { category, id } = spanNode;
|
||||
const workflowNodeId =
|
||||
// 兼容合并后的Batch节点
|
||||
// Compatible with merged Batch Nodes
|
||||
(spanNode as CSPanBatch).workflow_node_id ??
|
||||
(spanNode as CSpanAttrCondition).extra?.workflow_node_id;
|
||||
if (workflowNodeId) {
|
||||
@@ -133,7 +133,7 @@ export const generateStaticTopologyDataMapKey = (
|
||||
return `${space_id}-${resource_id}-${version}-${env}-${resource_type}`;
|
||||
};
|
||||
|
||||
// 单 agent 下 trace 里将去除 SpanType.InvokeAgent 节点,topology root 可能为 SpanType.UserInput
|
||||
// The SpanType. InvokeAgent node will be removed from the trace under a single agent, and the topology root may be SpanType. UserInput.
|
||||
export const getTopologyAgentRootType = () => {
|
||||
const FLAGS = getFlags();
|
||||
return FLAGS['bot.devops.use_user_input_as_agent']
|
||||
@@ -142,7 +142,7 @@ export const getTopologyAgentRootType = () => {
|
||||
};
|
||||
|
||||
export const isTopologyRootSpan = (span: CSpan) => {
|
||||
// 只有基础工作流展示拓扑
|
||||
// Only the base workflow shows the topology
|
||||
if (span.type === SpanType.Workflow) {
|
||||
return (span as CSpanAttrWorkflow).extra?.workflow_schema_type === 1;
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export const extractOriginDynamicNodeMap = (
|
||||
|
||||
while (queue.length > 0) {
|
||||
const currentNode = queue.shift() as SpanNode;
|
||||
// 过滤掉broken节点
|
||||
// Filter out broken nodes
|
||||
if (currentNode.id === rootBreakSpanId) {
|
||||
continue;
|
||||
}
|
||||
@@ -204,9 +204,9 @@ function findUserInputRootNode(dynamicNodeMap: DynamicNodeMap) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将动态节点填充给对应的静态节点和连线,补全其动态运行信息
|
||||
* Fill the dynamic nodes to the corresponding static nodes and connections to complete their dynamic operation information
|
||||
* @param staticTopoInfo
|
||||
* @param dynamicNodeMap 当前所需要展示的动态节点map
|
||||
* @Param dynamicNodeMap The dynamic node map that needs to be displayed at present
|
||||
* @param layoutDirection
|
||||
* @returns
|
||||
*/
|
||||
@@ -247,7 +247,7 @@ export const completeDynamicTopologyInfo = (
|
||||
|
||||
let dynamicSpanNode = dynamicNodeMap[resource_id] as SpanNode | undefined;
|
||||
|
||||
// 特化逻辑:单agent场景下节点信息将不包含agent节点,需要使用userInput替换
|
||||
// Specialized logic: In a single agent scenario, the node information will not include the agent node, and it needs to be replaced with userInput.
|
||||
if (
|
||||
typedResourceKind === SpanCategory.Agent &&
|
||||
!dynamicSpanNode &&
|
||||
@@ -287,7 +287,7 @@ export const completeDynamicTopologyInfo = (
|
||||
const { edge_id = '', source_node_id = '', target_node_id = '' } = item;
|
||||
|
||||
const sourceNodeInfo = nodeInfoMap[source_node_id];
|
||||
// 特化逻辑:动态tracing中没有workflow_start节点,默认将其下游的动态节点填入
|
||||
// Specialized logic: there is no workflow_start node in dynamic tracing, the dynamic node downstream is filled in by default
|
||||
const isWorkflowStartNode =
|
||||
Number(sourceNodeInfo?.node?.resource_kind) ===
|
||||
SpanCategory.WorkflowStart;
|
||||
@@ -297,7 +297,7 @@ export const completeDynamicTopologyInfo = (
|
||||
const targetNode: SpanNode | undefined =
|
||||
dynamicNodeMap[nodeInfoMap[target_node_id]?.resourceId];
|
||||
|
||||
// 特化逻辑:单agent场景下节点信息将不包含agent节点,需要使用userInput替换
|
||||
// Specialized logic: In a single agent scenario, the node information will not include the agent node, and it needs to be replaced with userInput.
|
||||
if (
|
||||
sourceNodeInfo.node.resource_kind === SpanCategory.Agent &&
|
||||
!sourceNode &&
|
||||
@@ -364,7 +364,7 @@ const getStaticSpanTitle = (category: SpanCategory, name: string) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 进行对原始topo数据的布局和样式处理
|
||||
* Layout and style processing of raw topo data
|
||||
* @param originTopologicalData
|
||||
* @param layoutDirection
|
||||
* @returns
|
||||
@@ -399,7 +399,7 @@ export const getLayoutedMeta = (
|
||||
|
||||
Dagre.layout(graphInstance);
|
||||
|
||||
// 采集节点的定位信息,用于vertical类型的连线绘制时进行定位
|
||||
// Acquire the positioning information of the node for positioning when drawing vertical types of connections
|
||||
const nodeXAxisMap: Record<string, number> = {};
|
||||
|
||||
const layoutNodes: TopologicalNode[] = nodes.map(node => {
|
||||
@@ -444,9 +444,9 @@ export const getTopologyItemStatus = (spanNode?: SpanNode) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用静态topo数据生成记录每个节点的上游节点的graph以及id map
|
||||
* Generate graphs and id maps of upstream nodes for each node using static topo data
|
||||
* @param topoInfo
|
||||
* @returns graph和map的meta信息
|
||||
* @Returns meta information for graphs and maps
|
||||
*/
|
||||
export const generateTopologyMetaInfo = (topoInfo: TopoInfo): TopoMetaInfo => {
|
||||
const { nodes = [], edges = [] } = topoInfo;
|
||||
@@ -471,11 +471,11 @@ export const generateTopologyMetaInfo = (topoInfo: TopoInfo): TopoMetaInfo => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询静态topo图中某个节点的所有上游节点,使用DP降低复杂度
|
||||
* @param selectedNodeId 当前span id
|
||||
* @param topoGraph 存有记录每个节点的上游节点的graph
|
||||
* @param upstreamNodeMap 存有记录某个节点所有上游节点的map
|
||||
* @returns 上游所有节点的id list
|
||||
* Query all upstream nodes of a node in a static topo graph, using DP to reduce complexity
|
||||
* @param selectedNodeId Current span id
|
||||
* @Param topoGraph holds a graph that records the upstream nodes of each node
|
||||
* @Param upstreamNodeMap holds a map that records all upstream nodes of a node
|
||||
* @Returns the id list of all nodes upstream
|
||||
*/
|
||||
export const getAllUpstreamTopologyNodeIds = (
|
||||
selectedNodeId: string,
|
||||
|
||||
@@ -56,7 +56,7 @@ const TraceFlamethread: FC<TraceFlamethreadProps> = props => {
|
||||
onClick,
|
||||
} = props;
|
||||
|
||||
// 初始化flamethreadData
|
||||
// Initialize flamethreadData
|
||||
useEffect(() => {
|
||||
if (dataType === DataSourceTypeEnum.SpanData && spanData) {
|
||||
if (spanData?.length === 0 && flamethreadData.length === 0) {
|
||||
|
||||
@@ -68,18 +68,18 @@ const genRectNode = (info: {
|
||||
};
|
||||
|
||||
export const spanData2flamethreadData = (spanData: CSpan[]): RectNode[] => {
|
||||
// 1. 根据spans,组装call trees
|
||||
// 1. According to spans, assemble call trees
|
||||
const callTrees = buildCallTrees(spanData);
|
||||
|
||||
// 2. 生成tartSpan
|
||||
// 2. Generate tartSpan
|
||||
const startSpan: SpanNode = getRootSpan(callTrees, false);
|
||||
|
||||
// 3. 获取 break节点(非start的根节点都是breakSpan)
|
||||
// 3. Get the break node (all non-start root nodes are breakSpan)
|
||||
const breakSpans: SpanNode[] = getBreakSpans(callTrees, false);
|
||||
|
||||
let rstSpans: SpanNode[] = [];
|
||||
|
||||
// 前序搜索,确保父节点在前
|
||||
// Preorder search to ensure that the parent node is in front
|
||||
const walk = (spans: SpanNode[]) => {
|
||||
rstSpans = rstSpans.concat(spans);
|
||||
spans.forEach(span => {
|
||||
@@ -93,13 +93,13 @@ export const spanData2flamethreadData = (spanData: CSpan[]): RectNode[] => {
|
||||
}
|
||||
walk(breakSpans);
|
||||
|
||||
// 过滤掉不显示的span节点
|
||||
// Filter out undisplayed span nodes
|
||||
rstSpans = rstSpans.filter(span => isVisibleSpan(span));
|
||||
|
||||
// 按start_time稳定排序
|
||||
// Sort by start_time
|
||||
const sortedSpans = sortBy(rstSpans, o => o.start_time);
|
||||
|
||||
// 添加跟节点
|
||||
// Add follow node
|
||||
sortedSpans.unshift(startSpan);
|
||||
|
||||
const rectNodes: RectNode[] = [];
|
||||
|
||||
@@ -90,7 +90,7 @@ const TraceTree: FC<TraceTreeProps> = props => {
|
||||
newWindow: true,
|
||||
});
|
||||
};
|
||||
// 初始化flamethreadData
|
||||
// Initialize flamethreadData
|
||||
useEffect(() => {
|
||||
if (dataType === DataSourceTypeEnum.SpanData && spanData) {
|
||||
if (spanData?.length === 0 && treeData === undefined) {
|
||||
|
||||
@@ -38,8 +38,8 @@ export type TraceTreeProps = {
|
||||
|
||||
export interface SpanDetail {
|
||||
isCozeWorkflowNode: boolean;
|
||||
workflowLevel: number; // workflow 层级
|
||||
workflowVersion?: string; // 父节点透传给子节点
|
||||
workflowLevel: number; // Workflow Hierarchy
|
||||
workflowVersion?: string; // Parent node passes through to sub-node
|
||||
}
|
||||
|
||||
export interface WorkflowJumpParams {
|
||||
|
||||
@@ -99,7 +99,7 @@ const genTitleRender = ({
|
||||
}
|
||||
const config = spanCategoryConfig[category];
|
||||
|
||||
// 虚拟的break的根节点
|
||||
// Virtual broken root node
|
||||
if (span.id === rootBreakSpanId) {
|
||||
return (
|
||||
<div
|
||||
@@ -181,7 +181,7 @@ export const spanData2treeData = (
|
||||
span,
|
||||
},
|
||||
};
|
||||
// breakSpan节点
|
||||
// breakSpan node
|
||||
if (span.id === rootBreakSpanId) {
|
||||
treeNode = {
|
||||
...treeNode,
|
||||
@@ -202,7 +202,7 @@ export const getSpanInfoMap = (root: SpanNode) => {
|
||||
const spanInfoMap: Record<string, SpanDetail | undefined> = {};
|
||||
|
||||
const bfs = (node: SpanNode) => {
|
||||
// coze workflow 设置 isCozeWorkflowNode
|
||||
// Coze workflow settings isCozeWorkflowNode
|
||||
if (
|
||||
node.type === SpanType.Workflow &&
|
||||
getSpanProp(node, 'workflow_schema_type') === 1
|
||||
@@ -218,7 +218,7 @@ export const getSpanInfoMap = (root: SpanNode) => {
|
||||
: undefined,
|
||||
};
|
||||
} else {
|
||||
// coze workflow 的子节点设置 isCozeWorkflowNode
|
||||
// Coze workflow sub-node settings isCozeWorkflowNode
|
||||
const { isCozeWorkflowNode, workflowLevel = 0 } =
|
||||
spanInfoMap[node.parent_id] || {};
|
||||
|
||||
@@ -234,7 +234,7 @@ export const getSpanInfoMap = (root: SpanNode) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 递归
|
||||
// recursion
|
||||
for (const childNode of node.children || []) {
|
||||
bfs(childNode);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-magic-numbers -- 本组件中会有很多位置计算的数字,无须处理*/
|
||||
/* eslint-disable @typescript-eslint/no-magic-numbers -- there will be many numbers calculated in this component, no need to deal with them*/
|
||||
import { type FC, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { isFunction, mergeWith } from 'lodash-es';
|
||||
@@ -72,8 +72,8 @@ const Tree: FC<TreeProps> = ({
|
||||
);
|
||||
|
||||
/**
|
||||
* 使得指定的selectKey的Line置于顶层。
|
||||
* 通过调整line顺序,来实现z-index效果:key为${selectKey}的line在最上层
|
||||
* Causes the Line of the specified selectKey to be placed at the top level.
|
||||
* By adjusting the line order, the z-index effect is achieved: the line with the key ${selectKey} is at the top
|
||||
*/
|
||||
const adjustLineOrder = useCallback(
|
||||
(lines: Line[]): Line[] => {
|
||||
@@ -90,7 +90,7 @@ const Tree: FC<TreeProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// 支持根据zIndex控制高度
|
||||
// Support controlling height according to zIndex
|
||||
lines.sort((lineA, lineB) => {
|
||||
const zIndexA = lineA.endNode.zIndex ?? -1;
|
||||
const zIndexB = lineB.endNode.zIndex ?? -1;
|
||||
@@ -141,7 +141,7 @@ const Tree: FC<TreeProps> = ({
|
||||
);
|
||||
|
||||
/**
|
||||
* 根据line信息生成svg path。 colNo, rowNum都从0开始
|
||||
* Generate svg path from line information. colNo, rowNum all start from 0
|
||||
*/
|
||||
const genSvgPath = useCallback(
|
||||
(line: Line): string => {
|
||||
@@ -155,41 +155,41 @@ const Tree: FC<TreeProps> = ({
|
||||
const { lineRadius = 0, lineGap = 0 } = normalLineStyle;
|
||||
const nodeHeight = nodeBoxHeight + verticalInterval;
|
||||
|
||||
// 起始点
|
||||
// starting point
|
||||
const startX = startColNo * indent + offsetX;
|
||||
const startY =
|
||||
startRowNo * nodeHeight + (nodeBoxHeight + verticalInterval / 2);
|
||||
|
||||
if (startColNo === endColNo) {
|
||||
// 竖线的长度
|
||||
// The length of the vertical line
|
||||
const lineASize =
|
||||
(endRowNo - startRowNo - 1) * nodeHeight + verticalInterval;
|
||||
// 移动到起始点
|
||||
// Move to the starting point
|
||||
const moveToStartPoint = `M ${startX} ${startY + lineGap}`;
|
||||
// 竖线
|
||||
// vertical line
|
||||
const lineA = `L ${startX} ${startY + lineASize}`;
|
||||
return `${moveToStartPoint} ${lineA}`;
|
||||
} else {
|
||||
// 竖线的长度
|
||||
// The length of the vertical line
|
||||
const lineASize =
|
||||
(endRowNo - startRowNo - 1) * nodeHeight +
|
||||
verticalInterval / 2 +
|
||||
nodeHeight / 2 -
|
||||
lineRadius;
|
||||
// 横线的长度
|
||||
// The length of the horizontal line
|
||||
const lineBSize =
|
||||
(endColNo - startColNo) * indent - offsetX - lineRadius;
|
||||
// 结束点的坐标
|
||||
// Coordinates of the end point
|
||||
const endX = startX + lineBSize + lineRadius;
|
||||
const endY = startY + lineASize + lineRadius;
|
||||
|
||||
// 移动到起始点
|
||||
// Move to the starting point
|
||||
const moveToStartPoint = `M ${startX} ${startY + lineGap}`;
|
||||
// 竖线
|
||||
// vertical line
|
||||
const lineA = `L ${startX} ${startY + lineASize}`;
|
||||
// 二次贝塞尔曲线
|
||||
// Quadratic Bézier Curve
|
||||
const qbc = `Q ${startX} ${endY} ${startX + lineRadius} ${endY}`;
|
||||
// 横线
|
||||
// horizontal line
|
||||
const lineB = `L ${endX - lineGap} ${endY}`;
|
||||
return `${moveToStartPoint} ${lineA} ${qbc} ${lineB}`;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ export type LineAttrs = Pick<
|
||||
SVGAttributes<unknown>,
|
||||
'stroke' | 'strokeDasharray' | 'strokeWidth'
|
||||
> & {
|
||||
lineRadius?: number; // line圆角半径 注意:这个数值不要大于 indent/2
|
||||
lineGap?: number; // line距离box的gap
|
||||
lineRadius?: number; // Line fillet radius, note: this value should not be greater than indent/2
|
||||
lineGap?: number; // Line distance box gap
|
||||
};
|
||||
|
||||
export interface LineStyle {
|
||||
@@ -34,13 +34,13 @@ export interface LineStyle {
|
||||
export interface TreeNode {
|
||||
key: string;
|
||||
title: ReactNode | ((nodeData: TreeNodeExtra) => ReactNode);
|
||||
selectEnabled?: boolean; // 默认值 true
|
||||
indentDisabled?: boolean; // 关闭缩进。 仅针对如下场景生效:子节点中的最后一个节点
|
||||
lineStyle?: LineStyle; // 当指定了此属性时,会覆盖全局的lineStyle
|
||||
selectEnabled?: boolean; // Default value true
|
||||
indentDisabled?: boolean; // Turn off indentation. Only works for the following scenarios: the last node in the sub-node
|
||||
lineStyle?: LineStyle; // When this property is specified, the global lineStyle is overridden.
|
||||
children?: TreeNode[];
|
||||
linePath?: PathEnum[];
|
||||
zIndex?: number;
|
||||
// 其他字段,会透传
|
||||
// Other fields will be passed through
|
||||
extra?: unknown;
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ export enum PathEnum {
|
||||
export type TreeNodeExtra = Omit<TreeNode, 'children'> & {
|
||||
colNo: number;
|
||||
rowNo: number;
|
||||
unindented: boolean; // 相对于父节点,是否未缩进
|
||||
selected: boolean; // 是否被选中
|
||||
hover: boolean; // 是否hover
|
||||
unindented: boolean; // Is it unindented relative to the parent node?
|
||||
selected: boolean; // Is it selected?
|
||||
hover: boolean; // Whether to hover
|
||||
};
|
||||
|
||||
// 拉平后的TreeNode信息
|
||||
// Flattened TreeNode information
|
||||
export type TreeNodeFlatten = Omit<TreeNodeExtra, 'selected' | 'hover'>;
|
||||
|
||||
export interface Line {
|
||||
@@ -67,9 +67,9 @@ export interface Line {
|
||||
}
|
||||
|
||||
export interface GlobalStyle {
|
||||
indent?: number; // 父节点和子节点的缩进距离
|
||||
verticalInterval?: number; // node节点的垂直间距
|
||||
nodeBoxHeight?: number; // node-box节点的高度
|
||||
indent?: number; // Indent distance of parent and child nodes
|
||||
verticalInterval?: number; // Vertical spacing of nodes
|
||||
nodeBoxHeight?: number; // The height of the node-box node
|
||||
offsetX?: number;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export interface TreeProps {
|
||||
selectedKey?: string;
|
||||
hoverKey?: string;
|
||||
disableDefaultHover?: boolean;
|
||||
indentDisabled?: boolean; // 关闭缩进。 仅针对如下场景生效:最后一个节点
|
||||
indentDisabled?: boolean; // Turn off indentation. Only works for the following scenarios: the last node
|
||||
lineStyle?: LineStyle;
|
||||
globalStyle?: GlobalStyle;
|
||||
className?: string;
|
||||
|
||||
@@ -19,14 +19,14 @@ import { omit } from 'lodash-es';
|
||||
import { type TreeNodeFlatten, type TreeNode, type Line } from './typing';
|
||||
|
||||
/**
|
||||
* 基于TreeData生成:
|
||||
* Generated based on TreeData:
|
||||
*
|
||||
* @param treeData tree原始数据
|
||||
* @param options.indentDisabled 是否取消缩进。仅针对下述场景有效:异常节点+最后一个节点
|
||||
* @param treeData tree original data source
|
||||
* @Param options.indentDisabled Whether to unindent. Valid only for the following scenarios: exception node + last node
|
||||
*
|
||||
* @returns
|
||||
* 1. nodes, 拉平后的node节点信息
|
||||
* 2. lines, 用于将node进行连接
|
||||
* 1. nodes, node information after leveling
|
||||
* 2. lines, used to connect nodes
|
||||
*/
|
||||
export const flattenTreeData = (
|
||||
treeData: TreeNode,
|
||||
@@ -45,7 +45,7 @@ export const flattenTreeData = (
|
||||
...omit(node, ['children']),
|
||||
colNo: nodeColNo,
|
||||
rowNo: nodes.length,
|
||||
unindented: fatherNodeFlatten?.colNo === nodeColNo, // 未缩进
|
||||
unindented: fatherNodeFlatten?.colNo === nodeColNo, // Unindented
|
||||
};
|
||||
nodes.push(nodeFlatten);
|
||||
if (fatherNodeFlatten !== undefined) {
|
||||
@@ -59,7 +59,7 @@ export const flattenTreeData = (
|
||||
const childNodes = node.children;
|
||||
|
||||
childNodes.forEach((childNode, index) => {
|
||||
// 取消缩进。 生效场景:异常节点+最后一个节点
|
||||
// Cancel indentation. Effective scene: exception node + last node
|
||||
const indentDisabled =
|
||||
childNode.indentDisabled ?? options.indentDisabled;
|
||||
if (indentDisabled && childNodes.length - 1 === index) {
|
||||
|
||||
@@ -120,7 +120,7 @@ export const spanTypeConfigMap: SpanTypeConfigMap = {
|
||||
label: I18n.t('analytic_query_subtype_value_knowledge'),
|
||||
},
|
||||
[SpanType.Chain]: {},
|
||||
// 特定业务
|
||||
// specific business
|
||||
[SpanType.Hook]: {
|
||||
label: I18n.t('analytics_query_invoke', {
|
||||
name: 'Hook',
|
||||
|
||||
@@ -40,13 +40,13 @@ import {
|
||||
type CSpanAttrUserInput,
|
||||
} from '../typings/cspan';
|
||||
|
||||
// 对根节点追加traceAdvanceInfo信息
|
||||
// Append traceAdvanceInfo to the root node
|
||||
const appendTraceAdvanceInfo = (
|
||||
spans: CSpan[],
|
||||
traceAdvanceInfo?: Omit<TraceAdvanceInfo, 'trace_id'>,
|
||||
): CSpan[] =>
|
||||
spans.map(span => {
|
||||
// 修改根节点的状态。 根节点的tokens和status以服务端获取的为准
|
||||
// Modify the state of the root node. The tokens and status of the root node are subject to the server level
|
||||
if (
|
||||
span.type === SpanType.UserInput ||
|
||||
span.type === SpanType.UserInputV2
|
||||
@@ -100,7 +100,7 @@ const appendSpans = (spans: CSpan[], callTrees: SpanNode[]) => {
|
||||
span.type === SpanType.UserInput ||
|
||||
span.type === SpanType.UserInputV2
|
||||
) {
|
||||
// 根节点input_tokens_sum和output_tokens_sum的数值以服务端获取为准,不做计算
|
||||
// The values input_tokens_sum and output_tokens_sum of the root node are subject to server level acquisition and are not calculated
|
||||
return span;
|
||||
} else {
|
||||
return {
|
||||
@@ -112,7 +112,7 @@ const appendSpans = (spans: CSpan[], callTrees: SpanNode[]) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 获取CSpan节点的tokens信息
|
||||
// Get the tokens information of the CSpan node
|
||||
const getCSpanTokens = (
|
||||
span: CSpan,
|
||||
): {
|
||||
@@ -138,7 +138,7 @@ const getCSpanTokens = (
|
||||
output_tokens: outputTokensRst,
|
||||
};
|
||||
} else {
|
||||
// SingleSpan节点
|
||||
// SingleSpan Node
|
||||
return {
|
||||
input_tokens: (getSpanProp(span, 'input_tokens') as number) ?? 0,
|
||||
output_tokens: (getSpanProp(span, 'output_tokens') as number) ?? 0,
|
||||
@@ -146,7 +146,7 @@ const getCSpanTokens = (
|
||||
}
|
||||
};
|
||||
|
||||
// 追加invokeAgentInfo的dialog_round和model字段
|
||||
// Append invokeAgentInfo's dialog_round and model fields
|
||||
const appendRootSpan = (info: { rootSpan: CSpan; spans: CSpan[] }): CTrace => {
|
||||
const { rootSpan, spans } = info;
|
||||
const rstSpan: CTrace = rootSpan;
|
||||
@@ -181,14 +181,14 @@ interface UseSpanTransformReturn {
|
||||
spans: CSpan[];
|
||||
}
|
||||
|
||||
// start节点不存在时,生成虚拟start节点
|
||||
// When the start node does not exist, generate a virtual start node
|
||||
export const appendVirtualStart = (spans: CSpan[]): CSpan[] => {
|
||||
const startSpans = spans.filter(rootSpan => {
|
||||
const { category } = rootSpan;
|
||||
return category === SpanCategory.Start;
|
||||
});
|
||||
|
||||
// 生成虚拟span
|
||||
// Generate virtual spans
|
||||
if (startSpans.length > 0) {
|
||||
return spans;
|
||||
} else {
|
||||
@@ -204,27 +204,27 @@ export const useSpanTransform = (
|
||||
|
||||
const rst = useMemo(() => {
|
||||
let spans = spans2CSpans(orgSpans, spanCategoryMeta);
|
||||
// 追加虚拟span
|
||||
// Append virtual span
|
||||
spans = appendVirtualStart(spans);
|
||||
|
||||
// 追加traceAdvanceInfo信息
|
||||
// append traceAdvanceInfo
|
||||
spans = appendTraceAdvanceInfo(spans, traceAdvanceInfo);
|
||||
|
||||
// 根据spans,组装call trees
|
||||
// According to spans, assembling called trees
|
||||
let callTrees = buildCallTrees(spans);
|
||||
// 根节点超过 1 个,需要按 message id 过滤
|
||||
// If there is more than 1 root node, it needs to be filtered by message id.
|
||||
if (callTrees.length > 1 && messageId) {
|
||||
callTrees = callTrees.filter(
|
||||
root =>
|
||||
!('extra' in root) ||
|
||||
(root.extra && !('message_id' in root.extra)) ||
|
||||
// 存在 message_id 的情况下,过滤 id 匹配的节点
|
||||
// In the presence of message_id, filter the nodes with matching IDs
|
||||
root.extra?.message_id === messageId,
|
||||
);
|
||||
}
|
||||
const rootSpan = getRootSpan(callTrees, false);
|
||||
|
||||
// rootSpan的根节点调整: 追加invokeAgent信息
|
||||
// Root node adjustment of rootSpan: add invokeAgent information
|
||||
const rootSpanRst = appendRootSpan({
|
||||
rootSpan,
|
||||
spans,
|
||||
@@ -237,10 +237,10 @@ export const useSpanTransform = (
|
||||
return root.children?.some(subRoot => visit(targetId, subRoot)) ?? false;
|
||||
};
|
||||
|
||||
// 过滤掉不在rootSpan中的节点
|
||||
// Filter out nodes not in rootSpan
|
||||
spans = spans.filter(span => visit(span.id, rootSpan));
|
||||
|
||||
// 对spans节点进行调整: spans中workflow节点tokens累加计算
|
||||
// Adjust the spans node: the accumulation calculation of tokens in the workflow node in spans
|
||||
const spansRst = appendSpans(spans, callTrees);
|
||||
return {
|
||||
rootSpan: rootSpanRst,
|
||||
|
||||
@@ -23,13 +23,13 @@ export {
|
||||
} from './components/flamethread';
|
||||
export { default as Tree, type MouseEventParams } from './components/tree';
|
||||
export { useSpanTransform } from './hooks/use-span-transform';
|
||||
// Tree和Flamethread的参数类型
|
||||
// Parameter types for Tree and FlamethRead
|
||||
export { DataSourceTypeEnum } from './typings/graph';
|
||||
|
||||
export {
|
||||
// useSpanTransform相关类型
|
||||
// useSpanTransform related types
|
||||
type SpanCategoryMeta,
|
||||
// useSpanTransform 生成的定制span
|
||||
// useSpanTransform generated custom spans
|
||||
type CSpan,
|
||||
type CTrace,
|
||||
type CSpanSingle,
|
||||
|
||||
@@ -62,11 +62,11 @@ type CSpanCommonProp = Pick<
|
||||
Span,
|
||||
'trace_id' | 'id' | 'parent_id' | 'name' | 'type' | 'status'
|
||||
> & {
|
||||
start_time: number; // 默认为Int64,用起来不方便
|
||||
latency: number; // 默认为Int64,用起来不方便
|
||||
category?: SpanCategory; // 加载Meta失败时才为空
|
||||
input_tokens_sum?: number; // 扩展字段,用于存储子节点的input_tokens之和
|
||||
output_tokens_sum?: number; // 扩展字段,用于存储子节点的output_tokens之和
|
||||
start_time: number; // The default is Int64, which is inconvenient to use
|
||||
latency: number; // The default is Int64, which is inconvenient to use
|
||||
category?: SpanCategory; // Empty only when Meta fails to load
|
||||
input_tokens_sum?: number; // Extended field to store the sum of sub-node input_tokens
|
||||
output_tokens_sum?: number; // Extended field to store the sum of sub-node output_tokens
|
||||
};
|
||||
|
||||
type GenCSpan<T> = CSpanCommonProp & {
|
||||
|
||||
@@ -22,8 +22,8 @@ export enum DataSourceTypeEnum {
|
||||
}
|
||||
|
||||
export interface DataSource {
|
||||
// 取值为traceId时,组件会根据traceId查询SpanData
|
||||
// When the value is traceId, the component queries SpanData based on traceId.
|
||||
type: DataSourceTypeEnum;
|
||||
spanData?: CSpan[]; // type为spanData时,特有字段
|
||||
traceId?: string; // type为traceId时,特有字段
|
||||
spanData?: CSpan[]; // When type is spanData, unique fields
|
||||
traceId?: string; // When type is traceId, unique field
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export type SpanNode = CSpan & {
|
||||
|
||||
export const getSpanDataByTraceId = (traceId: string): CSpan[] => [];
|
||||
|
||||
// 获取tree的跟节点
|
||||
// Get the heel node of the tree
|
||||
export const buildCallTrees = (
|
||||
spans: CSpan[],
|
||||
splitBatchSpan = true,
|
||||
@@ -49,7 +49,7 @@ export const buildCallTrees = (
|
||||
|
||||
spans.forEach(span => {
|
||||
const curSpan = { ...span, children: [] };
|
||||
// Batch节点
|
||||
// Batch Node
|
||||
if ('spans' in span && splitBatchSpan) {
|
||||
span.spans.forEach(subSpan => {
|
||||
map[subSpan.id] = curSpan;
|
||||
@@ -88,7 +88,7 @@ export const getRootSpan = (spans: SpanNode[], needBuildTrees = true) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 无start的场景: 虚拟一个startSpan(供多方使用,火焰图,树状图,详情图,以确保一致);多个startSpans,则取第一个
|
||||
// Scenarios without start: virtual one StartSpan (for multiple parties to use, flame map, tree map, detail map to ensure consistency); multiple StartSpans, take the first one
|
||||
return startSpans.length > 0 ? startSpans[0] : genVirtualStart(rootSpans);
|
||||
};
|
||||
|
||||
@@ -150,7 +150,7 @@ export const getStatusLabel = (
|
||||
return spanStatusConfigMap[status]?.label ?? '';
|
||||
};
|
||||
|
||||
// start节点不存在时,生成虚拟start节点
|
||||
// When the start node does not exist, generate a virtual start node
|
||||
const getRootBreakSpan = (breakSpans: SpanNode[]): SpanNode => ({
|
||||
id: rootBreakSpanId,
|
||||
parent_id: '',
|
||||
@@ -163,7 +163,7 @@ const getRootBreakSpan = (breakSpans: SpanNode[]): SpanNode => ({
|
||||
children: breakSpans,
|
||||
});
|
||||
|
||||
// 根据switchAgent/restartAgent建立父子关系
|
||||
// Create a parent-child relationship according to switchAgent/reStartAgent
|
||||
const handleAgent = (spans: SpanNode[]): SpanNode[] => {
|
||||
const getAgent = (startAt: number, agents: SpanNode[]) => {
|
||||
const len = agents.length;
|
||||
@@ -213,7 +213,7 @@ const handleAgent = (spans: SpanNode[]): SpanNode[] => {
|
||||
return [...rstSpans, ...agentSpans];
|
||||
};
|
||||
|
||||
// 只有特殊的节点类型可以作为根节点
|
||||
// Only special node types can be used as root nodes
|
||||
const isTreeRootSpanType = (type: SpanType) =>
|
||||
[
|
||||
SpanType.InvokeAgent,
|
||||
@@ -222,7 +222,7 @@ const isTreeRootSpanType = (type: SpanType) =>
|
||||
SpanType.LLMCall,
|
||||
SpanType.WorkflowLLMCall,
|
||||
SpanType.WorkflowLLMBatchCall,
|
||||
// BlockWise的都放在这里
|
||||
// All BlockWise's are put here
|
||||
SpanType.BWStart,
|
||||
SpanType.BWEnd,
|
||||
SpanType.BWBatch,
|
||||
@@ -234,11 +234,11 @@ const isTreeRootSpanType = (type: SpanType) =>
|
||||
SpanType.BWVariable,
|
||||
SpanType.BWCallFlow,
|
||||
SpanType.BWConnector,
|
||||
// 新增类型都支持层级
|
||||
// New types all support hierarchies
|
||||
SpanType.Hook,
|
||||
].includes(type);
|
||||
|
||||
// 依据调用树,构建TraceTree
|
||||
// Build TraceTree based on call tree
|
||||
const callTree2TraceTree = (rootSpan: SpanNode): SpanNode => {
|
||||
const rstSpans: SpanNode[] = [];
|
||||
const walk = (span: SpanNode) => {
|
||||
@@ -248,10 +248,10 @@ const callTree2TraceTree = (rootSpan: SpanNode): SpanNode => {
|
||||
rstSpans.push(callTree2TraceTree(subSpan));
|
||||
} else {
|
||||
if (isVisibleSpan(subSpan)) {
|
||||
// 当前节点加入到 rootSpan.children
|
||||
// The current node is added to rootSpan.children
|
||||
rstSpans.push(omit(subSpan, 'children'));
|
||||
}
|
||||
// 递归子节点(当前节点)。 注意:隐藏的节点类型,也要递归的。 当前节点隐藏,其子节点有可能是显示的
|
||||
// Recursive sub-node (current node). Note: The type of hidden node should also be recursive. The current node is hidden, and its sub-node may be displayed
|
||||
walk(subSpan);
|
||||
}
|
||||
});
|
||||
@@ -265,25 +265,25 @@ const callTree2TraceTree = (rootSpan: SpanNode): SpanNode => {
|
||||
};
|
||||
|
||||
export const buildTraceTree = (spans: SpanNode[], splitBatchSpan?: boolean) => {
|
||||
// 1. 根据spans,组装call trees
|
||||
// 1. According to spans, assemble call trees
|
||||
const callTrees = buildCallTrees(spans, splitBatchSpan);
|
||||
|
||||
// 2. 生成startSpan
|
||||
// 2. Generate the gastSpan
|
||||
const startSpan: SpanNode = getRootSpan(callTrees, false);
|
||||
|
||||
// 3. 获取 break节点(非start的根节点都是breakSpan)
|
||||
// 3. Get the break node (all non-start root nodes are breakSpan)
|
||||
const breakSpans: SpanNode[] = getBreakSpans(callTrees, false);
|
||||
|
||||
// 4. 根据调用tree,生成PRD中的Tree(即,PRD中的Tree)
|
||||
// 4. According to the call tree, generate the Tree in PRD (ie, the Tree in PRD)
|
||||
const treeStartSpan = callTree2TraceTree(startSpan);
|
||||
|
||||
if (breakSpans.length > 0) {
|
||||
// 5. 将所有breakSpans挂载到rootBreakSpan节点下
|
||||
// 5. Mount all breakSpans under the rootBreakSpan node
|
||||
const breakSpan: SpanNode = getRootBreakSpan(breakSpans);
|
||||
// 6. 根据调用tree,生成TraceTree
|
||||
// 6. Generate TraceTree according to the call tree
|
||||
const treeBreakSpan = callTree2TraceTree(breakSpan);
|
||||
|
||||
// 7. 将treeBreakSpan挂在到treeStartSpan下
|
||||
// 7. Hang treeBreakSpan under treeStartSpan
|
||||
treeStartSpan.children = treeStartSpan.children ?? [];
|
||||
treeStartSpan.children.push(treeBreakSpan);
|
||||
treeBreakSpan.parent = treeStartSpan;
|
||||
|
||||
@@ -86,7 +86,7 @@ const getStatusForBatch = (spans: CSpanSingleForBatch[]): SpanStatus => {
|
||||
return isSuccess ? SpanStatus.Success : SpanStatus.Error;
|
||||
};
|
||||
|
||||
// spans直接聚合,生成batchSpan
|
||||
// Spans are directly polymerized to generate batchSpan.
|
||||
const genBatchSpan = function (
|
||||
spans: CSpanSingleForBatch[],
|
||||
spanCategoryMap?: SpanCategoryMap,
|
||||
@@ -94,13 +94,13 @@ const genBatchSpan = function (
|
||||
if (spans.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
// 合法性检查
|
||||
// legality check
|
||||
const taskTotal = spans[0].extra?.task_total;
|
||||
const spans0 = spans.filter(curSpan => {
|
||||
const curTaskTotal = curSpan.extra?.task_total;
|
||||
return curTaskTotal !== taskTotal;
|
||||
});
|
||||
// taskTotal不全部相等,数据不合法
|
||||
// taskTotal is not all equal, the data is invalid
|
||||
if (spans0.length > 0) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -122,7 +122,7 @@ const aggregationBatchSpan = function (
|
||||
) {
|
||||
const batchSpans: CSPanBatch[] = [];
|
||||
|
||||
// 根据 workflowNodeId + type对span进行归类
|
||||
// Sorting spans by workflowNodeId + type
|
||||
const map: {
|
||||
[key: string]: CSpanSingleForBatch[];
|
||||
} = {};
|
||||
@@ -136,14 +136,14 @@ const aggregationBatchSpan = function (
|
||||
map[type + workflowNodeId].push(span);
|
||||
});
|
||||
|
||||
// 进一步根据时间+序号进行归类,生成CSpanBatch
|
||||
// Further classify according to time + serial number to generate CSpanBatch
|
||||
Object.keys(map).forEach(key => {
|
||||
const workflowSpans = map[key];
|
||||
|
||||
// 排序:时间
|
||||
// Sort by: Time
|
||||
workflowSpans.sort(compareByStartAt);
|
||||
|
||||
// 根据task_index进行聚合
|
||||
// Aggregate according to task_index
|
||||
let curTaskIndexs: number[] = [];
|
||||
let curSpans: CSpanSingleForBatch[] = [];
|
||||
workflowSpans.forEach(span => {
|
||||
@@ -153,7 +153,7 @@ const aggregationBatchSpan = function (
|
||||
}
|
||||
|
||||
if (curTaskIndexs.includes(taskIndex)) {
|
||||
// 序号存在了,则新开启一组
|
||||
// If the serial number exists, open a new set.
|
||||
const batchSpan = genBatchSpan(curSpans, spanCategoryMap);
|
||||
if (batchSpan) {
|
||||
batchSpans.push(batchSpan);
|
||||
@@ -175,11 +175,11 @@ const aggregationBatchSpan = function (
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理原始Span(将额外节点字段统一到extra)
|
||||
* Process raw Span (unify extra node fields to extra)
|
||||
* @param span Span
|
||||
* @returns CSpanSingle
|
||||
*/
|
||||
// eslint-disable-next-line complexity -- 参数过多
|
||||
// eslint-disable-next-line complexity -- too many parameters
|
||||
export const span2CSpan = function (
|
||||
span: Span,
|
||||
spanCategoryMap?: SpanCategoryMap,
|
||||
@@ -260,7 +260,7 @@ export const spans2CSpans = function (
|
||||
? genSpanCategoryMap(spanCategoryMeta)
|
||||
: undefined;
|
||||
|
||||
// 根据span.id进行去重
|
||||
// Deduplicate according to span.id
|
||||
const uniqSpans = uniqBy(spans, 'id');
|
||||
|
||||
// Span -> CSpanSingle
|
||||
|
||||
@@ -69,7 +69,7 @@ export const getTokens = (
|
||||
span.type === SpanType.UserInputV2 ||
|
||||
span.type === SpanType.Workflow
|
||||
) {
|
||||
// SingleSpan节点 - Workflow
|
||||
// SingleSpan Node - Workflow
|
||||
const inputTokens = getSpanProp(span, 'input_tokens_sum') as number;
|
||||
const outputTokens = getSpanProp(span, 'output_tokens_sum') as number;
|
||||
|
||||
@@ -78,7 +78,7 @@ export const getTokens = (
|
||||
output_tokens: outputTokens,
|
||||
};
|
||||
} else {
|
||||
// SingleSpan节点 - 非workflow节点
|
||||
// SingleSpan Node - Non-workflow Node
|
||||
const inputTokens = getSpanProp(span, 'input_tokens') as number;
|
||||
const outputTokens = getSpanProp(span, 'output_tokens') as number;
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ const getFieldInput = (span: CSpan): FieldItem => ({
|
||||
|
||||
const getStreamOutput = (span: CSpan): FieldItem => {
|
||||
const value = getSpanProp(span, 'streaming_output') as StreamingOutputStatus;
|
||||
// key 为 starling key
|
||||
// Key to starling key
|
||||
return {
|
||||
key: I18n.t('query_stream_output'),
|
||||
value: streamingOutputStatusConfigMap[value]?.label,
|
||||
|
||||
@@ -195,7 +195,7 @@ export const SideDebugPanel = (props: SideDebugPanelProps) => {
|
||||
if (!spanCategory) {
|
||||
await handleFetchTracesMetaInfo();
|
||||
}
|
||||
// 从某条消息进入
|
||||
// Enter from a message
|
||||
if (entranceMessageLogId) {
|
||||
try {
|
||||
const spans = await handleFetchQueryDetail(entranceMessageLogId);
|
||||
@@ -232,7 +232,7 @@ export const SideDebugPanel = (props: SideDebugPanelProps) => {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
//直接进入
|
||||
//Direct entry
|
||||
else {
|
||||
try {
|
||||
const spans = await handleFetchQuery();
|
||||
|
||||
@@ -15,26 +15,26 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 支持筛选的query时间范围
|
||||
* Support for filtering query time ranges
|
||||
*/
|
||||
export const DATE_FILTERING_DAYS_NUMBER = 7;
|
||||
export const FILTERING_OPTION_ALL = 'ALL';
|
||||
/**
|
||||
* query每次加载条数
|
||||
* Query the number of pieces loaded per time
|
||||
*/
|
||||
export const FILTERING_LIMIT = 30;
|
||||
export const TRACES_ADVANCE_INFO_TIME_BUFFER = 1000;
|
||||
export const TIME_MINUTE = 60;
|
||||
/**
|
||||
* query拉取默认偏移量
|
||||
* Query Pull Default Offset
|
||||
*/
|
||||
export const INITIAL_OFFSET = '0';
|
||||
export const EMPTY_TEXT = '-';
|
||||
/**
|
||||
* query拉取防抖时间
|
||||
* Query Pull anti-shake time
|
||||
*/
|
||||
export const QUERY_FILTER_DEBOUNCE_TIME = 300;
|
||||
/**
|
||||
* 调试台位置信息localStorage key
|
||||
* Debug station location information localStorage key
|
||||
*/
|
||||
export const DEBUG_PANEL_LAYOUT_KEY = 'coze_debug_panel_layout_config';
|
||||
|
||||
@@ -31,7 +31,7 @@ export type UseDebugPanelLayoutConfig = () => [
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取和修改存储在localStorage中的调试台布局数据
|
||||
* Get and modify debug bench layout data stored in localStorage
|
||||
* @returns UseDebugPanelLayoutConfig
|
||||
*/
|
||||
export const useDebugPanelLayoutConfig: UseDebugPanelLayoutConfig = () => {
|
||||
|
||||
@@ -36,35 +36,35 @@ interface DebugPanelStore {
|
||||
isPanelShow: boolean;
|
||||
basicInfo: BasicInfo;
|
||||
/**
|
||||
* 当前选中的Query LogID
|
||||
* The currently selected Query LogID
|
||||
*/
|
||||
entranceMessageLogId?: string;
|
||||
/**
|
||||
* 日期筛选结果
|
||||
* date filter results
|
||||
*/
|
||||
targetDateId?: QueryFilterItemId;
|
||||
/**
|
||||
* 状态筛选结果
|
||||
* status filter results
|
||||
*/
|
||||
targetExecuteStatusId?: QueryFilterItemId;
|
||||
/**
|
||||
* 当前选中的Trace节点信息
|
||||
* Trace node information currently selected
|
||||
*/
|
||||
targetOverallSpanInfo?: TargetOverallSpanInfo;
|
||||
/**
|
||||
* 当前计算后的Trace列表
|
||||
* Current Calculated Trace List
|
||||
*/
|
||||
enhancedOverallSpans: CSpan[];
|
||||
/**
|
||||
* 某条Trace下Span节点列表
|
||||
* List of Spans under a Trace
|
||||
*/
|
||||
orgDetailSpans?: Span[];
|
||||
/**
|
||||
* 额外Span类型信息(服务端提供)
|
||||
* Additional Span type information (server level provided)
|
||||
*/
|
||||
spanCategory?: SpanCategory;
|
||||
/**
|
||||
* 当前选中的Span节点信息
|
||||
* Information about the currently selected Span node
|
||||
*/
|
||||
targetDetailSpan?: CSpan;
|
||||
curBatchPage?: number;
|
||||
|
||||
@@ -31,7 +31,7 @@ dayjs.extend(utc);
|
||||
const jsonBig = JSONBig({ storeAsString: true });
|
||||
|
||||
/**
|
||||
* 转换时间戳为当前格式化当前时区时间
|
||||
* Convert timestamp to current format current time zone
|
||||
* @param timestamp string | number
|
||||
* @returns UTCTimeInfo
|
||||
*/
|
||||
@@ -62,7 +62,7 @@ export const getPastWeekDates = (): string[] => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 从格式化时间提取其当前对应的开始/结束时间戳
|
||||
* Extract its current corresponding start/end timestamp from the format time
|
||||
* @param formattedDate QueryFilterItemId
|
||||
* @returns DailyTime
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@ export const getSpanProp = (span: CSpan, key: string) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 加强原始Span信息(注入服务端采集的token、status等信息)
|
||||
* Enhance the original Span information (inject token, status, etc. collected at the server level)
|
||||
* @param originSpans Span[]
|
||||
* @param traceAdvanceInfo TraceAdvanceInfo[]
|
||||
* @returns CSpan[]
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// 目前拿不到文件的 size 信息 文件过大会导致浏览器卡顿 PDF 暂时不需要注册进去 之后放开
|
||||
// At present, the size information of the file cannot be obtained. If the file is too large, the browser card PDF does not need to be registered for the time being, and then it will be released.
|
||||
import { JsonPreviewBasePlugin } from '../base';
|
||||
import OverlayAPI from '../../common/overlay';
|
||||
import PdfPreviewContent from './preview';
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function -- 迁移代码 */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any -- 迁移代码 */
|
||||
/* eslint-disable max-lines -- 迁移代码 */
|
||||
/* eslint-disable max-lines-per-function -- 迁移代码 */
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function -- migrating code */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any -- migrate code */
|
||||
/* eslint-disable max-lines -- migrate code */
|
||||
/* eslint-disable max-lines-per-function -- migrating code */
|
||||
|
||||
import {
|
||||
useState,
|
||||
@@ -171,7 +171,7 @@ const MockSetSelectComp = (
|
||||
const bizCtx: BizCtx = {
|
||||
...bizSceneCtx,
|
||||
connectorUID: uid,
|
||||
connectorID: CONNECTOR_ID, // 业务线为Coze
|
||||
connectorID: CONNECTOR_ID, // Business line for Coze
|
||||
};
|
||||
|
||||
const { jump } = usePageJumpService();
|
||||
@@ -457,7 +457,7 @@ const MockSetSelectComp = (
|
||||
content={getTooltipInfo()}
|
||||
visible={disabled && focused}
|
||||
position="left"
|
||||
style={{ display: disabled ? 'block' : 'none' }} // visible disabled不生效
|
||||
style={{ display: disabled ? 'block' : 'none' }} // Visible disabled not effective
|
||||
>
|
||||
<div
|
||||
style={style}
|
||||
|
||||
@@ -72,7 +72,7 @@ export const MockSetDeleteModal = ({
|
||||
} = mockSetInfo || {};
|
||||
const [mockSetRefCount, setMockSetRefCount] = useState(-1);
|
||||
|
||||
// space信息
|
||||
// Space information
|
||||
const spaceType = useSpaceStore(s => s.space.space_type);
|
||||
const isPersonal = spaceType === SpaceType.Personal;
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function -- 迁移代码 */
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function -- migrating code */
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
@@ -87,10 +87,10 @@ export const MockSetEditModal = ({
|
||||
|
||||
const [FLAGS] = useFlags();
|
||||
|
||||
// 根据是否传入 id 判断是否为创建场景
|
||||
// Determine whether to create a scene based on whether to pass in the id
|
||||
const isCreate = !initialInfo.id;
|
||||
|
||||
// space信息
|
||||
// Space information
|
||||
const spaceType = useSpaceStore(s => s.space.space_type);
|
||||
const isPersonal = spaceType === SpaceType.Personal;
|
||||
|
||||
@@ -216,7 +216,7 @@ export const MockSetEditModal = ({
|
||||
>
|
||||
{({ formState }) => (
|
||||
<>
|
||||
{/* mockSet名称 */}
|
||||
{/* mockSet name */}
|
||||
{disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
@@ -244,7 +244,7 @@ export const MockSetEditModal = ({
|
||||
rules={mockSetInfoRules.name}
|
||||
/>
|
||||
)}
|
||||
{/* mockSet描述 */}
|
||||
{/* mockSet description */}
|
||||
{disabled ? (
|
||||
<Form.Slot
|
||||
label={{
|
||||
@@ -273,8 +273,8 @@ export const MockSetEditModal = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* 二期支持autoGenerate*/}
|
||||
{/* 社区版暂不支持该功能 */}
|
||||
{/* Phase II supports autoGenerate*/}
|
||||
{/* The community edition does not support this function for the time being */}
|
||||
{isCreate && FLAGS['bot.devops.mockset_auto_generate'] ? (
|
||||
<>
|
||||
<Form.Checkbox
|
||||
|
||||
@@ -24,7 +24,7 @@ export const REAL_DATA_MOCKSET = {
|
||||
name: I18n.t('real_data'),
|
||||
};
|
||||
|
||||
// 初始化仅有real_data
|
||||
// Initialization only real_data
|
||||
export const MOCK_OPTION_LIST = [REAL_DATA_MOCKSET];
|
||||
|
||||
export const POLLING_INTERVAL = 10000;
|
||||
@@ -48,7 +48,7 @@ export const mockSetInfoRules: {
|
||||
message: I18n.t('create_plugin_modal_nameerror'),
|
||||
}
|
||||
: {
|
||||
pattern: /^[\w\s\u4e00-\u9fa5]+$/u, // 国内增加支持中文
|
||||
pattern: /^[\w\s\u4e00-\u9fa5]+$/u, // Increased domestic support for Chinese
|
||||
message: I18n.t('create_plugin_modal_nameerror_cn'),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-lines-per-function -- 代码迁移 */
|
||||
/* eslint-disable @coze-arch/max-line-per-function -- 代码迁移 */
|
||||
|
||||
/* eslint-disable max-lines-per-function -- code migration */
|
||||
/* eslint-disable @coze-arch/max-line-per-function -- code migration */
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
@@ -149,7 +149,7 @@ export const useInitialGetEnabledMockSet = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 取消
|
||||
// cancel
|
||||
const cancel = () => {
|
||||
pollingTurnRef.current = undefined;
|
||||
cancelReq.current?.();
|
||||
|
||||
@@ -37,7 +37,7 @@ interface AutoFillButtonProps {
|
||||
onAutoFill?: (schemas: NodeFormSchema[]) => void;
|
||||
}
|
||||
|
||||
/** AI生成节点数据的按钮 */
|
||||
/** AI button to generate node data */
|
||||
export function AutoFillButton({
|
||||
className,
|
||||
style,
|
||||
@@ -79,7 +79,7 @@ export function AutoFillButton({
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
|
||||
// 社区版暂不支持该功能
|
||||
// The community edition does not support this function for the time being
|
||||
if (!FLAGS['bot.devops.testset_auto_gen'] || !(IS_OVERSEA || IS_BOE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ interface FormLabelProps {
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
// 内置的FormLabel样式不支持 typeLabel,所以简单自定义
|
||||
// The built-in FormLabel style does not support typeLabel, so it is easy to customize
|
||||
export function FormLabel({
|
||||
label,
|
||||
typeLabel,
|
||||
|
||||
@@ -70,7 +70,7 @@ interface TestsetEditSideSheetProps extends TestsetEditState {
|
||||
onClose?: () => void;
|
||||
onSuccess?: (testset?: TestsetData) => void;
|
||||
onCancel?: () => void;
|
||||
/** 是否为多人协作模式 */
|
||||
/** Is it a multiplayer collaboration mode? */
|
||||
isExpertMode?: boolean;
|
||||
}
|
||||
|
||||
@@ -78,10 +78,10 @@ const TESTSET_NAME_FIELD = '__TESTSET_NAME__';
|
||||
const TESTSET_DESC_FIELD = '__TESTSET_DESC__';
|
||||
|
||||
/**
|
||||
* 特化逻辑:表单项赋默认值
|
||||
* - Boolean类型:`false` 因为undefined的表现上和false一样,容易引发用户误解
|
||||
* - Object类型:`{}`
|
||||
* - Array类型: `[]`
|
||||
* Specialized logic: list entries are assigned default values
|
||||
* - Boolean type: 'false' because undefined behaves the same as false, which is easy to cause user misunderstandings
|
||||
* - Object type: '{}'
|
||||
* - Array type: '[]'
|
||||
*/
|
||||
function assignDefaultValue(ipt: FormItemSchema) {
|
||||
if (!isNil(ipt.value)) {
|
||||
@@ -140,7 +140,7 @@ export function TestsetEditSideSheet({
|
||||
const remoteSchemas = toNodeFormSchemas(res.schemaJson);
|
||||
|
||||
if (localSchemas.length) {
|
||||
// 编辑模式:比对本地和远程schema并尝试赋值
|
||||
// Edit schema: compare local and remote schemas and try to assign values
|
||||
const localSchemaMap: Record<string, FormItemSchema | undefined> = {};
|
||||
traverseNodeFormSchemas(
|
||||
localSchemas,
|
||||
@@ -156,7 +156,7 @@ export function TestsetEditSideSheet({
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 创建模式:赋默认值
|
||||
// Creation mode: assigns default values
|
||||
traverseNodeFormSchemas(remoteSchemas, (schema, ipt) => {
|
||||
assignDefaultValue(ipt);
|
||||
});
|
||||
@@ -178,7 +178,7 @@ export function TestsetEditSideSheet({
|
||||
});
|
||||
}, [visible, testset]);
|
||||
|
||||
// 给节点表单设置值
|
||||
// Set a value for the node form
|
||||
useEffect(() => {
|
||||
if (typeof nodeSchemas === 'undefined') {
|
||||
return;
|
||||
@@ -260,7 +260,7 @@ export function TestsetEditSideSheet({
|
||||
}
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
// Submit Form
|
||||
const onSubmit = async () => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
@@ -278,7 +278,7 @@ export function TestsetEditSideSheet({
|
||||
ipt.value = val;
|
||||
}
|
||||
|
||||
// 清除 object/array的空值,包括空字符串
|
||||
// Clears null values of objects/arrays, including empty strings
|
||||
if (
|
||||
!val &&
|
||||
(ipt.type === FormItemSchemaType.LIST ||
|
||||
@@ -287,7 +287,7 @@ export function TestsetEditSideSheet({
|
||||
ipt.value = undefined;
|
||||
}
|
||||
|
||||
// bool 类型 需要将枚举转为布尔值
|
||||
// Bool type, you need to convert the enumeration to a boolean
|
||||
if (ipt.type === FormItemSchemaType.BOOLEAN) {
|
||||
ipt.value = transBoolSelect2Bool(ipt.value as ValuesForBoolSelect);
|
||||
}
|
||||
@@ -330,7 +330,7 @@ export function TestsetEditSideSheet({
|
||||
});
|
||||
|
||||
testsetFormApi.current?.setValues(formValues);
|
||||
// 设置值之后再校验一次
|
||||
// Check again after setting the value
|
||||
testsetFormApi.current?.validate(validateFields);
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ import s from './node-form-section.module.less';
|
||||
|
||||
interface NodeFormSectionProps {
|
||||
schema: NodeFormSchema;
|
||||
/** AI生成中 */
|
||||
/** AI is being generated */
|
||||
autoGenerating?: boolean;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
@@ -49,7 +49,7 @@ interface NodeFormSectionProps {
|
||||
|
||||
const { Section, InputNumber } = Form;
|
||||
|
||||
/** 整数类型表单精度 */
|
||||
/** integer type form precision */
|
||||
const INTEGER_PRECISION = 0.1;
|
||||
|
||||
export function NodeFormSection({
|
||||
@@ -62,7 +62,7 @@ export function NodeFormSection({
|
||||
|
||||
const renderSectionTitle = () => {
|
||||
let sectionName = schema.component_name;
|
||||
// 目前只有start和variable两种节点
|
||||
// Currently only two nodes are start and variable
|
||||
switch (schema.component_type) {
|
||||
case ComponentType.CozeStartNode:
|
||||
sectionName = I18n.t('workflow_testset_start_node');
|
||||
|
||||
@@ -28,7 +28,7 @@ function count(val: unknown) {
|
||||
return val ? `${val}`.length : 0;
|
||||
}
|
||||
|
||||
/** 需要后缀 & blur trim,扩展下原始的input */
|
||||
/** The suffix & blur trim is required to expand the original input */
|
||||
function InnerInput(props: InputProps) {
|
||||
const onBlur = (evt: FocusEvent<HTMLInputElement>) => {
|
||||
props.onChange?.(
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
} from '../../types';
|
||||
|
||||
let ajv: Ajv | undefined;
|
||||
/** jsonStr转为节点表单schema(简单的`JSON.parse`) */
|
||||
/** jsonStr converts to a node form schema (simply'JSON.parse ') */
|
||||
export function toNodeFormSchemas(jsonStr?: string): NodeFormSchema[] {
|
||||
if (!jsonStr) {
|
||||
return [];
|
||||
@@ -48,7 +48,7 @@ export function toNodeFormSchemas(jsonStr?: string): NodeFormSchema[] {
|
||||
}
|
||||
}
|
||||
|
||||
/** 空值判断,null/undefined/NaN */
|
||||
/** Null value judgment, null/undefined/NaN */
|
||||
export function isNil(val: unknown) {
|
||||
return (
|
||||
typeof val === 'undefined' ||
|
||||
@@ -61,7 +61,7 @@ function isNumberType(t: string) {
|
||||
return t === FormItemSchemaType.NUMBER || t === FormItemSchemaType.FLOAT;
|
||||
}
|
||||
|
||||
/** 判断类型一致,**特化:**`number`和`float`视为同一类型 */
|
||||
/** Determine that the type is consistent, ** specialization: ** 'number' and'float 'are regarded as the same type */
|
||||
export function isSameType(t1?: string, t2?: string) {
|
||||
if (typeof t1 === 'undefined' || typeof t2 === 'undefined') {
|
||||
return false;
|
||||
@@ -70,7 +70,7 @@ export function isSameType(t1?: string, t2?: string) {
|
||||
return isNumberType(t1) ? isNumberType(t2) : t1 === t2;
|
||||
}
|
||||
|
||||
/** 两层for遍历schema (经常需要遍历,单独抽一个的函数) */
|
||||
/** Two layers for traversing schema (often need to traverse, draw a single function) */
|
||||
export function traverseNodeFormSchemas(
|
||||
schemas: NodeFormSchema[],
|
||||
cb: (s: NodeFormSchema, ip: FormItemSchema) => any,
|
||||
@@ -83,9 +83,9 @@ export function traverseNodeFormSchemas(
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验名称格式(参考插件名称)
|
||||
* - 海外:仅支持输入字母、数字、下划线或空格
|
||||
* - 国内:仅支持输入中文、字母、数字、下划线或空格
|
||||
* Verification name format (refer to plug-in name)
|
||||
* - Overseas: Only support entering letters, numbers, underscores or spaces
|
||||
* - Domestic: Only supports entering Chinese, letters, numbers, underscores or spaces
|
||||
*/
|
||||
function validateNamePattern(
|
||||
name: string,
|
||||
@@ -109,19 +109,19 @@ interface GetTestsetNameRulesProps {
|
||||
bizCtx?: BizCtx;
|
||||
/** bizComponentSubject */
|
||||
bizComponentSubject?: ComponentSubject;
|
||||
/** 原始值 */
|
||||
/** raw value */
|
||||
originVal?: string;
|
||||
/** 是否为海外(海外不允许输入中文 ,与PluginName校验规则对齐) */
|
||||
/** Whether it is overseas (overseas is not allowed to enter Chinese, it is aligned with the PluginName verification rule) */
|
||||
isOversea?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Testset名称表单校验规则
|
||||
* TestSet Name Form Validation Rules
|
||||
*
|
||||
* @param param.bizCtx - bizCtx
|
||||
* @param param.bizComponentSubject - bizComponentSubject
|
||||
* @param param.originVal - 原始值
|
||||
* @param param.isOversea - 是否为海外(海外不允许输入中文 ,与PluginName校验规则对齐)
|
||||
* @Param param.originVal - original value
|
||||
* @Param param.isOverseas - whether it is overseas (overseas is not allowed to enter Chinese, it is aligned with the PluginName verification rule)
|
||||
*/
|
||||
export function getTestsetNameRules({
|
||||
bizCtx,
|
||||
@@ -143,12 +143,12 @@ export function getTestsetNameRules({
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑模式下,名称与原名相同时跳过
|
||||
// In edit mode, skip when the name is the same as the original name
|
||||
if (originVal && value === originVal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 中文、字母等等等等
|
||||
// Chinese, letters, etc., etc
|
||||
const formatMsg = validateNamePattern(value, isOversea);
|
||||
|
||||
if (formatMsg) {
|
||||
@@ -156,7 +156,7 @@ export function getTestsetNameRules({
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查重复
|
||||
// Check for duplicates
|
||||
try {
|
||||
const { isPass } = await debuggerApi.CheckCaseDuplicate({
|
||||
bizCtx,
|
||||
@@ -179,9 +179,9 @@ export function getTestsetNameRules({
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单label
|
||||
* - bot:选择你需要的Bot
|
||||
* - 其他:字段名
|
||||
* Form label
|
||||
* - bot: choose the bot you need
|
||||
* - Other: field names
|
||||
*/
|
||||
export function getLabel(formSchema: FormItemSchema) {
|
||||
return formSchema.type === FormItemSchemaType.BOT
|
||||
@@ -207,7 +207,7 @@ function getSubType(type: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 类型标签 */
|
||||
/** type label */
|
||||
export function getTypeLabel(formSchema: FormItemSchema) {
|
||||
switch (formSchema.type) {
|
||||
case FormItemSchemaType.STRING:
|
||||
@@ -230,8 +230,8 @@ export function getTypeLabel(formSchema: FormItemSchema) {
|
||||
|
||||
/**
|
||||
* placeholder
|
||||
* - bot:请选择bot
|
||||
* - 其他:xx必填
|
||||
* - bot: Please select bot
|
||||
* - Other: xx required
|
||||
*/
|
||||
export function getPlaceholder({ name, type }: FormItemSchema) {
|
||||
if (type === FormItemSchemaType.BOT) {
|
||||
@@ -245,7 +245,7 @@ export function getPlaceholder({ name, type }: FormItemSchema) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 字段在表单中的唯一字段名 */
|
||||
/** The unique field name of the field in the form */
|
||||
export function getSubFieldName(
|
||||
formSchema: NodeFormSchema,
|
||||
itemSchema: FormItemSchema,
|
||||
@@ -322,8 +322,8 @@ function validateByJsonSchema(val: any, jsonSchema: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义表单的额外参数
|
||||
* 目前只对array和object表单加jsonSchema校验
|
||||
* Customize the form's additional parameters
|
||||
* Currently only jsonSchema validation is applied to array and object forms
|
||||
*/
|
||||
export function getCustomProps(formItemSchema: FormItemSchema) {
|
||||
switch (formItemSchema.type) {
|
||||
@@ -368,7 +368,7 @@ export enum ValuesForBoolSelect {
|
||||
UNDEFINED = 'undefined',
|
||||
}
|
||||
|
||||
/** 布尔类型选项 */
|
||||
/** Boolean Type Options */
|
||||
export const optionsForBoolSelect = [
|
||||
{
|
||||
value: ValuesForBoolSelect.TRUE,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
}
|
||||
|
||||
.semi-select-option-list {
|
||||
/* stylelint-disable-next-line declaration-no-important -- semi-select-option-list的max-height写在了style上,所以要important覆盖 */
|
||||
/* stylelint-disable-next-line declaration-no-important -- semi-select-option-list max-height is written on style, so override important */
|
||||
max-height: 208px !important;
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
|
||||
@@ -43,12 +43,12 @@ import {
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface TestsetSelectProps {
|
||||
/** 当前testset */
|
||||
/** Current testset */
|
||||
testset: TestsetData | undefined;
|
||||
placeholder?: string;
|
||||
/** 是否有workflow编辑权限,也挂放在外层的 TestsetManageProvider上,组件上的editable优先级更高 */
|
||||
/** Whether there is workflow editing permission, it is also hung on the outer TestsetManageProvider, and the editable priority on the component is higher */
|
||||
editable?: boolean;
|
||||
/** 编辑面板mask */
|
||||
/** Edit panel mask */
|
||||
editSideSheetMask?: boolean;
|
||||
onSelect: (v?: TestsetData) => void;
|
||||
className?: string;
|
||||
@@ -57,7 +57,7 @@ export interface TestsetSelectProps {
|
||||
|
||||
const DEBOUNCE_DELAY = 200;
|
||||
|
||||
/** option key, 更新 name、incompatible、input时都要重新渲染 */
|
||||
/** Option key, re-render when updating name, incompatible, input */
|
||||
function getOptionKey({ caseBase, schemaIncompatible }: TestsetData) {
|
||||
return `${caseBase?.caseID}_${caseBase?.name}_${caseBase?.input}_${
|
||||
schemaIncompatible ? 0 : 1
|
||||
@@ -65,12 +65,12 @@ function getOptionKey({ caseBase, schemaIncompatible }: TestsetData) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Testset下拉选择组件
|
||||
* 需配合`TestsetManageProvider`一起使用
|
||||
* TestSet drop-down selection component
|
||||
* Should be used with TestsetManageProvider
|
||||
* @example
|
||||
* ``` tsx
|
||||
* <TestsetManageProvider
|
||||
* // 一些必填参数 bizCtx bizComponentSubject editable formRenders
|
||||
* //Some required parameters bizCtx bizComponentSubject editable formRendersitable formRenders
|
||||
* >
|
||||
* <TestsetSideSheet visible={visible} onClose={() => setVisible(false)} />
|
||||
* </TestsetManageProvider>
|
||||
@@ -107,7 +107,7 @@ export function TestsetSelect({
|
||||
{},
|
||||
);
|
||||
|
||||
// 首次加载
|
||||
// first load
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setPending(true);
|
||||
@@ -153,7 +153,7 @@ export function TestsetSelect({
|
||||
closeTestsetEdit();
|
||||
};
|
||||
|
||||
// 选中Testset
|
||||
// Select Testset
|
||||
const onSelectTestset = (val: SelectProps['value']) => {
|
||||
if (typeof val !== 'string' || editRef.current) {
|
||||
return;
|
||||
@@ -163,7 +163,7 @@ export function TestsetSelect({
|
||||
op => op.caseBase?.caseID === val,
|
||||
);
|
||||
|
||||
// 不兼容的不可选中
|
||||
// Incompatible unselectable
|
||||
if (!selectedTestset || selectedTestset.schemaIncompatible) {
|
||||
return;
|
||||
}
|
||||
@@ -220,11 +220,11 @@ export function TestsetSelect({
|
||||
}
|
||||
};
|
||||
|
||||
// 自定义选中选项
|
||||
// Custom selected options
|
||||
const renderSelectedItem: RenderSingleSelectedItemFn = () =>
|
||||
testset ? <SelectedTestsetOptionItem data={testset} /> : null;
|
||||
|
||||
// testset为空的时候,不展示下拉选项(对大部分来说可能不需要看到这个下拉)
|
||||
// When the testset is empty, the drop-down option is not displayed (for most people, you may not need to see this drop-down).
|
||||
if (pending) {
|
||||
return null;
|
||||
}
|
||||
@@ -258,8 +258,8 @@ export function TestsetSelect({
|
||||
{optionsData.list.map(data => (
|
||||
<Select.Option
|
||||
value={data.caseBase?.caseID}
|
||||
// disabled的option编辑/删除唤起其他浮层后,select不会自动失焦
|
||||
// 用样式模拟disabled,并修改onSelect选中不兼容testset的逻辑
|
||||
// Disabled option Edit/Delete to select without auto-out of focus after evoking other floating layers
|
||||
// Simulate disabled with styles and modify the logic of onSelect selecting incompatible testsets
|
||||
className={cls(data.schemaIncompatible && s['incompatible-option'])}
|
||||
key={getOptionKey(data)}
|
||||
>
|
||||
|
||||
@@ -32,7 +32,7 @@ import s from './testset-option-item.module.less';
|
||||
interface TestsetOptionItemProps {
|
||||
className?: string;
|
||||
data: TestsetData;
|
||||
/** 有编辑权限 */
|
||||
/** Have editing permission */
|
||||
editable?: boolean;
|
||||
onEdit?: (data: TestsetData) => void;
|
||||
onDelete?: (data: TestsetData) => void;
|
||||
@@ -40,7 +40,7 @@ interface TestsetOptionItemProps {
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
/** 多行文本展示优化 */
|
||||
/** multi-line text display optimization */
|
||||
const MULTILINE_TOOLTIP_STYLE: CSSProperties = { wordBreak: 'break-word' };
|
||||
|
||||
export function TestsetOptionItem({
|
||||
@@ -54,7 +54,7 @@ export function TestsetOptionItem({
|
||||
const testsetName = data.caseBase?.name ?? '-';
|
||||
|
||||
const onOptionClick = (evt: MouseEvent<HTMLDivElement>) => {
|
||||
// 非兼容时需要阻止冒泡
|
||||
// Bubbling needs to be prevented when incompatible
|
||||
if (incompatible) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
@@ -123,7 +123,7 @@ export function TestsetOptionItem({
|
||||
);
|
||||
}
|
||||
|
||||
/** 选中的回填项 */
|
||||
/** Selected backfill */
|
||||
export function SelectedTestsetOptionItem({
|
||||
data,
|
||||
className,
|
||||
|
||||
@@ -52,7 +52,7 @@ export interface TestsetSideSheetProps {
|
||||
visible: boolean;
|
||||
editable?: boolean;
|
||||
onClose: () => void;
|
||||
/** 是否为多人协作模式 */
|
||||
/** Is it a multiplayer collaboration mode? */
|
||||
isExpertMode?: boolean;
|
||||
}
|
||||
|
||||
@@ -89,19 +89,19 @@ interface TestsetQueryResult {
|
||||
const DEFAULT_PAGE_SIZE = 30;
|
||||
|
||||
/**
|
||||
* Testset管理侧边面板
|
||||
* 需配合`TestsetManageProvider`一起使用
|
||||
* Testset Management Side Panel
|
||||
* Should be used with TestsetManageProvider
|
||||
*
|
||||
* @example
|
||||
* ``` tsx
|
||||
* <TestsetManageProvider
|
||||
* // 一些必填参数 bizCtx bizComponentSubject editable formRenders
|
||||
* //Some required parameters bizCtx bizComponentSubject editable formRendersitable formRenders
|
||||
* >
|
||||
* <TestsetSideSheet visible={visible} onClose={() => setVisible(false)} />
|
||||
* </TestsetManageProvider>
|
||||
* ```
|
||||
*/
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function -- 大组件>150行,只超了不到5行哈
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function -- large components > 150 lines, only less than 5 lines
|
||||
export function TestsetSideSheet({
|
||||
visible,
|
||||
onClose,
|
||||
@@ -142,7 +142,7 @@ export function TestsetSideSheet({
|
||||
if (visible) {
|
||||
patchTestsetResp({ list: [] });
|
||||
reloadTestsetList();
|
||||
// 检查schema
|
||||
// Check schema
|
||||
checkSchema();
|
||||
}
|
||||
}, [visible]);
|
||||
@@ -240,7 +240,7 @@ export function TestsetSideSheet({
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Testset管理侧边面板 */}
|
||||
{/* Testset Management Side Panel */}
|
||||
<SideSheet
|
||||
className={s.sidesheet}
|
||||
title={
|
||||
@@ -267,7 +267,7 @@ export function TestsetSideSheet({
|
||||
<AutoLoadMore noMore={noMore} loadingMore={loadingMore} />
|
||||
</div>
|
||||
</SideSheet>
|
||||
{/* Testset创建/编辑侧边面板 */}
|
||||
{/* Testset Create/Edit Side Panel */}
|
||||
<TestsetEditSideSheet
|
||||
{...testsetEditState}
|
||||
mask={false}
|
||||
|
||||
@@ -39,9 +39,9 @@ import s from './testset-list-item.module.less';
|
||||
interface TestsetListItemProps {
|
||||
data: TestsetData;
|
||||
onEdit?: (data: TestsetData) => void;
|
||||
/** 点击了删除 */
|
||||
/** I clicked delete. */
|
||||
onClickDelete?: () => void;
|
||||
/** 确认删除 */
|
||||
/** Confirm deletion */
|
||||
onDelete?: (data: TestsetData) => Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
export enum TestsetManageEventName {
|
||||
/** 创建测试集成功 */
|
||||
/** Created test set successfully */
|
||||
CREATE_TESTSET_SUCCESS = 'create_testset_success',
|
||||
/** 点击AI生成节点入参 */
|
||||
/** Click AI to generate node imported parameters */
|
||||
AIGC_PARAMS_CLICK = 'aigc_params_click',
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export enum SchemaError {
|
||||
INVALID = 'invalid',
|
||||
}
|
||||
|
||||
/** 变量命名校验规则(对齐workflow得参数名校验) */
|
||||
/** Variable name verification rules (parameter name verification for aligned workflow) */
|
||||
const PARAM_NAME_VALIDATION_RULE =
|
||||
/^(?!.*\b(true|false|and|AND|or|OR|not|NOT|null|nil|If|Switch)\b)[a-zA-Z_][a-zA-Z_$0-9]*$/;
|
||||
|
||||
@@ -112,21 +112,21 @@ function checkArrayOrObjectField(field: FormItemSchema) {
|
||||
}
|
||||
|
||||
function checkNodeFormSchema(schema: NodeFormSchema) {
|
||||
// 节点参数为空
|
||||
// Node parameter is empty
|
||||
if (!schema.inputs.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nameSet = new Set<string>();
|
||||
for (const ipt of schema.inputs) {
|
||||
// 名称非法 or 重复
|
||||
// Name illegal or duplicate
|
||||
if (!validateParamName(ipt.name) || nameSet.has(ipt.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nameSet.add(ipt.name);
|
||||
|
||||
// 单独检测复杂类型
|
||||
// Detect complex types individually
|
||||
if (!checkArrayOrObjectField(ipt)) {
|
||||
return false;
|
||||
}
|
||||
@@ -143,7 +143,7 @@ function validateSchema(json?: string) {
|
||||
try {
|
||||
const schemas = JSON.parse(json) as NodeFormSchema[];
|
||||
|
||||
// schema为空 or start节点的inputs为空
|
||||
// Schema is empty or start node inputs are empty
|
||||
const isEmpty =
|
||||
schemas.length === 0 ||
|
||||
(schemas[0].component_type === ComponentType.CozeStartNode &&
|
||||
@@ -166,7 +166,7 @@ function validateSchema(json?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查workflow节点表单是否为空(schema为空 or start节点的inputs为空) */
|
||||
/** Checks if the workflow node form is empty (schema is empty or start node inputs are empty) */
|
||||
export function useCheckSchema() {
|
||||
const { bizComponentSubject, bizCtx } = useTestsetManageStore(store => store);
|
||||
const [schemaError, setSchemaError] = useState(SchemaError.OK);
|
||||
|
||||
@@ -26,11 +26,11 @@ import { type TestsetManageEventName } from './events';
|
||||
export interface TestsetManageState {
|
||||
bizCtx?: BizCtx;
|
||||
bizComponentSubject?: ComponentSubject;
|
||||
/** 编辑权限 */
|
||||
/** edit permission */
|
||||
editable?: boolean;
|
||||
/** 表单渲染组件 */
|
||||
/** form rendering component */
|
||||
formRenders?: Partial<Record<FormItemSchemaType, NodeFormItem>>;
|
||||
/** 埋点事件上报 */
|
||||
/** Event tracking event reporting */
|
||||
reportEvent?: (
|
||||
name: TestsetManageEventName,
|
||||
params?: Record<string, unknown>,
|
||||
@@ -38,7 +38,7 @@ export interface TestsetManageState {
|
||||
}
|
||||
|
||||
export interface TestsetManageAction {
|
||||
/** 更新状态 */
|
||||
/** update status */
|
||||
patch: (s: Partial<TestsetManageState>) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,13 +47,13 @@ export type ObjectFieldSchema = {
|
||||
}[];
|
||||
|
||||
export interface FormItemSchema {
|
||||
// 扩展为枚举
|
||||
// Expand to enumeration
|
||||
type: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
value?: string | number | boolean;
|
||||
/** object/array复杂类型有schema定义 */
|
||||
/** Object/array complex types have schema definitions */
|
||||
schema?: ArrayFieldSchema | ObjectFieldSchema;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user