chore: replace all cn comments of fe to en version by volc api (#320)
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
|
||||
import { StandardNodeType } from '@coze-workflow/base';
|
||||
|
||||
// 默认所有节点可用,可以自定义
|
||||
// All nodes are available by default and can be customized.
|
||||
export const getEnabledNodeTypes = (_params: {
|
||||
loopSelected: boolean;
|
||||
isProject: boolean;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { api, type InferEditorAPIFromPlugins } from '@coze-editor/editor/react';
|
||||
import preset from '@coze-editor/editor/preset-code';
|
||||
import { type EditorView } from '@codemirror/view';
|
||||
|
||||
// 忽略 readOnly 强制设置值
|
||||
// Ignore readOnly to force a value
|
||||
const forceSetValue =
|
||||
({ view }: { view: EditorView }) =>
|
||||
(value: string) => {
|
||||
|
||||
@@ -35,7 +35,7 @@ export interface Output {
|
||||
children?: Output[];
|
||||
}
|
||||
|
||||
// javascript 为历史数据,目前只会有 python | typescript
|
||||
// Javascript is historical data, currently only python | typescript is available.
|
||||
export type LanguageType = 'python' | 'typescript' | 'javascript';
|
||||
|
||||
export interface PreviewerProps {
|
||||
@@ -67,7 +67,7 @@ export interface EditorProps {
|
||||
onTestRun?: () => void;
|
||||
testRunIcon?: ReactNode;
|
||||
/**
|
||||
* @deprecated onTestRunStateChange 已失效,线上也未使用到
|
||||
* @Deprecated onTestRunStateChange has expired and is not used online
|
||||
*/
|
||||
onTestRunStateChange?: (status: string) => void;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ export function DemoComponent(props: { name: string }): JSX.Element {
|
||||
const [foo] = useState('hello world');
|
||||
const { name } = props;
|
||||
return (
|
||||
// font-bold 来自 taiwindcss
|
||||
// 建议优先使用 taiwindcss
|
||||
// Font-bold from taiwindcss
|
||||
// It is recommended to use taiwindcss first.
|
||||
<div className={classNames(s.foo, 'font-bold')}>
|
||||
{foo} {name}!
|
||||
<div>
|
||||
|
||||
@@ -38,14 +38,14 @@ const hasHistory = (): boolean =>
|
||||
type NavigateScene = 'publish' | 'exit';
|
||||
|
||||
/**
|
||||
* 返回上一页逻辑
|
||||
* Return to previous page logic
|
||||
*/
|
||||
export const useNavigateBack = () => {
|
||||
const pageJumpResponse = usePageJumpResponse(PageType.WORKFLOW);
|
||||
const { jump } = usePageJumpService();
|
||||
const navigate = useNavigate();
|
||||
|
||||
/** 流程详情页路由更新会导致 pageJumpResponse 失效, 因此需要缓存一份原始版本 */
|
||||
/** Process details page route update will invalidate pageJumpResponse, so you need to cache a copy of the original version */
|
||||
const memoPageJumpResponse = useMemo(() => pageJumpResponse, []);
|
||||
|
||||
const navigateBack = async (
|
||||
@@ -71,12 +71,12 @@ export const useNavigateBack = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 跳转回bot详情页,并带上场景参数
|
||||
// Jump back to the bot details page with scene parameters
|
||||
if (memoPageJumpResponse?.scene) {
|
||||
if (scene === 'publish') {
|
||||
let pluginID = plugin_id;
|
||||
|
||||
// 第一次发布流程时,需要重新从接口获取pluginID
|
||||
// When you publish the process for the first time, you need to retrieve the pluginID from the interface again.
|
||||
if (!pluginID || pluginID === '0') {
|
||||
const workflowRes = await workflowApi.GetCanvasInfo(
|
||||
{
|
||||
@@ -143,9 +143,9 @@ export const useNavigateBack = () => {
|
||||
namespace: 'workflow',
|
||||
eventName: 'workflow_navigate_back_to_list',
|
||||
});
|
||||
// 如果 history 就一个页面,此时 navigate(-1) 不生效,需要手动指定路径
|
||||
// 目前先写死路径,缺点是需要跟随页面结构更改而更改,后面再优化更好的实现
|
||||
// /library 为 coze 2.0 混排资源列表页
|
||||
// If history is only one page, navigate (-1) does not take effect at this time, you need to manually specify the path
|
||||
// At present, the dead path is written first. The disadvantage is that it needs to be changed with the change of the page structure, and then optimized for a better implementation later.
|
||||
// /library coze 2.0 mixed resource list page
|
||||
navigate(`/space/${spaceId}/library`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,23 +22,23 @@ import { OperateType } from '@coze-workflow/base/api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
|
||||
/** 流程详情页参数 */
|
||||
/** Process details page parameters */
|
||||
interface SearchParams {
|
||||
workflow_id: string;
|
||||
space_id: string;
|
||||
/** 流程版本, 当多人协作时有版本概念, 单独设置时可预览对应版本流程 */
|
||||
/** Process version, when multiple people cooperate, there is a version concept, and the corresponding version process can be previewed when set separately */
|
||||
version?: string;
|
||||
/** 是否要恢复到目标版本, 如果设置, 则流程草稿会自动设置到对应 version */
|
||||
/** Whether to restore to the target version, if set, the process draft will be automatically set to the corresponding version */
|
||||
set_version?: string;
|
||||
/** 对应 version 的操作类型 */
|
||||
/** Corresponding version of the operation type */
|
||||
opt_type?: string;
|
||||
/** 流程页面打开来源 */
|
||||
/** Process page open source */
|
||||
from?: WorkflowPlaygroundProps['from'];
|
||||
/** 节点id 配置会自动定位到对应节点 */
|
||||
/** The node id configuration will automatically locate to the corresponding node. */
|
||||
node_id?: string;
|
||||
/** 执行id 配置会展示对应执行结果 */
|
||||
/** The execution id configuration will display the corresponding execution result. */
|
||||
execute_id?: string;
|
||||
/** 子流程执行id */
|
||||
/** subprocess execution id */
|
||||
sub_execute_id?: string;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export function usePageParams() {
|
||||
needUpdateSearch = true;
|
||||
}
|
||||
|
||||
// 强制设置历史版本
|
||||
// Force setting of historical version
|
||||
if (setVersion) {
|
||||
newSearchParams.delete('set_version');
|
||||
newSearchParams.delete('version');
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import { usePageParams } from './hooks/use-page-params';
|
||||
import { useNavigateBack } from './hooks';
|
||||
|
||||
// 添加节点放在工具栏了,原来侧边栏不需要了
|
||||
// The added node is placed in the toolbar, but the original sidebar is no longer needed.
|
||||
const EmptySidebar = React.forwardRef<AddNodeRef, unknown>(
|
||||
(_props, _addNodeRef) => null,
|
||||
);
|
||||
@@ -48,7 +48,7 @@ export function WorkflowPage(): React.ReactNode {
|
||||
const [initOnce, setInitOnce] = useState(false);
|
||||
const { navigateBack } = useNavigateBack();
|
||||
|
||||
/** 是否只读模式, 来源于流程探索模块 */
|
||||
/** Whether it is read-only mode, derived from the process exploration module */
|
||||
const readonly = from === 'explore';
|
||||
|
||||
if (!workflowId || !spaceId) {
|
||||
@@ -75,14 +75,14 @@ export function WorkflowPage(): React.ReactNode {
|
||||
});
|
||||
}
|
||||
|
||||
// onInit可能被多次调用 这里只需要执行一次
|
||||
// onInit may be called multiple times, it only needs to be executed once
|
||||
if (!initOnce) {
|
||||
// 读取链接上的node_id参数 滚动到对应节点
|
||||
// Read the node_id parameters on the link and scroll to the corresponding node
|
||||
if (nodeId) {
|
||||
workflowPlaygroundRef.current?.scrollToNode(nodeId);
|
||||
}
|
||||
|
||||
// 读取execute_id 展示对应执行结果
|
||||
// Read execute_id show the corresponding execution result
|
||||
if (executeId) {
|
||||
workflowPlaygroundRef.current?.showTestRunResult(
|
||||
executeId,
|
||||
|
||||
@@ -18,79 +18,79 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
interface IAudioPlayerOptions {
|
||||
// 是否自动播放
|
||||
// Whether to play automatically
|
||||
autoPlay?: boolean;
|
||||
// 是否循环播放
|
||||
// Whether to loop
|
||||
loop?: boolean;
|
||||
// 播放结束回调
|
||||
// end of play callback
|
||||
onEnded?: () => void;
|
||||
// 加载完成回调
|
||||
// load completion callback
|
||||
onLoad?: () => void;
|
||||
}
|
||||
|
||||
interface IAudioPlayer {
|
||||
// 是否正在播放
|
||||
// Is it playing?
|
||||
isPlaying: boolean;
|
||||
// 是否暂停
|
||||
// Whether to pause
|
||||
isPaused: boolean;
|
||||
// 是否停止
|
||||
// Whether to stop
|
||||
isStopped: boolean;
|
||||
// 当前播放时间
|
||||
// current playing time
|
||||
currentTime: number;
|
||||
// 音频总时长
|
||||
// total audio duration
|
||||
duration: number;
|
||||
// 播放
|
||||
// play
|
||||
play: () => void;
|
||||
// 暂停
|
||||
// pause
|
||||
pause: () => void;
|
||||
// 停止
|
||||
// Stop
|
||||
stop: () => void;
|
||||
// 切换播放暂停
|
||||
// toggle playback pause
|
||||
togglePlayPause: () => void;
|
||||
// 跳转
|
||||
// jump
|
||||
seek: (time: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频播放器 Hook
|
||||
* @param src - 音频文件的
|
||||
* @param options - 播放器配置选项
|
||||
* @returns 音频播放器控制接口
|
||||
* Audio Player Hook
|
||||
* @Param src - audio files
|
||||
* @Param options - player configuration options
|
||||
* @Returns Audio Player Control Interface
|
||||
*/
|
||||
export const useAudioPlayer = (
|
||||
src: File | string | undefined | null,
|
||||
{ autoPlay, loop, onEnded, onLoad }: IAudioPlayerOptions = {},
|
||||
): IAudioPlayer => {
|
||||
// 播放状态管理
|
||||
// playback state management
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [isStopped, setIsStopped] = useState(true);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
|
||||
// 音频元素引用
|
||||
// audio element reference
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
// 初始化音频元素和事件监听
|
||||
// Initialize audio elements and event listeners
|
||||
useEffect(() => {
|
||||
if (!src) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = src instanceof Blob ? URL.createObjectURL(src) : src;
|
||||
// 创建音频实例
|
||||
// Create audio instance
|
||||
const audio = new Audio(url);
|
||||
audioRef.current = audio;
|
||||
|
||||
// 设置循环播放
|
||||
// Set up loop playback
|
||||
audio.loop = loop ?? false;
|
||||
|
||||
// 更新当前播放时间的处理函数
|
||||
// Update the handler for the current playback time
|
||||
const handleTimeUpdate = () => {
|
||||
setCurrentTime(audio.currentTime);
|
||||
};
|
||||
|
||||
// 元数据加载完成的处理函数(获取音频时长等信息)
|
||||
// The processing function for the completion of metadata loading (to obtain information such as audio duration)
|
||||
const handleLoadedMetadata = () => {
|
||||
setDuration(audio.duration);
|
||||
setCurrentTime(0);
|
||||
@@ -101,12 +101,12 @@ export const useAudioPlayer = (
|
||||
}
|
||||
|
||||
if (src instanceof Blob) {
|
||||
// 释放音频文件的 URL,避免内存泄漏
|
||||
// Release the URL of the audio file to avoid memory leaks
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
};
|
||||
|
||||
// 播放结束的处理函数
|
||||
// Handler function for end of playback
|
||||
const handleEnded = () => {
|
||||
setIsPlaying(false);
|
||||
setIsStopped(true);
|
||||
@@ -114,12 +114,12 @@ export const useAudioPlayer = (
|
||||
onEnded?.();
|
||||
};
|
||||
|
||||
// 添加事件监听器
|
||||
// Add event listeners
|
||||
audio.addEventListener('loadedmetadata', handleLoadedMetadata);
|
||||
audio.addEventListener('timeupdate', handleTimeUpdate);
|
||||
audio.addEventListener('ended', handleEnded);
|
||||
|
||||
// 清理函数:组件卸载时移除事件监听并释放资源
|
||||
// Cleanup function: removes event listeners and frees resources when a component is unloaded
|
||||
return () => {
|
||||
audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
|
||||
audio.removeEventListener('timeupdate', handleTimeUpdate);
|
||||
@@ -131,7 +131,7 @@ export const useAudioPlayer = (
|
||||
};
|
||||
}, [src, autoPlay, loop, onEnded, onLoad]);
|
||||
|
||||
// 播放控制函数
|
||||
// playback control function
|
||||
const play = useCallback(() => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.play();
|
||||
@@ -141,7 +141,7 @@ export const useAudioPlayer = (
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 暂停控制函数
|
||||
// pause control function
|
||||
const pause = useCallback(() => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.pause();
|
||||
@@ -150,7 +150,7 @@ export const useAudioPlayer = (
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 停止控制函数
|
||||
// Stop control function
|
||||
const stop = useCallback(() => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.pause();
|
||||
@@ -161,7 +161,7 @@ export const useAudioPlayer = (
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 切换播放/暂停状态
|
||||
// Switch play/pause state
|
||||
const togglePlayPause = useCallback(() => {
|
||||
if (isPlaying) {
|
||||
pause();
|
||||
@@ -170,7 +170,7 @@ export const useAudioPlayer = (
|
||||
}
|
||||
}, [isPlaying, play, pause]);
|
||||
|
||||
// 跳转到指定时间
|
||||
// Jump to the specified time
|
||||
const seek = useCallback((time: number) => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.currentTime = time;
|
||||
@@ -178,7 +178,7 @@ export const useAudioPlayer = (
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 返回音频播放器控制接口
|
||||
// Back to Audio Player Control Interface
|
||||
return {
|
||||
isPlaying,
|
||||
isPaused,
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
|
||||
describe('with-query-client.tsx', () => {
|
||||
beforeEach(() => {
|
||||
// 清理 QueryClient 缓存
|
||||
// Clean up the QueryClient cache
|
||||
workflowQueryClient.clear();
|
||||
});
|
||||
|
||||
@@ -89,10 +89,10 @@ describe('with-query-client.tsx', () => {
|
||||
const WrappedComponent = withQueryClient(TestComponent);
|
||||
const { container } = render(<WrappedComponent />);
|
||||
|
||||
// 初始加载状态
|
||||
// initial loading state
|
||||
expect(container.innerHTML).toContain('Loading...');
|
||||
|
||||
// 等待数据加载完成
|
||||
// Wait for data loading to complete
|
||||
await waitFor(() => {
|
||||
expect(container.innerHTML).toContain('test data');
|
||||
});
|
||||
@@ -198,10 +198,10 @@ describe('with-query-client.tsx', () => {
|
||||
const WrappedComponent = withQueryClient(ParentComponent);
|
||||
const { container } = render(<WrappedComponent />);
|
||||
|
||||
// 初始加载状态
|
||||
// initial loading state
|
||||
expect(container.innerHTML).toContain('Loading Parent...');
|
||||
|
||||
// 等待所有查询完成
|
||||
// Wait for all queries to complete
|
||||
await waitFor(() => {
|
||||
expect(container.innerHTML).toContain('parent data');
|
||||
expect(container.innerHTML).toContain('child data');
|
||||
|
||||
@@ -28,7 +28,7 @@ vi.mock('../../src/entities', async () => {
|
||||
const actual = await vi.importActual('../../src/entities');
|
||||
return {
|
||||
...actual,
|
||||
// 如果需要mock其他实体,可以在这里添加
|
||||
// If you need to mock other entities, you can add them here.
|
||||
};
|
||||
});
|
||||
|
||||
@@ -198,7 +198,7 @@ describe('WorkflowNode', () => {
|
||||
const newData = { test: 'new data' };
|
||||
workflowNode.setData(newData);
|
||||
|
||||
// 验证原始数据没有被修改
|
||||
// Verify that the original data source has not been modified
|
||||
expect(newData).toEqual({ test: 'new data' });
|
||||
expect(mockFormItem.value).toEqual({ test: 'new data' });
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ vi.mock('../../src/utils', () => ({
|
||||
describe('useNodeTestId', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// 设置 concatTestId 的默认行为
|
||||
// Set the default behavior of concatTestId
|
||||
mockConcatTestId.mockImplementation((...args: string[]) => args.join('.'));
|
||||
});
|
||||
|
||||
@@ -99,8 +99,8 @@ describe('useNodeTestId', () => {
|
||||
it('应该返回正确的节点设置器 ID', () => {
|
||||
mockUseCurrentEntity.mockReturnValue({ id: '123' });
|
||||
mockConcatTestId
|
||||
.mockReturnValueOnce('playground.node.123') // 用于 getNodeTestId
|
||||
.mockReturnValueOnce('playground.node.123.llm'); // 用于 getNodeSetterId
|
||||
.mockReturnValueOnce('playground.node.123') // For getNodeTestId
|
||||
.mockReturnValueOnce('playground.node.123.llm'); // For getNodeSetterId
|
||||
|
||||
const { result } = renderHook(() => useNodeTestId());
|
||||
|
||||
@@ -140,7 +140,7 @@ describe('useNodeTestId', () => {
|
||||
});
|
||||
|
||||
it('应该在多个组件中返回不同的节点测试 ID', () => {
|
||||
// 第一个组件
|
||||
// The first component
|
||||
mockUseCurrentEntity.mockReturnValue({ id: '123' });
|
||||
mockConcatTestId.mockReturnValueOnce('playground.node.123');
|
||||
|
||||
@@ -148,7 +148,7 @@ describe('useNodeTestId', () => {
|
||||
expect(result1.current.getNodeTestId()).toBe('playground.node.123');
|
||||
expect(mockConcatTestId).toHaveBeenCalledWith('playground.node', '123');
|
||||
|
||||
// 第二个组件
|
||||
// The second component
|
||||
mockUseCurrentEntity.mockReturnValue({ id: '456' });
|
||||
mockConcatTestId.mockReturnValueOnce('playground.node.456');
|
||||
|
||||
@@ -158,11 +158,11 @@ describe('useNodeTestId', () => {
|
||||
});
|
||||
|
||||
it('应该在多个组件中返回不同的节点设置器 ID', () => {
|
||||
// 第一个组件
|
||||
// The first component
|
||||
mockUseCurrentEntity.mockReturnValue({ id: '123' });
|
||||
mockConcatTestId
|
||||
.mockReturnValueOnce('playground.node.123') // 用于 getNodeTestId
|
||||
.mockReturnValueOnce('playground.node.123.llm'); // 用于 getNodeSetterId
|
||||
.mockReturnValueOnce('playground.node.123') // For getNodeTestId
|
||||
.mockReturnValueOnce('playground.node.123.llm'); // For getNodeSetterId
|
||||
|
||||
const { result: result1 } = renderHook(() => useNodeTestId());
|
||||
expect(result1.current.getNodeSetterId('llm')).toBe(
|
||||
@@ -179,11 +179,11 @@ describe('useNodeTestId', () => {
|
||||
'llm',
|
||||
);
|
||||
|
||||
// 第二个组件
|
||||
// The second component
|
||||
mockUseCurrentEntity.mockReturnValue({ id: '456' });
|
||||
mockConcatTestId
|
||||
.mockReturnValueOnce('playground.node.456') // 用于 getNodeTestId
|
||||
.mockReturnValueOnce('playground.node.456.llm'); // 用于 getNodeSetterId
|
||||
.mockReturnValueOnce('playground.node.456') // For getNodeTestId
|
||||
.mockReturnValueOnce('playground.node.456.llm'); // For getNodeSetterId
|
||||
|
||||
const { result: result2 } = renderHook(() => useNodeTestId());
|
||||
expect(result2.current.getNodeSetterId('llm')).toBe(
|
||||
|
||||
@@ -22,7 +22,7 @@ import { useWorkflowStore } from '../../../src/store/workflow';
|
||||
|
||||
describe('useWorkflowStore', () => {
|
||||
beforeEach(() => {
|
||||
// 重置 store 状态
|
||||
// Reset store state
|
||||
useWorkflowStore.setState({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
@@ -63,7 +63,7 @@ describe('useWorkflowStore', () => {
|
||||
});
|
||||
|
||||
it('应该能够重置为空数组', () => {
|
||||
// 先设置一些数据
|
||||
// Let's set up some data.
|
||||
useWorkflowStore.getState().setNodes([
|
||||
{
|
||||
id: '1',
|
||||
@@ -75,7 +75,7 @@ describe('useWorkflowStore', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
// 设置为空数组
|
||||
// Set to empty array
|
||||
useWorkflowStore.getState().setNodes([]);
|
||||
expect(useWorkflowStore.getState().nodes).toEqual([]);
|
||||
});
|
||||
@@ -99,7 +99,7 @@ describe('useWorkflowStore', () => {
|
||||
});
|
||||
|
||||
it('应该能够重置为空数组', () => {
|
||||
// 先设置一些数据
|
||||
// Let's set up some data.
|
||||
useWorkflowStore.getState().setEdges([
|
||||
{
|
||||
sourceNodeID: '1',
|
||||
@@ -107,7 +107,7 @@ describe('useWorkflowStore', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
// 设置为空数组
|
||||
// Set to empty array
|
||||
useWorkflowStore.getState().setEdges([]);
|
||||
expect(useWorkflowStore.getState().edges).toEqual([]);
|
||||
});
|
||||
@@ -115,11 +115,11 @@ describe('useWorkflowStore', () => {
|
||||
|
||||
describe('setIsCreatingWorkflow', () => {
|
||||
it('应该正确设置创建状态', () => {
|
||||
// 设置为 true
|
||||
// Set to true
|
||||
useWorkflowStore.getState().setIsCreatingWorkflow(true);
|
||||
expect(useWorkflowStore.getState().isCreatingWorkflow).toBe(true);
|
||||
|
||||
// 设置为 false
|
||||
// Set to false
|
||||
useWorkflowStore.getState().setIsCreatingWorkflow(false);
|
||||
expect(useWorkflowStore.getState().isCreatingWorkflow).toBe(false);
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
describe('node-type', () => {
|
||||
describe('StandardNodeType', () => {
|
||||
it('应该包含所有预定义的节点类型', () => {
|
||||
// 测试一些关键的节点类型
|
||||
// Test some key node types
|
||||
expect(StandardNodeType.Start).toBe('1');
|
||||
expect(StandardNodeType.End).toBe('2');
|
||||
expect(StandardNodeType.LLM).toBe('3');
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('is-general-workflow', () => {
|
||||
});
|
||||
|
||||
it('应该在 flowMode 为其他值时返回 false', () => {
|
||||
// 测试其他可能的 WorkflowMode 值
|
||||
// Test other possible WorkflowMode values
|
||||
expect(isGeneralWorkflow(WorkflowMode.Imageflow)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,17 +27,17 @@ vi.mock('@coze-arch/bot-utils', () => ({
|
||||
typeSafeJSONParse: (str: string) => {
|
||||
try {
|
||||
const result = JSON.parse(str);
|
||||
// 如果是批处理数据,确保返回数组类型
|
||||
// If it is batch data, make sure to return an array type
|
||||
if (str === 'invalid json') {
|
||||
return str;
|
||||
}
|
||||
// 如果是批处理数据,确保返回数组类型
|
||||
// If it is batch data, make sure to return an array type
|
||||
if (str.includes('batch')) {
|
||||
return Array.isArray(result) ? result : [];
|
||||
}
|
||||
return result;
|
||||
} catch {
|
||||
// 如果是批处理数据,返回空数组
|
||||
// If it is batch data, return an empty array
|
||||
if (str.includes('batch')) {
|
||||
return [];
|
||||
}
|
||||
@@ -110,7 +110,7 @@ describe('default-parser', () => {
|
||||
expect(result.nodeType).toBe(StandardNodeType.LLM);
|
||||
expect(result.isBatch).toBe(false);
|
||||
expect(result.caseResult).toHaveLength(1);
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(3); // 输入、原始输出、最终输出
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(3); // Input, original output, final output
|
||||
});
|
||||
|
||||
it('应该正确解析 Code 节点结果', () => {
|
||||
@@ -122,7 +122,7 @@ describe('default-parser', () => {
|
||||
const result = defaultParser(nodeResult, workflowSchema);
|
||||
|
||||
expect(result.nodeType).toBe(StandardNodeType.Code);
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(3); // 输入、原始输出、最终输出
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(3); // Input, original output, final output
|
||||
});
|
||||
|
||||
it('应该正确解析 Start 节点结果', () => {
|
||||
@@ -134,7 +134,7 @@ describe('default-parser', () => {
|
||||
const result = defaultParser(nodeResult, workflowSchema);
|
||||
|
||||
expect(result.nodeType).toBe(StandardNodeType.Start);
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(1); // 只有输入
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(1); // input only
|
||||
});
|
||||
|
||||
it('应该正确解析 End 节点结果', () => {
|
||||
@@ -154,7 +154,7 @@ describe('default-parser', () => {
|
||||
const result = defaultParser(nodeResult, workflowSchema);
|
||||
|
||||
expect(result.nodeType).toBe(StandardNodeType.End);
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(2); // 输出变量、回答内容
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(2); // Output variables, answer content
|
||||
});
|
||||
|
||||
it('应该正确解析 Message 节点结果', () => {
|
||||
@@ -166,7 +166,7 @@ describe('default-parser', () => {
|
||||
const result = defaultParser(nodeResult, workflowSchema);
|
||||
|
||||
expect(result.nodeType).toBe(StandardNodeType.Output);
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(2); // 输出变量、回答内容
|
||||
expect(result.caseResult?.[0].dataList).toHaveLength(2); // Output variables, answer content
|
||||
});
|
||||
|
||||
it('应该正确解析包含图片的输出', () => {
|
||||
@@ -256,7 +256,7 @@ describe('default-parser', () => {
|
||||
|
||||
it('应该正确处理 Text 节点的原始输出', () => {
|
||||
const nodeResult = createMockNodeResult('1', {
|
||||
raw_output: '{"key": "value"}', // 即使是有效的 JSON 字符串也应该保持原样
|
||||
raw_output: '{"key": "value"}', // Even valid JSON strings should remain intact
|
||||
});
|
||||
const workflowSchema = createMockWorkflowSchema(
|
||||
'1',
|
||||
@@ -272,7 +272,7 @@ describe('default-parser', () => {
|
||||
|
||||
it('应该正确处理不存在的节点类型', () => {
|
||||
const nodeResult = createMockNodeResult('1');
|
||||
const workflowSchema = createMockWorkflowSchema('2'); // 不同的节点 ID
|
||||
const workflowSchema = createMockWorkflowSchema('2'); // Different node IDs
|
||||
const result = defaultParser(nodeResult, workflowSchema);
|
||||
|
||||
expect(result.nodeType).toBeUndefined();
|
||||
|
||||
@@ -184,7 +184,7 @@ describe('ref-input-parameters-parser', () => {
|
||||
param4: undefined,
|
||||
param5: {
|
||||
type: 'string',
|
||||
value: {}, // 空的 value 对象
|
||||
value: {}, // Empty value object
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -21,7 +21,7 @@ export * from '@coze-arch/bot-api/workflow_api';
|
||||
|
||||
export { withQueryClient, workflowQueryClient } from './with-query-client';
|
||||
|
||||
/** 运营接口平台,会替换权限验证 */
|
||||
/** Operating the interface platform will replace permission verification */
|
||||
const workflowOperationApiNameMap = {
|
||||
GetHistorySchema: 'OPGetHistorySchema',
|
||||
GetWorkFlowProcess: 'OPGetWorkFlowProcess',
|
||||
|
||||
@@ -14,35 +14,35 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** 空方法 */
|
||||
/** empty method */
|
||||
export const EmptyFunction = () => {
|
||||
/** 空方法 */
|
||||
/** empty method */
|
||||
};
|
||||
export const EmptyAsyncFunction = () => Promise.resolve();
|
||||
|
||||
/** 公共空间 ID */
|
||||
/** Public space ID */
|
||||
export const PUBLIC_SPACE_ID = '999999';
|
||||
|
||||
/** BOT_USER_INPUT 变量名 */
|
||||
/** BOT_USER_INPUT variable name */
|
||||
export const BOT_USER_INPUT = 'BOT_USER_INPUT';
|
||||
|
||||
/** USER_INPUT 参数,新版 BOT_USER_INPUT 参数,作用和 BOT_USER_INPUT 相同,Coze2.0 Chatflow 需求引入 */
|
||||
/** USER_INPUT parameters, the new version BOT_USER_INPUT parameters, the same function as BOT_USER_INPUT, Coze2.0 Chatflow requirements are introduced */
|
||||
export const USER_INPUT = 'USER_INPUT';
|
||||
|
||||
/** CONVERSATION_NAME 变量名,start 节点会话名称入参 */
|
||||
/** CONVERSATION_NAME variable name, start node session name imported parameter */
|
||||
export const CONVERSATION_NAME = 'CONVERSATION_NAME';
|
||||
|
||||
/**
|
||||
* workflow 名称最大字符数
|
||||
* Workflow name Maximum number of characters
|
||||
*/
|
||||
export const WORKFLOW_NAME_MAX_LEN = 30;
|
||||
|
||||
/**
|
||||
* 工作流命名正则
|
||||
* workflow naming regular
|
||||
*/
|
||||
export const WORKFLOW_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]{0,63}$/;
|
||||
|
||||
/**
|
||||
* 节点测试ID前缀
|
||||
* Node test ID prefix
|
||||
*/
|
||||
export const NODE_TEST_ID_PREFIX = 'playground.node';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 业务层流程节点类 (对节点业务规则的封装)
|
||||
* Business layer process node class (encapsulation of node business rules)
|
||||
*/
|
||||
import {
|
||||
FlowNodeErrorData,
|
||||
@@ -69,7 +69,7 @@ export class WorkflowNode {
|
||||
}
|
||||
}
|
||||
|
||||
// 这个方法建议暂时先不要使用 原因是该方法没有体现业务逻辑 只是底层方法的简化
|
||||
// This method is recommended not to be used for the time being, because it does not reflect business logic, but is a simplification of the underlying method
|
||||
protected getFormValueByPathEnds<T = unknown>(
|
||||
pathEnds: string,
|
||||
): T | undefined {
|
||||
|
||||
@@ -21,21 +21,21 @@ import { concatTestId } from '../utils';
|
||||
import { NODE_TEST_ID_PREFIX } from '../constants';
|
||||
|
||||
/**
|
||||
* 仅限在 node 内使用
|
||||
* Only used within the node
|
||||
*/
|
||||
type UseNodeTestId = () => {
|
||||
/**
|
||||
* 返回当前节点的 test-id,也就是当前节点的 node id
|
||||
* Returns the test-id of the current node, which is the node id of the current node.
|
||||
* 'playground.11001
|
||||
*/
|
||||
getNodeTestId: () => string;
|
||||
/**
|
||||
* 返回当前 setter 的 test-id,会自动带上节点的 test-id
|
||||
* Returns the test-id of the current setter, which will automatically bring the test-id of the node.
|
||||
* 'playground.11001.llm'
|
||||
*/
|
||||
getNodeSetterId: (setterName: string) => string;
|
||||
/**
|
||||
* 连接两个 test-id,生成一个新的 test-id
|
||||
* Connect two test-ids to generate a new test-id.
|
||||
* ('a', 'b') => 'a.b'
|
||||
*/
|
||||
concatTestId: typeof concatTestId;
|
||||
@@ -55,17 +55,17 @@ export const useNodeTestId: UseNodeTestId = () => {
|
||||
|
||||
return {
|
||||
/**
|
||||
* 返回当前节点的 test-id,也就是当前节点的 node id
|
||||
* Returns the test-id of the current node, which is the node id of the current node.
|
||||
* 'playground.11001
|
||||
*/
|
||||
getNodeTestId,
|
||||
/**
|
||||
* 返回当前 setter 的 test-id,会自动带上节点的 test-id
|
||||
* Returns the test-id of the current setter, which will automatically bring the test-id of the node.
|
||||
* 'playground.11001.llm'
|
||||
*/
|
||||
getNodeSetterId: setterName => concatTestId(getNodeTestId(), setterName),
|
||||
/**
|
||||
* 连接两个 test-id,生成一个新的 test-id
|
||||
* Connect two test-ids to generate a new test-id.
|
||||
* ('a', 'b') => 'a.b'
|
||||
*/
|
||||
concatTestId,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 抽象类
|
||||
* abstract class
|
||||
*/
|
||||
export {
|
||||
ValidationService,
|
||||
|
||||
@@ -29,20 +29,20 @@ export const useValidationServiceStore = <T>(
|
||||
) => useValidationService().store(selector);
|
||||
|
||||
export interface ValidateError {
|
||||
// 错误描述
|
||||
// error description
|
||||
errorInfo: string;
|
||||
// 错误等级
|
||||
// error level
|
||||
errorLevel: FeedbackStatus;
|
||||
// 错误类型: 节点 / 连线
|
||||
// Error Type: Node/Connection
|
||||
errorType: 'node' | 'line';
|
||||
// 节点id
|
||||
// Node ID
|
||||
nodeId: string;
|
||||
// 若为连线错误,还需要目标节点来确认这条连线
|
||||
// In the case of a connection error, the target node is also required to confirm the connection
|
||||
targetNodeId?: string;
|
||||
}
|
||||
export interface WorkflowValidateError {
|
||||
workflowId: string;
|
||||
/** 流程名 */
|
||||
/** process name */
|
||||
name?: string;
|
||||
errors: ValidateErrorMap;
|
||||
}
|
||||
@@ -61,12 +61,12 @@ export interface ValidateResultV2 {
|
||||
|
||||
export interface ValidationState {
|
||||
/**
|
||||
* @deprecated 请使用 errorsV2
|
||||
* @deprecated Please use errorsV2
|
||||
*/
|
||||
errors: ValidateErrorMap;
|
||||
/** 按照流程归属分类的错误 */
|
||||
/** Errors classified by process attribution */
|
||||
errorsV2: WorkflowValidateErrorMap;
|
||||
/** 正在校验中 */
|
||||
/** Verifying now. */
|
||||
validating: boolean;
|
||||
}
|
||||
|
||||
@@ -74,48 +74,48 @@ export interface ValidationService {
|
||||
store: UseBoundStoreWithEqualityFn<StoreApi<ValidationState>>;
|
||||
|
||||
/**
|
||||
* 前端流程校验,包括节点、表单、端口等
|
||||
* Front-end process validation, including nodes, forms, ports, etc
|
||||
*/
|
||||
validateWorkflow: () => Promise<ValidateResult>;
|
||||
|
||||
/**
|
||||
* 对节点的校验,包括表单、端口等
|
||||
* Verification of nodes, including forms, ports, etc
|
||||
*/
|
||||
validateNode: (node: WorkflowNodeEntity) => Promise<ValidateResult>;
|
||||
|
||||
/**
|
||||
* @deprecated 请使用 validateSchemaV2
|
||||
* 流程定义合法性校验,通常为后端校验
|
||||
* @Deprecated please use validateSchemaV2
|
||||
* Process definition validation, usually backend validation
|
||||
*/
|
||||
validateSchema: () => Promise<ValidateResult>;
|
||||
/**
|
||||
* 流程定义合法性校验,通常为后端校验
|
||||
* Process definition validation, usually backend validation
|
||||
*/
|
||||
validateSchemaV2: () => Promise<ValidateResultV2>;
|
||||
|
||||
/**
|
||||
* 获取指定 id 的错误列表
|
||||
* Gets the error list for the specified id
|
||||
*/
|
||||
getErrors: (id: string) => ValidateError[];
|
||||
/**
|
||||
* @deprecated 请使用 setErrorsV2
|
||||
* 设置错误
|
||||
* @deprecated Please use setErrorsV2
|
||||
* setting error
|
||||
* @param errors
|
||||
* @param force 强制覆盖所有错误
|
||||
* @param force to overwrite all errors
|
||||
*/
|
||||
setErrors: (errors: ValidationState['errors'], force?: boolean) => void;
|
||||
|
||||
/**
|
||||
* 设置错误
|
||||
* setting error
|
||||
* @param errors
|
||||
* @returns
|
||||
*/
|
||||
setErrorsV2: (errors: ValidationState['errorsV2']) => void;
|
||||
/**
|
||||
* 清空所有错误
|
||||
* Clear all errors
|
||||
*/
|
||||
clearErrors: () => void;
|
||||
/** 线条是否存在错误 */
|
||||
/** Is there an error in the line? */
|
||||
isLineError: (fromId: string, toId?: string) => boolean;
|
||||
|
||||
get validating(): boolean;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// workflow store,目前保存 flow 的 nodes 和 edges 数据
|
||||
// Workflow store, currently holds the nodes and edges data of the flow
|
||||
|
||||
import { devtools } from 'zustand/middleware';
|
||||
import { create } from 'zustand';
|
||||
@@ -24,13 +24,13 @@ import {
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
interface WorkflowStoreState {
|
||||
/** 节点数据 */
|
||||
/** node data */
|
||||
nodes: WorkflowNodeJSON[];
|
||||
|
||||
/** 边数据 */
|
||||
/** edge data */
|
||||
edges: WorkflowEdgeJSON[];
|
||||
|
||||
/** 是否在创建 workflow */
|
||||
/** Are you creating a workflow? */
|
||||
isCreatingWorkflow: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ import { ViewVariableType } from './view-variable-type';
|
||||
import { type DTODefine, type InputValueDTO } from './dto';
|
||||
|
||||
/**
|
||||
* BlockInput是后端定义的类型,对应的就是 InputValueDTO
|
||||
* BlockInput is a type defined by the backend, which corresponds to InputValueDTO.
|
||||
*/
|
||||
export type BlockInput = InputValueDTO;
|
||||
|
||||
/**
|
||||
* BlockInput 转换方法
|
||||
* BlockInput conversion method
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace BlockInput {
|
||||
|
||||
@@ -26,7 +26,7 @@ export enum ConditionLogicDTO {
|
||||
|
||||
export type ConditionOperator =
|
||||
| 'EQUAL' // "="
|
||||
| 'NOT_EQUAL' // "<>" 或 "!="
|
||||
| 'NOT_EQUAL' // "< >" or "! ="
|
||||
| 'GREATER_THAN' // ">"
|
||||
| 'LESS_THAN' // "<"
|
||||
| 'GREATER_EQUAL' // ">="
|
||||
@@ -35,7 +35,7 @@ export type ConditionOperator =
|
||||
| 'NOT_IN' // "NOT IN"
|
||||
| 'IS_NULL' // "IS NULL"
|
||||
| 'IS_NOT_NULL' // "IS NOT NULL"
|
||||
| 'LIKE' // "LIKE" 模糊匹配字符串
|
||||
| 'NOT_LIKE' // "NOT LIKE" 反向模糊匹配
|
||||
| 'BE_TRUE' // "BE TRUE" 布尔值为 true
|
||||
| 'BE_FALSE'; // "BE FALSE" 布尔值为 false
|
||||
| 'LIKE' // "LIKE" fuzzy match string
|
||||
| 'NOT_LIKE' // "NOT LIKE" inverse fuzzy match
|
||||
| 'BE_TRUE' // "BE TRUE" is true
|
||||
| 'BE_FALSE'; // "BE FALSE" Boolean value false
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface WorkflowDatabase {
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库配置字段
|
||||
* Database configuration field
|
||||
*/
|
||||
export interface DatabaseSettingField {
|
||||
fieldID: number;
|
||||
@@ -66,7 +66,7 @@ export type DatabaseSettingFieldDTO = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 数据库条件
|
||||
* database conditions
|
||||
*/
|
||||
export type DatabaseConditionOperator = ConditionOperator;
|
||||
export type DatabaseConditionLeft = string;
|
||||
@@ -89,7 +89,7 @@ export interface DatabaseConditionLeftDTO {
|
||||
}
|
||||
|
||||
export interface DatabaseConditionOperatorDTO {
|
||||
// 对操作符的翻译前后端没有统一
|
||||
// Translation of operators is not uniform between front and back ends
|
||||
name: 'operation';
|
||||
input: {
|
||||
type: 'string';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 这些是迁移的变量定义,可以不用关注
|
||||
* These are the variable definitions for migration, you can ignore them.
|
||||
*/
|
||||
|
||||
import { type ValueExpressionRawMeta } from './vo';
|
||||
@@ -63,7 +63,7 @@ export namespace DTODefine {
|
||||
rawMeta?: ValueExpressionRawMeta;
|
||||
}
|
||||
|
||||
// 当 schema 为 string 时表示原始的 FDL 类型export type ListVariableSchema = VariableTypeDef & Omit<VariableOption, 'name'>
|
||||
// Represents the original FDL type when schema is string export type ListVariableSchema = VariableTypeDef & Omit < VariableOption, 'name' >
|
||||
export type ObjectVariableSchema = InputVariableDTO[];
|
||||
export type ListVariableSchema = VariableTypeDef &
|
||||
Omit<VariableOption, 'name'>;
|
||||
@@ -109,21 +109,21 @@ export namespace DTODefine {
|
||||
}
|
||||
|
||||
/**
|
||||
* 后端定义的表达式格式
|
||||
* Expression formats defined by the backend
|
||||
* @example
|
||||
* - literal
|
||||
* {
|
||||
* type: 'string',
|
||||
* value: {
|
||||
* type: 'liteal',
|
||||
* content: '浙江'
|
||||
* Content: 'Zhejiang'
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* - ref
|
||||
* // 普通引用类型
|
||||
* //Common reference typery reference type
|
||||
* {
|
||||
* type: 'string', // 由引用的变量类型判断
|
||||
* Type: 'string',//Determined by the type of the referenced variable the type of variable being referenced
|
||||
* value: {
|
||||
* type: 'ref',
|
||||
* content: {
|
||||
@@ -134,10 +134,10 @@ export namespace DTODefine {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // list or object 引用类型
|
||||
* //list or object reference typerence type
|
||||
* {
|
||||
* type: 'list', // 由引用的变量路径的最后一个值类型判断, 如果list.a.c, 则为c的格式
|
||||
* schema: { // 只有list和object有schema
|
||||
* Type: 'list',//Determined by the last value type of the referenced variable path, if list.a.c, the format of che referenced variable path, if list.a.c, the format of c
|
||||
* Schema : { // only list and object have schemasts have schemas
|
||||
* type: 'object',
|
||||
* schema: [
|
||||
* { name: 'role', type: 'string' },
|
||||
@@ -149,7 +149,7 @@ export namespace DTODefine {
|
||||
* content: {
|
||||
* source: 'block-output',
|
||||
* blockID: '1002',
|
||||
* name: 'list.a.c' // 这里存的是引用的路径
|
||||
* Name: 'list.a.c'//Here is the path of the referencee path to the reference
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
@@ -168,7 +168,7 @@ export interface ValueExpressionDTO {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace ValueExpressionDTO {
|
||||
/**
|
||||
* 空引用
|
||||
* null reference
|
||||
*/
|
||||
export function createEmpty(): ValueExpressionDTO {
|
||||
return {
|
||||
@@ -219,7 +219,7 @@ export enum AssistTypeDTO {
|
||||
}
|
||||
|
||||
/**
|
||||
* 后端的变量格式
|
||||
* Variable format for the backend
|
||||
* @example
|
||||
* 1. simple
|
||||
* {
|
||||
@@ -256,19 +256,19 @@ export enum AssistTypeDTO {
|
||||
*/
|
||||
export interface VariableMetaDTO {
|
||||
/**
|
||||
* 变量类型
|
||||
* Variable type
|
||||
*/
|
||||
type: VariableTypeDTO;
|
||||
/**
|
||||
* 辅助类型,如:string 类型的变量可以是 file 或者 image
|
||||
* Auxiliary types, such as: string type variables can be file or image
|
||||
*/
|
||||
assistType?: AssistTypeDTO;
|
||||
/**
|
||||
* 变量名,在节点内不可重复
|
||||
* Variable names are not repeatable within a node
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 变量数据结构,仅object 和 list 类型变量有
|
||||
* Variable data structures, only object and list variables are available
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
schema?: any; // BlockVariableDefine.ObjectVariableSchema | BlockVariableDefine.ListVariableSchema
|
||||
@@ -282,7 +282,7 @@ export interface VariableMetaDTO {
|
||||
export interface BatchDTOInputList {
|
||||
name?: string;
|
||||
input: ValueExpressionDTO;
|
||||
// 初始化后存在,服务端数据不存在
|
||||
// Exists after initialization, server level data does not exist
|
||||
id?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ export {
|
||||
type BasicStandardNodeTypes,
|
||||
} from './node-type';
|
||||
export { type WorkflowJSON, type WorkflowNodeJSON } from './node';
|
||||
// !Notice data-set.ts 在用 io-ts 做运行时类型检查,禁止直接导出,避免 io-ts 被打到其它页面中
|
||||
// ! Notice data-set.ts is using io-ts for runtime type checking, and direct export is prohibited to avoid io-ts being hit into other pages
|
||||
// export { datasetParams, type DatasetParams } from './data-set';
|
||||
import { InputType } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
@@ -78,7 +78,7 @@ import {
|
||||
import { AssistTypeDTO } from './dto';
|
||||
|
||||
export { type ValueOf, type WithCustomStyle } from './utils';
|
||||
// 和后端定义撞了,注释
|
||||
// Bumped with the back-end definition, comment
|
||||
export { type WorkflowInfo as FrontWorkflowInfo } from './workflow';
|
||||
|
||||
export {
|
||||
@@ -88,8 +88,8 @@ export {
|
||||
} from './registry';
|
||||
|
||||
/**
|
||||
* 参数类型展示文案
|
||||
* @tips workflow 编辑页请使用 {PARAM_TYPE_ALIAS_MAP}
|
||||
* Parameter type display copy
|
||||
* @Tips workflow edit page please use {PARAM_TYPE_ALIAS_MAP}
|
||||
*/
|
||||
export const PARAM_TYPE_LABEL_MAP: Record<InputType, string> = {
|
||||
[InputType.String]: 'String',
|
||||
@@ -116,9 +116,9 @@ export const STRING_ASSIST_TYPE_LABEL_MAP = {
|
||||
|
||||
export enum WorkflowExecStatus {
|
||||
DEFAULT = 'default',
|
||||
/** 执行中 */
|
||||
/** in progress */
|
||||
EXECUTING = 'executing',
|
||||
/** 执行结束(此时依然有执行结束 banner,且工作流为 disable 状态) */
|
||||
/** End of execution (there is still an end of execution banner at this time, and the workflow is disabled) */
|
||||
DONE = 'done',
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// 跟后端约定好的协议,workflow 后端不感知。对应 api/bot/get_type_list 接口中 response.data.modal_list[*].model_params[*].default 的 key
|
||||
// The agreement agreed with the backend is not perceived by the workflow backend. Corresponding to the key response.data modal_list [*] .model_params [*] .default in the api/bot/get_type_list interface
|
||||
export enum GenerationDiversity {
|
||||
Customize = 'default_val',
|
||||
Creative = 'creative',
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 节点基础类型定义
|
||||
* Node base type definition
|
||||
*/
|
||||
export enum StandardNodeType {
|
||||
Start = '1',
|
||||
@@ -38,32 +38,32 @@ export enum StandardNodeType {
|
||||
SetVariable = '20',
|
||||
Loop = '21',
|
||||
Intent = '22',
|
||||
// 对于新节点,可以使用文本的形式来编写 StandardNodeType,不再依赖 NodeTemplateType
|
||||
// For new nodes, StandardNodeType can be written in text form, without relying on NodeTemplateType.
|
||||
ImageCanvas = '23',
|
||||
SceneChat = '25',
|
||||
SceneVariable = '24',
|
||||
|
||||
/** 长期记忆,long term memory */
|
||||
/** Long term memory */
|
||||
LTM = '26',
|
||||
/** 数据库写入节点 */
|
||||
/** database write node */
|
||||
DatasetWrite = '27',
|
||||
Batch = '28',
|
||||
|
||||
Continue = '29',
|
||||
// 输入节点
|
||||
// input node
|
||||
Input = '30',
|
||||
|
||||
Comment = '31',
|
||||
// 变量聚合
|
||||
// variable aggregation
|
||||
VariableMerge = '32',
|
||||
// 查询消息列表
|
||||
// Query message list
|
||||
QueryMessageList = '37',
|
||||
// 清空上下文节点
|
||||
// clear the context node
|
||||
ClearContext = '38',
|
||||
// 创建会话节点
|
||||
// Create a session node
|
||||
CreateConversation = '39',
|
||||
|
||||
// 触发器 CURD 4个节点
|
||||
// Trigger CURD 4 nodes
|
||||
// TriggerCreate = '33',
|
||||
// TriggerUpdate = '34',
|
||||
TriggerUpsert = '34',
|
||||
@@ -72,34 +72,34 @@ export enum StandardNodeType {
|
||||
|
||||
VariableAssign = '40',
|
||||
|
||||
// http 节点
|
||||
// HTTP Node
|
||||
Http = '45',
|
||||
// 数据库 crud 节点
|
||||
// Database crud node
|
||||
|
||||
DatabaseUpdate = '42',
|
||||
DatabaseQuery = '43',
|
||||
DatabaseDelete = '44',
|
||||
DatabaseCreate = '46',
|
||||
|
||||
// 更新会话
|
||||
// Update session
|
||||
UpdateConversation = '51',
|
||||
|
||||
// 删除会话
|
||||
// Delete session
|
||||
DeleteConversation = '52',
|
||||
|
||||
// 查询会话列表
|
||||
// Query session list
|
||||
QueryConversationList = '53',
|
||||
|
||||
// 查询会话历史
|
||||
// Query session history
|
||||
QueryConversationHistory = '54',
|
||||
|
||||
// 创建消息(某个会话)
|
||||
// Create a message (a conversation)
|
||||
CreateMessage = '55',
|
||||
|
||||
// 更新消息(某个会话的某个消息)
|
||||
// Update message (a message for a session)
|
||||
UpdateMessage = '56',
|
||||
|
||||
// 删除消息(某个会话的某个消息)
|
||||
// Delete a message (a message for a session)
|
||||
DeleteMessage = '57',
|
||||
|
||||
JsonStringify = '58',
|
||||
@@ -107,7 +107,7 @@ export enum StandardNodeType {
|
||||
}
|
||||
|
||||
/**
|
||||
* 除了 Api、SubWorkflow、Imageflow 之外的基础节点类型
|
||||
* Basic node types other than APIs, SubWorkflow, and Imageflow
|
||||
*/
|
||||
export type BasicStandardNodeTypes = Exclude<
|
||||
StandardNodeType,
|
||||
@@ -116,7 +116,7 @@ export type BasicStandardNodeTypes = Exclude<
|
||||
| StandardNodeType.SubWorkflow
|
||||
>;
|
||||
|
||||
/** 节点展示排序 */
|
||||
/** Node display sorting */
|
||||
export const NODE_ORDER = {
|
||||
[StandardNodeType.Start]: 1,
|
||||
[StandardNodeType.End]: 2,
|
||||
@@ -148,7 +148,7 @@ export const NODE_ORDER = {
|
||||
[StandardNodeType.TriggerDelete]: 28,
|
||||
};
|
||||
|
||||
/** 会话类节点 */
|
||||
/** session class node */
|
||||
export const CONVERSATION_NODES = [
|
||||
StandardNodeType.CreateConversation,
|
||||
StandardNodeType.UpdateConversation,
|
||||
@@ -156,7 +156,7 @@ export const CONVERSATION_NODES = [
|
||||
StandardNodeType.QueryConversationList,
|
||||
];
|
||||
|
||||
/** 消息类节点 */
|
||||
/** message class node */
|
||||
export const MESSAGE_NODES = [
|
||||
StandardNodeType.CreateMessage,
|
||||
StandardNodeType.UpdateMessage,
|
||||
@@ -164,7 +164,7 @@ export const MESSAGE_NODES = [
|
||||
StandardNodeType.QueryMessageList,
|
||||
];
|
||||
|
||||
/** 会话历史类节点 */
|
||||
/** session history class node */
|
||||
export const CONVERSATION_HISTORY_NODES = [
|
||||
StandardNodeType.QueryConversationHistory,
|
||||
StandardNodeType.ClearContext,
|
||||
|
||||
@@ -37,5 +37,5 @@ export interface WorkflowJSON {
|
||||
edges: WorkflowEdgeJSON[];
|
||||
}
|
||||
|
||||
// 节点模版类型
|
||||
// Node template type
|
||||
export { NodeTemplateType } from '@coze-arch/bot-api/workflow_api';
|
||||
|
||||
@@ -17,26 +17,26 @@
|
||||
import { type ViewVariableType } from './view-variable-type';
|
||||
|
||||
/**
|
||||
* 参数定义
|
||||
* parameter definition
|
||||
*
|
||||
* 递归定义,包含了复杂类型的层级结构
|
||||
* Recursive definition, including a hierarchy of complex types
|
||||
*/
|
||||
export interface RecursedParamDefinition {
|
||||
name?: string;
|
||||
/** Tree 组件要求每一个节点都有 key,而 key 不适合用名称(前后缀)等任何方式赋值,最终确定由接口转换层一次性提供随机 key */
|
||||
/** The Tree component requires each node to have a key, and the key is not suitable for assignment in any way such as name (before and after). Finally, the interface conversion layer provides a random key at one time. */
|
||||
fieldRandomKey?: string;
|
||||
desc?: string;
|
||||
required?: boolean;
|
||||
type: ViewVariableType;
|
||||
children?: RecursedParamDefinition[];
|
||||
// region 参数值定义
|
||||
// 输入参数的值可以来自上游变量引用,也可以是用户输入的定值(复杂类型则只允许引用)
|
||||
// 如果是定值,传 fixedValue
|
||||
// 如果是引用,传 quotedValue
|
||||
// Region parameter value definition
|
||||
// The value of the input parameter can come from an upstream variable reference, or it can be a fixed value entered by the user (for complex types, only references are allowed).
|
||||
// If it is a fixed value, pass the fixedValue.
|
||||
// If it is a reference, pass the quotedValue.
|
||||
isQuote?: ParamValueType;
|
||||
/** 参数定值 */
|
||||
/** parameter setting */
|
||||
fixedValue?: string;
|
||||
/** 参数引用 */
|
||||
/** parameter reference */
|
||||
quotedValue?: [nodeId: string, ...path: string[]]; // string[]
|
||||
// endregion
|
||||
}
|
||||
|
||||
@@ -34,11 +34,11 @@ import { type StandardNodeType } from './node-type';
|
||||
|
||||
export interface WorkflowNodeVariablesMeta {
|
||||
/**
|
||||
* 输出变量路径, 默认 ['outputs']
|
||||
* Output variable path, default ['outputs']
|
||||
*/
|
||||
outputsPathList?: string[];
|
||||
/**
|
||||
* 输入变量路径,默认 ['inputs.inputParameters']
|
||||
* Input variable path, default ['input.inputParameters']
|
||||
*/
|
||||
inputsPathList?: string[];
|
||||
batchInputListPath?: string;
|
||||
@@ -56,7 +56,7 @@ export interface NodeMeta<NodeTest = any> {
|
||||
isStart?: boolean;
|
||||
isNodeEnd?: boolean;
|
||||
deleteDisable?: boolean;
|
||||
copyDisable?: boolean; // 无法复制
|
||||
copyDisable?: boolean; // Unable to copy
|
||||
nodeDTOType: StandardNodeType;
|
||||
nodeMetaPath?: string;
|
||||
batchPath?: string;
|
||||
@@ -65,41 +65,41 @@ export interface NodeMeta<NodeTest = any> {
|
||||
renderKey?: string;
|
||||
style?: any;
|
||||
errorStyle?: any;
|
||||
size?: { width: number; height: number }; // 初始化时候的默认大小
|
||||
size?: { width: number; height: number }; // Default size at initialization
|
||||
defaultPorts?: Array<any>;
|
||||
useDynamicPort?: boolean;
|
||||
disableSideSheet?: boolean;
|
||||
subCanvas?: (node: WorkflowNodeEntity) => WorkflowSubCanvas | undefined;
|
||||
|
||||
/** 是否展示触发器 icon */
|
||||
/** Whether to show trigger icon */
|
||||
showTrigger?: (props: { projectId?: string }) => boolean;
|
||||
/** 是否隐藏测试 */
|
||||
/** Whether to hide the test */
|
||||
hideTest?: boolean;
|
||||
/** 获取卡片输入变量标签 */
|
||||
/** Get card input variable label */
|
||||
getInputVariableTag?: (
|
||||
/** 变量名称 */
|
||||
/** variable name */
|
||||
variableName: string | undefined,
|
||||
/** 输入值或变量 */
|
||||
/** Input value or variable */
|
||||
input: ValueExpression,
|
||||
/** 附加参数 */
|
||||
/** additional parameters */
|
||||
extra?: {
|
||||
/**
|
||||
* 变量服务,从 '@coze-workflow/variable' 导入的 WorkflowVariableService
|
||||
* 这里不写上类型,是为了避免循环引用,另外 base 包引用 variable 包内容也不合理
|
||||
* Variable service, WorkflowVariableService imported from '@code-workflow/variable'
|
||||
* The type is not written here to avoid circular references. In addition, it is unreasonable for the base package to refer to the content of the variable package.
|
||||
*/
|
||||
variableService: any;
|
||||
|
||||
/** 当前节点 */
|
||||
/** current node */
|
||||
node: WorkflowNodeEntity;
|
||||
},
|
||||
) => VariableTagProps;
|
||||
|
||||
/** 是否支持单节点调试copilot生成表单参数 */
|
||||
/** Whether to support single-node debugging copilot generation form parameters */
|
||||
enableCopilotGenerateTestNodeForm?: boolean;
|
||||
/**
|
||||
* 获取llm ids,所有需要用到大模型选择的的节点必须实现该方法
|
||||
* 因为用户可能存在账户升级或者模型下架,正常拉到的列表数据可能没有该id
|
||||
* 所以模型列表获取需要遍历所有节点拿到被用过的模型id
|
||||
* Get llm ids, all nodes that require large model selection must implement this method
|
||||
* Because the user may have an account upgrade or the model is removed from the shelves, the list data pulled normally may not have this id.
|
||||
* Therefore, to obtain the model list, you need to traverse all nodes to get the used model IDs.
|
||||
* @param nodeJSON
|
||||
* @returns
|
||||
*/
|
||||
@@ -107,17 +107,17 @@ export interface NodeMeta<NodeTest = any> {
|
||||
nodeJSON: WorkflowNodeJSON,
|
||||
) => string[] | undefined | string | number | number[];
|
||||
/**
|
||||
* 节点 header 不可编辑 & 隐藏 ... 按钮
|
||||
* Node header not editable & hide... button
|
||||
*/
|
||||
headerReadonly?: boolean;
|
||||
headerReadonlyAllowDeleteOperation?: boolean;
|
||||
|
||||
/**
|
||||
* 节点帮助文档
|
||||
* Node Help Documentation
|
||||
*/
|
||||
helpLink?: string | ((props: { apiName: string }) => string);
|
||||
/**
|
||||
* 节点测试相关数据
|
||||
* Node test related data
|
||||
*/
|
||||
test?: NodeTest;
|
||||
}
|
||||
@@ -128,14 +128,14 @@ export interface WorkflowNodeRegistry<NodeTestMeta = any>
|
||||
meta: NodeMeta<NodeTestMeta>;
|
||||
|
||||
/**
|
||||
* 特化:根据节点获取输入 Parameters 的值,默认通过 /inputParameters 字段判断
|
||||
* Specialization: Get the value of the input Parameters according to the node, and judge by /inputParameters field by default
|
||||
* @param node
|
||||
* @returns
|
||||
*/
|
||||
getNodeInputParameters?: (node: FlowNodeEntity) => InputValueVO[] | undefined;
|
||||
|
||||
/**
|
||||
* 获取舞台节点右侧更多菜单的额外操作,目前在子流程节点和插件节点中使用
|
||||
* Get additional actions for the More menu to the right of the Stage node, currently used in the Subprocess node and the Plugin node
|
||||
* @returns
|
||||
*/
|
||||
getHeaderExtraOperation?: (
|
||||
@@ -144,39 +144,39 @@ export interface WorkflowNodeRegistry<NodeTestMeta = any>
|
||||
) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* 特化:根据节点获取输出 Outputs 的值,默认通过 /outputs 字段判断
|
||||
* Specialization: Get the value of Outputs according to the node, and judge by /outputs field by default
|
||||
*/
|
||||
getNodeOutputs?: (node: FlowNodeEntity) => OutputValueVO[] | undefined;
|
||||
|
||||
/**
|
||||
* 节点提交前最后一次转化,这个方法在 `formMeta.transformOnSubmit` 之后执行
|
||||
* @param node 节点数据
|
||||
* @param 转化后的节点数据
|
||||
* The last conversion before the node commits, this method is executed after'formMeta.transformOnSubmit'
|
||||
* @param node data
|
||||
* @Param converted node data
|
||||
*/
|
||||
beforeNodeSubmit?: (node: WorkflowNodeJSON) => WorkflowNodeJSON;
|
||||
|
||||
/**
|
||||
* - 节点 Registry 初始化,可以在这里获取初始化数据,然后再进行表单渲染,注意此时 node 还没有创建
|
||||
* - 目前适用于 v2 表单引擎节点
|
||||
* @param nodeJSON 节点初始化数据(点击添加时,或者从后端获取时的节点数据)
|
||||
* @param context WorkflowPlaygroundContext,参见 packages/workflow/playground/src/workflow-playground-context.ts
|
||||
* - Node Registry initialization, you can get the initialization data here, and then render the form. Note that the node has not been created at this time
|
||||
* - Currently works with v2 form engine nodes
|
||||
* @Param nodeJSON node initialization data (when clicking Add, or when fetching from the backend)
|
||||
* @Param context WorkflowPlaygroundContext, see packages/workflow/playground/src/workflow-playground-context
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
onInit?: (nodeJSON: WorkflowNodeJSON, context: any) => Promise<void>;
|
||||
|
||||
/**
|
||||
* - 是否有错误信息(从 service 中拿),如果有错误信息返回错误信息,否则返回空串或者 undefined
|
||||
* - 目前在 node-context-provider.tsx 中消费,监听当前节点错误信息,如果有,更新 FlowNodeErrorData 数据,从而触发节点渲染错误状态
|
||||
* - 目前适用于 v2 表单引擎节点
|
||||
* @param nodeJSON 节点初始化数据(点击添加时,或者从后端获取时的节点数据)
|
||||
* @param context WorkflowPlaygroundContext,参见 packages/workflow/playground/src/workflow-playground-context.ts
|
||||
* - Whether there is an error message (taken from the service), if there is an error message, return an error message, otherwise return an empty string or undefined
|
||||
* - Currently consumed in node-context-provider, monitor the current node error message, if any, update the FlowNodeErrorData data, thereby triggering the node to render the error state
|
||||
* - Currently works with v2 form engine nodes
|
||||
* @Param nodeJSON node initialization data (when clicking Add, or when fetching from the backend)
|
||||
* @Param context WorkflowPlaygroundContext, see packages/workflow/playground/src/workflow-playground-context
|
||||
*/
|
||||
checkError?: (nodeJSON: WorkflowNodeJSON, context: any) => string | undefined;
|
||||
|
||||
/**
|
||||
* - 节点销毁时调用,用于做一些清理工作,例如将相关节点的错误信息清空,回收资源
|
||||
* - 目前适用于 v2 表单引擎节点
|
||||
* @param node 节点
|
||||
* - Called when the node is destroyed, used to do some cleaning work, such as clearing the error information of the joint point and recycling resources
|
||||
* - Currently works with v2 form engine nodes
|
||||
* @param node
|
||||
* @param context workflow-playground-context
|
||||
* @returns
|
||||
*/
|
||||
|
||||
@@ -23,11 +23,11 @@ export interface ViewVariableTreeNode {
|
||||
children?: ViewVariableTreeNode[];
|
||||
required?: boolean;
|
||||
description?: string;
|
||||
// 标识该条参数是内置参数,默认为 false
|
||||
// Identifies that the parameter is a built-in parameter, and the default is false.
|
||||
isPreset?: boolean;
|
||||
// 标识该条参数是否启用,默认为 false
|
||||
// Identifies whether the parameter is enabled, the default is false
|
||||
enabled?: boolean;
|
||||
// 用户自定义节点名展示
|
||||
// user-defined node name display
|
||||
label?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
defaultValue?: any;
|
||||
@@ -37,13 +37,13 @@ export interface ViewVariableMeta extends ViewVariableTreeNode {
|
||||
required?: boolean;
|
||||
description?: string;
|
||||
readonly?: boolean;
|
||||
mutable?: boolean; // 是否可以被 Set 节点的左值选中
|
||||
mutable?: boolean; // Can be selected by the lvalue of the Set node
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace ViewVariableTreeNode {
|
||||
/**
|
||||
* 通过path 查询 子节点
|
||||
* Query sub-node via path
|
||||
* @param node
|
||||
*/
|
||||
export function getVariableTreeNodeByPath(
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* 前端变量类型
|
||||
* Front-end variable type
|
||||
*/
|
||||
export enum ViewVariableType {
|
||||
String = 1,
|
||||
@@ -38,7 +38,7 @@ export enum ViewVariableType {
|
||||
Svg,
|
||||
Voice,
|
||||
Time,
|
||||
// 上面是 api 中定义的 InputType。下面是整合后的。从 99 开始,避免和后端定义撞车
|
||||
// The above is the InputType defined in the api. The following is the integrated one. Start from 99 to avoid collisions with the backend definition.
|
||||
ArrayString = 99,
|
||||
ArrayInteger,
|
||||
ArrayBoolean,
|
||||
@@ -59,7 +59,7 @@ export enum ViewVariableType {
|
||||
ArrayTime,
|
||||
}
|
||||
/**
|
||||
* 使用 JSON 表示值的变量类型
|
||||
* Variable types that use JSON to represent values
|
||||
*/
|
||||
export const JSON_INPUT_TYPES = [
|
||||
ViewVariableType.Object,
|
||||
@@ -99,7 +99,7 @@ export const FILE_TYPES = [
|
||||
ViewVariableType.ArrayVoice,
|
||||
];
|
||||
|
||||
// 基础类型,及其对应数组类型的配对
|
||||
// Base types, and pairing with their corresponding array types
|
||||
export const BASE_ARRAY_PAIR: [ViewVariableType, ViewVariableType][] = [
|
||||
[ViewVariableType.String, ViewVariableType.ArrayString],
|
||||
[ViewVariableType.Integer, ViewVariableType.ArrayInteger],
|
||||
@@ -170,8 +170,8 @@ export namespace ViewVariableType {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有变量类型的补集
|
||||
* 该函数由GPT生成
|
||||
* Get the complement of all variable types
|
||||
* This function is generated by GPT
|
||||
* @param inputTypes
|
||||
*/
|
||||
export function getComplement(inputTypes: ViewVariableType[]) {
|
||||
@@ -203,7 +203,7 @@ export namespace ViewVariableType {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 JSON 表示值的变量类型
|
||||
* Variable types that use JSON to represent values
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
@@ -244,30 +244,30 @@ export namespace ViewVariableType {
|
||||
export interface InputVariable {
|
||||
name: string;
|
||||
/**
|
||||
* id 不一定有,非前后端约定,前端强加上的。风险:workflowSchema 结构化时可能会删掉此属性
|
||||
* 如需 id ,要在 nodes schema 中定义 :
|
||||
* The id does not necessarily have a front-end and back-end convention, imposed by the front-end. Risk: This property may be deleted when workflowSchema is structured
|
||||
* If you need an id, define it in the nodes schema:
|
||||
* getDefaultAppendValue: () => ({
|
||||
id: nanoid(),
|
||||
}),
|
||||
* 参考 packages/workflow/nodes/src/workflow-nodes/image-canvas/index.ts
|
||||
* 只能保证同一节点内唯一,只能保证同一节点内唯一,只能保证同一节点内唯一
|
||||
* 因为节点可以创建副本
|
||||
* Reference packages/workflow/nodes/src/workflow-nodes/image-canvas/index.ts
|
||||
* It can only be guaranteed to be unique within the same node, it can only be guaranteed to be unique within the same node, and it can only be guaranteed to be unique within the same node.
|
||||
* Because nodes can create replicas
|
||||
*/
|
||||
id?: string;
|
||||
type: ViewVariableType;
|
||||
/**
|
||||
* 索引
|
||||
* index
|
||||
*/
|
||||
index: number;
|
||||
}
|
||||
|
||||
/** 卡片上的变量展示标签 */
|
||||
/** Variable display labels on cards */
|
||||
export interface VariableTagProps {
|
||||
key?: string;
|
||||
/* 变量类型 */
|
||||
/* Variable type */
|
||||
type?: ViewVariableType;
|
||||
/* 变量名,为空时会展示为 Undefined/未定义 */
|
||||
/* Variable name, when empty, is displayed as Undefined/Undefined */
|
||||
label?: ReactNode;
|
||||
/** 是否有效 */
|
||||
/** Is it valid? */
|
||||
invalid?: boolean;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 这个文件定义的是前端消费的数据, 用 VO (view object) 来表示
|
||||
* This file defines the data consumed by the front end and is represented by VO (view object)
|
||||
*/
|
||||
import { isNil, isUndefined } from 'lodash-es';
|
||||
|
||||
@@ -23,13 +23,13 @@ import type { ViewVariableType } from './view-variable-type';
|
||||
|
||||
export interface RefExpressionContent {
|
||||
/**
|
||||
* 引用值是一个变量的 key 路径
|
||||
* The reference value is the key path of a variable
|
||||
*/
|
||||
keyPath: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 值表达式类型
|
||||
* value expression type
|
||||
*/
|
||||
export enum ValueExpressionType {
|
||||
LITERAL = 'literal',
|
||||
@@ -37,7 +37,7 @@ export enum ValueExpressionType {
|
||||
OBJECT_REF = 'object_ref',
|
||||
}
|
||||
/**
|
||||
* 用来存储 ValueExpression 的原始数据供前端消费
|
||||
* Used to store ValueExpression's original data source for front-end consumption
|
||||
*/
|
||||
export interface ValueExpressionRawMeta {
|
||||
type?: ViewVariableType;
|
||||
@@ -46,7 +46,7 @@ export interface ValueExpressionRawMeta {
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本表达式
|
||||
* Text expression
|
||||
*/
|
||||
export interface LiteralExpression {
|
||||
type: ValueExpressionType.LITERAL;
|
||||
@@ -55,7 +55,7 @@ export interface LiteralExpression {
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象表达式
|
||||
* object expression
|
||||
*/
|
||||
export interface ObjectRefExpression {
|
||||
type: ValueExpressionType.OBJECT_REF;
|
||||
@@ -64,20 +64,20 @@ export interface ObjectRefExpression {
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用变量
|
||||
* reference variable
|
||||
*/
|
||||
export interface RefExpression {
|
||||
type: ValueExpressionType.REF;
|
||||
content?: RefExpressionContent;
|
||||
/**
|
||||
* rawMeta 记录了该 ref expression 的类型
|
||||
* 可能和所引用变量类型不同,此时会触发类型自动转换: [Workflow 类型自动转换]()
|
||||
* rawMeta records the type of the ref expression
|
||||
* May be different from the referenced variable type, which triggers automatic type conversion: [Workflow type automatic conversion] ()
|
||||
*/
|
||||
rawMeta?: ValueExpressionRawMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端输入值表达式
|
||||
* Front-end input value expression
|
||||
*/
|
||||
export type ValueExpression =
|
||||
| RefExpression
|
||||
@@ -116,12 +116,12 @@ export namespace ValueExpression {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果 value 不是对象或者函数,也就是原生类型,在插件自定义组件中会存在
|
||||
// If value is not an object or function, that is, a native type, it will exist in the plug-in custom component
|
||||
if (typeof value !== 'object' && typeof value !== 'function') {
|
||||
return isNil(value);
|
||||
}
|
||||
|
||||
// value.content 有多种类型,可能是 false
|
||||
// Value.content has multiple types and may be false
|
||||
if (value?.content === '' || isNil(value?.content)) {
|
||||
return true;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ export namespace ValueExpression {
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端的value 输入值
|
||||
* Front end value input value
|
||||
* {
|
||||
* name: '',
|
||||
* input: {
|
||||
|
||||
@@ -22,10 +22,10 @@ export const concatTestId = (...testIds: string[]) =>
|
||||
testIds.filter(id => !!id).join('.');
|
||||
|
||||
/**
|
||||
* 生成节点的测试id
|
||||
* Generate the test ID of the node.
|
||||
* @example concatNodeTestId(node, 'right-panel') => playground.node.100001.right-panel
|
||||
* @param node 节点
|
||||
* @param testIds 其它id
|
||||
* @param node
|
||||
* @param testIds other id
|
||||
* @returns
|
||||
*/
|
||||
export const concatNodeTestId = (node: FlowNodeEntity, ...testIds: string[]) =>
|
||||
|
||||
@@ -20,7 +20,7 @@ import { FlowNodeFormData } from '@flowgram-adapter/free-layout-editor';
|
||||
import { type FlowNodeEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
/**
|
||||
* 找到以 pathEnds 为结尾的 FormItem,并获取它的值
|
||||
* Find the FormItem ending in pathEnds and get its value
|
||||
* @param node
|
||||
* @param pathEnds
|
||||
* @returns
|
||||
@@ -76,12 +76,12 @@ const findValueByPathEnds = <T = unknown>(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 检查当前路径是否以 pathEnds 结尾
|
||||
// Check if the current path ends with pathEnds
|
||||
if (currentPath.endsWith(pathEnds)) {
|
||||
return obj as T;
|
||||
}
|
||||
|
||||
// 处理对象
|
||||
// processing object
|
||||
if (typeof obj === 'object' && !Array.isArray(obj)) {
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
@@ -91,7 +91,7 @@ const findValueByPathEnds = <T = unknown>(
|
||||
return obj[key] as T;
|
||||
}
|
||||
|
||||
// 递归查找子对象
|
||||
// recursive search for child objects
|
||||
const result = findValueByPathEnds(obj[key], pathEnds, newPath);
|
||||
if (result !== undefined) {
|
||||
return result as T;
|
||||
@@ -100,7 +100,7 @@ const findValueByPathEnds = <T = unknown>(
|
||||
}
|
||||
}
|
||||
|
||||
// 处理数组
|
||||
// Processing Array
|
||||
if (Array.isArray(obj)) {
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
const newPath = `${currentPath}/${i}`;
|
||||
@@ -109,7 +109,7 @@ const findValueByPathEnds = <T = unknown>(
|
||||
return obj[i] as T;
|
||||
}
|
||||
|
||||
// 递归查找数组元素
|
||||
// Recursive lookup of array elements
|
||||
const result = findValueByPathEnds(obj[i], pathEnds, newPath);
|
||||
if (result !== undefined) {
|
||||
return result as T;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { WorkflowMode } from '@coze-arch/bot-api/workflow_api';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param flowMode 是否广义上的 workflow,包含原来的 Workflow 和 Coze 2.0 新增的 Chatflow
|
||||
* @Param flowMode Whether it is a workflow in a broad sense, including the original Workflow and the Chatflow added by Coze 2.0
|
||||
* @returns
|
||||
*/
|
||||
export const isGeneralWorkflow = (flowMode: WorkflowMode) =>
|
||||
|
||||
@@ -91,14 +91,14 @@ const parseData = (
|
||||
nodeSchema?.type === StandardNodeType.Text &&
|
||||
isString(rawOutput) &&
|
||||
rawOutput?.length > 0;
|
||||
// 文本节点的 raw_out 不需要反序列化,一定是 string,badcase:用户拼接的 json 字符串如 '{}'、'123',反序列化后会变成object 和 number
|
||||
// The raw_out of the text node does not need to be deserialized, it must be a string, badcase: the json string spliced by the user such as '{}'、' 123 'will become object and number after deserialization
|
||||
const rawOutputData = textHasRawout
|
||||
? rawOutput?.toString?.()
|
||||
: rawOutput
|
||||
? typeSafeJSONParse(rawOutput) || rawOutput?.toString?.()
|
||||
: undefined;
|
||||
|
||||
/** Code、Llm 节点需要展示 raw */
|
||||
/** Code, Llm nodes need to display raw */
|
||||
const hasRawOutput =
|
||||
(Boolean(nodeSchema?.type) &&
|
||||
[
|
||||
@@ -107,7 +107,7 @@ const parseData = (
|
||||
StandardNodeType.Question,
|
||||
].includes(nodeSchema?.type as StandardNodeType)) ||
|
||||
textHasRawout;
|
||||
// Start、Input 节点只展示输入
|
||||
// Start and Input nodes only display input
|
||||
const hasOutput =
|
||||
nodeSchema?.type !== StandardNodeType.Start &&
|
||||
nodeSchema?.type !== StandardNodeType.Input;
|
||||
@@ -151,7 +151,7 @@ const parseData = (
|
||||
return {
|
||||
dataList,
|
||||
imgList: parseImagesFromOutputData({
|
||||
// batch data 的 output 下钻了一层,需要再包一层和 output 的 schema 保持一致
|
||||
// The output of batch data is drilled down one layer, and another layer needs to be wrapped to be consistent with the schema of the output.
|
||||
outputData: isBatch
|
||||
? {
|
||||
outputList: [typeSafeJSONParse(outputJsonString)].filter(Boolean),
|
||||
|
||||
@@ -69,10 +69,10 @@ function getImgList(data: unknown, schema: Schema): string[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从节点 output data 中解析图片链接
|
||||
* @param outputData 节点输出数据 JSON 序列化后的字符串
|
||||
* @param nodeSchema 节点 schema
|
||||
* @param excludeNodeTypes 不解析该类型节点的图片链接
|
||||
* Parse image links from node output data
|
||||
* @Param outputData node output data JSON serialized string
|
||||
* @param nodeSchema
|
||||
* @Param excludeNodeTypes does not resolve image links for nodes of this type
|
||||
*/
|
||||
export function parseImagesFromOutputData({
|
||||
outputData,
|
||||
|
||||
@@ -69,10 +69,10 @@ it('extract schema with dataset param parser', () => {
|
||||
],
|
||||
});
|
||||
const extractedSchema = schemaExtractor.extract({
|
||||
// knowledge 知识库节点 6
|
||||
// Knowledge Base Node 6
|
||||
[StandardNodeType.Dataset]: [
|
||||
{
|
||||
// 对应知识库名称
|
||||
// Corresponding knowledge base name
|
||||
name: 'datasetParam',
|
||||
path: 'inputs.datasetParam',
|
||||
parser: SchemaExtractorParserName.DATASET_PARAM,
|
||||
|
||||
@@ -41,10 +41,10 @@ it('extract schema with default parser', () => {
|
||||
],
|
||||
});
|
||||
const extractedSchema = schemaExtractor.extract({
|
||||
// end 结束节点 2
|
||||
// End End Node 2
|
||||
[StandardNodeType.End]: [
|
||||
{
|
||||
// 对应输出指定内容
|
||||
// Corresponding output specified content
|
||||
name: 'content',
|
||||
path: 'inputs.content.value.content',
|
||||
},
|
||||
|
||||
@@ -73,10 +73,10 @@ it('extract schema with inputParameters parser', () => {
|
||||
],
|
||||
});
|
||||
const extractedSchema = schemaExtractor.extract({
|
||||
// llm 大模型节点 3
|
||||
// LLM Large Model Node 3
|
||||
[StandardNodeType.LLM]: [
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
|
||||
@@ -94,10 +94,10 @@ it('extract schema with intents param parser', () => {
|
||||
],
|
||||
});
|
||||
const extractedSchema = schemaExtractor.extract({
|
||||
// end 结束节点 2
|
||||
// End End Node 2
|
||||
[StandardNodeType.Intent]: [
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
|
||||
@@ -38,10 +38,10 @@ it('extract schema with json string parser', () => {
|
||||
],
|
||||
});
|
||||
const extractedSchema = schemaExtractor.extract({
|
||||
// end 结束节点 2
|
||||
// End End Node 2
|
||||
[StandardNodeType.SceneChat]: [
|
||||
{
|
||||
// 对应输出指定内容
|
||||
// Corresponding output specified content
|
||||
name: 'messages',
|
||||
path: 'inputs.Messages',
|
||||
parser: SchemaExtractorParserName.JSON_STRING_PARSER,
|
||||
|
||||
@@ -61,10 +61,10 @@ it('extract schema with outputs parser', () => {
|
||||
],
|
||||
});
|
||||
const extractedSchema = schemaExtractor.extract({
|
||||
// code 代码节点 5
|
||||
// Code Code Node 5
|
||||
[StandardNodeType.Code]: [
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
|
||||
@@ -125,7 +125,7 @@ it('extract schema with variableAssign parser', () => {
|
||||
const extractedSchema = schemaExtractor.extract({
|
||||
[StandardNodeType.SetVariable]: [
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.VARIABLE_ASSIGN,
|
||||
@@ -165,7 +165,7 @@ it('variableAssign parser with empty inputParameters', () => {
|
||||
const extractedSchema = schemaExtractor.extract({
|
||||
[StandardNodeType.SetVariable]: [
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.VARIABLE_ASSIGN,
|
||||
@@ -253,7 +253,7 @@ it('variableAssign parser with invalid schema', () => {
|
||||
const extractedSchema = schemaExtractor.extract({
|
||||
[StandardNodeType.SetVariable]: [
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.VARIABLE_ASSIGN,
|
||||
|
||||
@@ -20,10 +20,10 @@ import { SchemaExtractorParserName } from '../../constant';
|
||||
import { StandardNodeType } from '../../../../types';
|
||||
|
||||
export const imageflowExtractorConfig: SchemaExtractorConfig = {
|
||||
// api 节点 4
|
||||
// API Node 4
|
||||
[StandardNodeType.Api]: [
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
|
||||
@@ -19,211 +19,211 @@ import { SchemaExtractorParserName } from '../../constant';
|
||||
import { StandardNodeType } from '../../../../types';
|
||||
|
||||
export const workflowExtractorConfig: SchemaExtractorConfig = {
|
||||
// Start 开始节点 1
|
||||
// Start Start Node 1
|
||||
[StandardNodeType.Start]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应 input name / description
|
||||
// Corresponding input name/description
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// End 结束节点 2
|
||||
// End End Node 2
|
||||
[StandardNodeType.End]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应输出指定内容
|
||||
// Corresponding output specified content
|
||||
name: 'content',
|
||||
path: 'inputs.content.value.content',
|
||||
},
|
||||
],
|
||||
// LLM 大模型节点 3
|
||||
// LLM Large Model Node 3
|
||||
[StandardNodeType.LLM]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应batch value / batch description
|
||||
// Corresponding batch value/batch description
|
||||
name: 'batch',
|
||||
path: 'inputs.batch.inputLists',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应提示词
|
||||
// Cue word
|
||||
name: 'llmParam',
|
||||
path: 'inputs.llmParam',
|
||||
parser: SchemaExtractorParserName.LLM_PARAM,
|
||||
},
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// Plugin 节点 4
|
||||
// Plugin Node 4
|
||||
[StandardNodeType.Api]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应batch value / batch description
|
||||
// Corresponding batch value/batch description
|
||||
name: 'batch',
|
||||
path: 'inputs.batch.inputLists',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应input value / input description
|
||||
// Corresponding input value/input description
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// Code 代码节点 5
|
||||
// Code Node 5
|
||||
[StandardNodeType.Code]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input value / input description
|
||||
// Corresponding input value/input description
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应code内容
|
||||
// Corresponding code content
|
||||
name: 'code',
|
||||
path: 'inputs.code',
|
||||
},
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// Knowledge 知识库节点 6
|
||||
// Knowledge Base Node 6
|
||||
[StandardNodeType.Dataset]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应知识库名称
|
||||
// Corresponding knowledge base name
|
||||
name: 'datasetParam',
|
||||
path: 'inputs.datasetParam',
|
||||
parser: SchemaExtractorParserName.DATASET_PARAM,
|
||||
},
|
||||
],
|
||||
// If 判断节点 8
|
||||
// If the decision node 8
|
||||
[StandardNodeType.If]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'branches',
|
||||
path: 'inputs.branches',
|
||||
parser: SchemaExtractorParserName.DEFAULT,
|
||||
},
|
||||
],
|
||||
// Sub Workflow 工作流节点 9
|
||||
// Sub Workflow Node 9
|
||||
[StandardNodeType.SubWorkflow]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应batch value / batch description
|
||||
// Corresponding batch value/batch description
|
||||
name: 'batch',
|
||||
path: 'inputs.batch.inputLists',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应input value / input description
|
||||
// Corresponding input value/input description
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// Variable 变量节点 11
|
||||
// Variable Node 11
|
||||
[StandardNodeType.Variable]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// Database 数据库节点 12
|
||||
// Database Node 12
|
||||
[StandardNodeType.Database]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
@@ -234,21 +234,21 @@ export const workflowExtractorConfig: SchemaExtractorConfig = {
|
||||
path: 'inputs.sql',
|
||||
},
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// Message 消息节点 13
|
||||
// Message Node 13
|
||||
[StandardNodeType.Output]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
@@ -259,78 +259,78 @@ export const workflowExtractorConfig: SchemaExtractorConfig = {
|
||||
path: 'inputs.content.value.content',
|
||||
},
|
||||
],
|
||||
// Sub Imageflow 图像流节点 14
|
||||
// Sub ImageFlow Node 14
|
||||
[StandardNodeType.Imageflow]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应batch value / batch description
|
||||
// Corresponding batch value/batch description
|
||||
name: 'batch',
|
||||
path: 'inputs.batch.inputLists',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应input value / input description
|
||||
// Corresponding input value/input description
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// Text 文本处理节点 15
|
||||
// Text processing node 15
|
||||
[StandardNodeType.Text]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 拼接结果,以及拼接字符串
|
||||
// Splicing results, and splicing strings
|
||||
name: 'concatResult',
|
||||
path: 'inputs.concatParams',
|
||||
parser: SchemaExtractorParserName.CONCAT_RESULT,
|
||||
},
|
||||
{
|
||||
// 自定义数组拼接符号
|
||||
// Custom array stitching symbols
|
||||
name: 'arrayConcatChar',
|
||||
path: 'inputs.concatParams',
|
||||
parser: SchemaExtractorParserName.CUSTOM_ARRAY_CONCAT_CHAR,
|
||||
},
|
||||
{
|
||||
// 自定义分隔符
|
||||
// custom separator
|
||||
name: 'splitChar',
|
||||
path: 'inputs.splitParams',
|
||||
parser: SchemaExtractorParserName.CUSTOM_SPLIT_CHAR,
|
||||
},
|
||||
],
|
||||
// Question 问题节点 18
|
||||
// Question Node 18
|
||||
[StandardNodeType.Question]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// question 问题
|
||||
// Question question
|
||||
name: 'question',
|
||||
path: 'inputs.question',
|
||||
},
|
||||
{
|
||||
// answer_type 回答类型 option|text
|
||||
// answer_type answer type option | text
|
||||
name: 'answerType',
|
||||
path: 'inputs.answer_type',
|
||||
},
|
||||
@@ -340,69 +340,69 @@ export const workflowExtractorConfig: SchemaExtractorConfig = {
|
||||
path: 'inputs.options',
|
||||
},
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// Break 终止循环节点 19
|
||||
// Break Stop Loop Node 19
|
||||
[StandardNodeType.Break]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
],
|
||||
// Set Variable 设置变量节点 20
|
||||
// Set Variables Set Variables Node 20
|
||||
[StandardNodeType.SetVariable]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.VARIABLE_ASSIGN,
|
||||
},
|
||||
],
|
||||
// Loop 循环节点 21
|
||||
// Loop node 21
|
||||
[StandardNodeType.Loop]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应variable name
|
||||
// Corresponding variable name
|
||||
name: 'variables',
|
||||
path: 'inputs.variableParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应output name
|
||||
// Corresponding output name
|
||||
name: 'outputs',
|
||||
path: 'outputs',
|
||||
parser: SchemaExtractorParserName.OUTPUTS,
|
||||
},
|
||||
],
|
||||
// Intent 意图识别节点 22
|
||||
// Intent recognition node 22
|
||||
[StandardNodeType.Intent]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
@@ -419,21 +419,21 @@ export const workflowExtractorConfig: SchemaExtractorConfig = {
|
||||
path: 'inputs.llmParam.systemPrompt.value.content',
|
||||
},
|
||||
],
|
||||
// Knowledge Write 知识库写入节点 27
|
||||
// Knowledge Writing Knowledge Base Writing Node 27
|
||||
[StandardNodeType.DatasetWrite]: [
|
||||
{
|
||||
// 节点自定义名称
|
||||
// Node custom name
|
||||
name: 'title',
|
||||
path: 'nodeMeta.title',
|
||||
},
|
||||
{
|
||||
// 对应input name
|
||||
// Corresponding input name
|
||||
name: 'inputs',
|
||||
path: 'inputs.inputParameters',
|
||||
parser: SchemaExtractorParserName.INPUT_PARAMETERS,
|
||||
},
|
||||
{
|
||||
// 对应知识库名称
|
||||
// Corresponding knowledge base name
|
||||
name: 'datasetParam',
|
||||
path: 'inputs.datasetParam',
|
||||
parser: SchemaExtractorParserName.DATASET_PARAM,
|
||||
|
||||
@@ -51,10 +51,10 @@ export class SchemaExtractor {
|
||||
}
|
||||
public extract(config: SchemaExtractorConfig): SchemaExtracted[] {
|
||||
this.bindParser(config);
|
||||
// 1. 遍历schema中node数组,对每个node做处理
|
||||
// 1. Traverse the node array in the schema and process each node
|
||||
return this.schema.nodes
|
||||
.map((node: WorkflowNodeJSON): SchemaExtracted | null => {
|
||||
// 2. 获取节点对应的配置
|
||||
// 2. Get the configuration corresponding to the node
|
||||
const nodeConfigs: SchemaExtractorNodeConfig[] = config[node.type];
|
||||
if (!nodeConfigs) {
|
||||
return null;
|
||||
@@ -76,13 +76,13 @@ export class SchemaExtractor {
|
||||
extractedConfig: Record<string, unknown>,
|
||||
nodeConfig: SchemaExtractorNodeConfig,
|
||||
): Record<string, unknown> => {
|
||||
// 3. 根据节点配置路径获取属性值
|
||||
// 3. Get the attribute value according to the node configuration path
|
||||
const rawData: unknown = this.extractProperties(
|
||||
nodeData,
|
||||
nodeConfig.path,
|
||||
);
|
||||
if (nodeConfig.parser && typeof nodeConfig.parser === 'function') {
|
||||
// 4. 使用解析器对属性值进行转换
|
||||
// 4. Use the parser to convert the property value
|
||||
extractedConfig[nodeConfig.name] = nodeConfig.parser(rawData);
|
||||
}
|
||||
return extractedConfig;
|
||||
@@ -121,7 +121,7 @@ export class SchemaExtractor {
|
||||
const flattenNodeJSONs: WorkflowNodeJSON[] = [...rootNodes];
|
||||
const flattenEdgeJSONs: WorkflowEdgeJSON[] = [...rootEdges];
|
||||
|
||||
// 如需支持多层结构,以下部分改为递归
|
||||
// To support multi-layer structures, the following section is changed to recursive
|
||||
rootNodes.forEach(nodeJSON => {
|
||||
const { blocks, edges } = nodeJSON;
|
||||
if (blocks) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { isWorkflowImageTypeURL } from '../utils';
|
||||
import { type SchemaExtractorOutputsParser } from '../type';
|
||||
import { AssistTypeDTO, VariableTypeDTO } from '../../../types/dto';
|
||||
export const outputsParser: SchemaExtractorOutputsParser = outputs => {
|
||||
// 判断是否为数组
|
||||
// Determine whether it is an array
|
||||
if (!Array.isArray(outputs)) {
|
||||
return [];
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export const outputsParser: SchemaExtractorOutputsParser = outputs => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value?: any;
|
||||
isImage?: boolean;
|
||||
// 默认值里包含图片时,图片信息单独放到这里
|
||||
// When the default value includes pictures, the picture information is put here separately.
|
||||
images?: string[];
|
||||
} = {
|
||||
name: output.name || '',
|
||||
@@ -45,7 +45,7 @@ export const outputsParser: SchemaExtractorOutputsParser = outputs => {
|
||||
if (output.type === 'list' && Array.isArray(output.schema?.schema)) {
|
||||
parsed.children = outputsParser(output.schema.schema);
|
||||
}
|
||||
// Start 节点默认值放到 value 上
|
||||
// Start node default value put on value
|
||||
if (output.defaultValue) {
|
||||
parsed.value = output.defaultValue;
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ export const variableAssignParser: SchemaExtractorVariableAssignParser =
|
||||
.map(variableAssign => {
|
||||
const leftContent = getValueExpressionName(variableAssign.left);
|
||||
const rightContent = getValueExpressionName(variableAssign.right);
|
||||
// 变量赋值节点的右值字段
|
||||
// Rvalue field of variable assignment node
|
||||
const inputContent = variableAssign.input
|
||||
? getValueExpressionName(variableAssign.input)
|
||||
: null;
|
||||
|
||||
@@ -29,7 +29,7 @@ export type SchemaExtractorConfig = Partial<
|
||||
|
||||
export interface SchemaExtractorNodeConfig {
|
||||
name: string;
|
||||
/** lodash.get 入参格式 */
|
||||
/** Lodash.get imported parameter format */
|
||||
path: string;
|
||||
parser?: SchemaExtractorParserName | Function;
|
||||
displayName?: string;
|
||||
@@ -93,7 +93,7 @@ export type SchemaExtractorOutputsParser = (outputs: VariableMetaDTO[]) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value?: any;
|
||||
isImage?: boolean;
|
||||
// 默认值里包含图片时,图片信息单独放到这里
|
||||
// When the default value includes pictures, the picture information is put here separately.
|
||||
images?: string[];
|
||||
}[];
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ import { get } from 'lodash-es';
|
||||
|
||||
import { type ValueExpressionDTO } from '../../types';
|
||||
|
||||
// 是否我们自己上传生成的url, 这是个临时方案,等修复 schema 中的 type:string -> type:image 后,删掉此逻辑
|
||||
// Whether we upload the generated url ourselves, this is a temporary solution, after fixing the type: string - > type: image in the schema, delete this logic
|
||||
export function isWorkflowImageTypeURL(str: string): boolean {
|
||||
// base64 加工
|
||||
// base64 processing
|
||||
const hostWhiteList = [
|
||||
'cC1ib3Qtd29ya2Zsb3ctc2lnbi5ieXRlZGFuY2UubmV0',
|
||||
'cC1ib3Qtd29ya2Zsb3cuYnl0ZWQub3Jn',
|
||||
|
||||
@@ -18,14 +18,14 @@ import { reporter as infraReporter } from '@coze-arch/logger';
|
||||
const namespace = 'workflow';
|
||||
|
||||
/**
|
||||
* 流程使用的 slardar 上报实例
|
||||
* The slardar reporting instance used by the process
|
||||
*/
|
||||
export const reporter = infraReporter.createReporterWithPreset({
|
||||
namespace,
|
||||
});
|
||||
|
||||
/**
|
||||
* 异常捕获,会被当js error上报
|
||||
* Exception capture will be reported as a js error
|
||||
* @param exception
|
||||
* @param importErrorInfo
|
||||
*/
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
import { BOT_USER_INPUT, CONVERSATION_NAME, USER_INPUT } from '../constants';
|
||||
/**
|
||||
* 是否预设的开始节点的输入参数
|
||||
* Whether to preset the input parameters of the start node
|
||||
*/
|
||||
export const isPresetStartParams = (name?: string): boolean =>
|
||||
[BOT_USER_INPUT, USER_INPUT, CONVERSATION_NAME].includes(name ?? '');
|
||||
|
||||
/**
|
||||
* Start 节点参数是 BOT 聊天时用户的输入内容
|
||||
* The Start node parameter is the user's input during BOT chat
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
|
||||
@@ -38,9 +38,9 @@ export interface TraverseContext {
|
||||
export type TraverseHandler = (context: TraverseContext) => void;
|
||||
|
||||
/**
|
||||
* 深度遍历对象,对每个值做处理
|
||||
* @param value 遍历对象
|
||||
* @param handle 处理函数
|
||||
* Traverse the object in depth, processing each value
|
||||
* @param value over object
|
||||
* @param handling function
|
||||
*/
|
||||
export const traverse = <T extends TraverseValue = TraverseValue>(
|
||||
value: T,
|
||||
@@ -57,9 +57,9 @@ export const traverse = <T extends TraverseValue = TraverseValue>(
|
||||
|
||||
namespace TraverseUtils {
|
||||
/**
|
||||
* 深度遍历对象,对每个值做处理
|
||||
* @param node 遍历节点
|
||||
* @param handle 处理函数
|
||||
* Traverse the object in depth, processing each value
|
||||
* @param node traverse node
|
||||
* @param handling function
|
||||
*/
|
||||
export const traverseNodes = (
|
||||
node: TraverseNode,
|
||||
@@ -67,11 +67,11 @@ namespace TraverseUtils {
|
||||
): void => {
|
||||
const { value } = node;
|
||||
if (!value) {
|
||||
// 异常处理
|
||||
// exception handling
|
||||
return;
|
||||
}
|
||||
if (Object.prototype.toString.call(value) === '[object Object]') {
|
||||
// 对象,遍历对象的每个属性
|
||||
// Object, iterate through each property of the object
|
||||
Object.entries(value).forEach(([key, item]) =>
|
||||
traverseNodes(
|
||||
{
|
||||
@@ -84,8 +84,8 @@ namespace TraverseUtils {
|
||||
),
|
||||
);
|
||||
} else if (Array.isArray(value)) {
|
||||
// 数组,遍历数组的每个元素
|
||||
// 从数组的末尾开始遍历,这样即使中途移除了某个元素,也不会影响到未处理的元素的索引
|
||||
// Array, iterate through each element of the array
|
||||
// The iteration starts at the end of the array, so that even if an element is removed halfway through, it will not affect the index of the unprocessed element
|
||||
for (let index = value.length - 1; index >= 0; index--) {
|
||||
const item: string = value[index];
|
||||
traverseNodes(
|
||||
@@ -117,14 +117,14 @@ namespace TraverseUtils {
|
||||
});
|
||||
|
||||
const setValue = (node: TraverseNode, value: unknown) => {
|
||||
// 设置值函数
|
||||
// 引用类型,需要借助父元素修改值
|
||||
// 由于是递归遍历,所以需要根据node来判断是给对象的哪个属性赋值,还是给数组的哪个元素赋值
|
||||
// Set Value Function
|
||||
// Reference type, you need to modify the value with the help of the parent element
|
||||
// Since it is a recursive traversal, it is necessary to determine which property of the object to assign a value to, or which element of the array to assign a value to, according to node
|
||||
if (!value || !node) {
|
||||
return;
|
||||
}
|
||||
node.value = value;
|
||||
// 从上级作用域node中取出container,key,index
|
||||
// Remove container, key, index from upper scope node
|
||||
const { container, key, index } = node;
|
||||
if (key && container) {
|
||||
container[key] = value;
|
||||
@@ -162,7 +162,7 @@ namespace TraverseUtils {
|
||||
if (typeof pathItem === 'string') {
|
||||
const re = /\W/g;
|
||||
if (re.test(pathItem)) {
|
||||
// 包含特殊字符
|
||||
// Contains special characters
|
||||
return `${stringifyPath}["${pathItem}"]`;
|
||||
}
|
||||
return `${stringifyPath}.${pathItem}`;
|
||||
|
||||
@@ -57,7 +57,7 @@ function useSelection(editor: ExpressionEditorAPI | undefined) {
|
||||
const view = editor.$view;
|
||||
|
||||
function updateSelection(update?: ViewUpdate) {
|
||||
// 忽略 replaceTextByRange 导致的 selection change(效果:不触发 selection 变更,进而不显示推荐面板)
|
||||
// Ignore the selection change caused by replaceTextByRange (effect: no selection change is triggered, and the recommendation panel is not displayed)
|
||||
if (update?.transactions.some(tr => isSkipSelectionChangeUserEvent(tr))) {
|
||||
setSelection(undefined);
|
||||
return;
|
||||
|
||||
@@ -23,7 +23,7 @@ import { type ExpressionEditorTreeNode } from '@/expression-editor';
|
||||
import { generateUniqueId, getSearchValue, useLatest } from '../../shared';
|
||||
import { type InterpolationContent } from './types';
|
||||
|
||||
// 在数据更新后,强制 Tree 组件重新渲染
|
||||
// Force the Tree component to re-render after the data update
|
||||
function useTreeRefresh(filteredVariableTree: ExpressionEditorTreeNode[]) {
|
||||
const [treeRefreshKey, setTreeRefreshKey] = useState('');
|
||||
|
||||
@@ -34,7 +34,7 @@ function useTreeRefresh(filteredVariableTree: ExpressionEditorTreeNode[]) {
|
||||
return treeRefreshKey;
|
||||
}
|
||||
|
||||
// Tree 组件重新渲染后进行搜索
|
||||
// Search after the Tree component is re-rendered
|
||||
// eslint-disable-next-line max-params
|
||||
function useTreeSearch(
|
||||
treeRefreshKey: string,
|
||||
|
||||
@@ -104,10 +104,10 @@ function Popover({
|
||||
const { elements } = optionsInfo;
|
||||
selectNodeByIndex(elements, 0);
|
||||
});
|
||||
// selected 仅用于 Tree 组件对应项展示蓝色选中效果,无其他用途
|
||||
// Selected is only used to display the blue selection effect for the corresponding item of the Tree component, and has no other purpose.
|
||||
const selected = useSelectedValue(interpolationContent?.text, variableTree);
|
||||
|
||||
// 基于用户选中项,替换所在 {{}} 中的内容
|
||||
// Replace content in {{}} based on user selection
|
||||
const handleSelect = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
(_, __, node: TreeNodeData) => {
|
||||
@@ -126,7 +126,7 @@ function Popover({
|
||||
Boolean(emptyContent));
|
||||
|
||||
const [allowVisible, setAllowVisible] = useState(false);
|
||||
// 选区变化时,清除锁定效果
|
||||
// Clear the lock effect when the selection changes
|
||||
useEffect(() => {
|
||||
setAllowVisible(true);
|
||||
}, [selection]);
|
||||
@@ -140,14 +140,14 @@ function Popover({
|
||||
treeRef,
|
||||
);
|
||||
|
||||
// 上下键切换推荐项,回车填入
|
||||
// Press the up and down keys to switch the recommended items, and press Enter to fill in.
|
||||
useKeyboard(visible, {
|
||||
ArrowUp: prev,
|
||||
ArrowDown: next,
|
||||
Enter: apply,
|
||||
});
|
||||
|
||||
// ESC 关闭
|
||||
// ESC Close
|
||||
useKeyboard(visible, {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
Escape() {
|
||||
@@ -155,7 +155,7 @@ function Popover({
|
||||
},
|
||||
});
|
||||
|
||||
// 推荐面板出现时,禁用 ArrowUp/ArrowDown/Enter 的默认行为(行为改为上下键切换推荐项 & 回车插入)
|
||||
// When the recommendation panel appears, disable the default behavior of ArrowUp/ArrowDown/Enter (the behavior is changed to up and down keys to switch recommendations & enter to insert)
|
||||
useEffect(() => {
|
||||
if (visible === true) {
|
||||
editorRef.current?.disableKeybindings(['ArrowUp', 'ArrowDown', 'Enter']);
|
||||
|
||||
@@ -43,7 +43,7 @@ const getOptionInfoFromDOM = (
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有的选项元素
|
||||
// Get all option elements
|
||||
const foundNodes = root.querySelectorAll(
|
||||
'.semi-tree-option-list .semi-tree-option',
|
||||
);
|
||||
@@ -54,7 +54,7 @@ const getOptionInfoFromDOM = (
|
||||
|
||||
const optionElements = [...foundNodes];
|
||||
|
||||
// 找到当前高亮的选项
|
||||
// Find the currently highlighted option
|
||||
const selectedIndex = optionElements.findIndex(element =>
|
||||
element.classList.contains(SELECTED_OPTION_CLASSNAME),
|
||||
);
|
||||
|
||||
@@ -77,9 +77,9 @@ function useExtensions(
|
||||
|
||||
if (
|
||||
cursor.node.type.name === 'Interpolation' &&
|
||||
// 由于 parser 存在容错能力
|
||||
// 可能出现缺少右花括号也被正常解析为 Interpolation 的情况
|
||||
// 如:{{variable
|
||||
// Due to the fault tolerance of the parser
|
||||
// It is possible that the missing right curly brace is also parsed normally as Interpolation
|
||||
// Such as: {{variable
|
||||
cursor.node.firstChild?.type.name === '{{' &&
|
||||
cursor.node.lastChild?.type.name === '}}'
|
||||
) {
|
||||
|
||||
@@ -70,8 +70,8 @@ function Renderer({
|
||||
[onChange],
|
||||
);
|
||||
|
||||
// Note: changedVariableTree 这里只用来进行性能优化
|
||||
// useVariableTree 的触发时机仍然存在问题,缩放画布也会频繁触发 variableTree 的变更
|
||||
// Note: changedVariableTree is only used for performance optimization
|
||||
// useVariableTree still has issues with the timing of triggering, and scaling the canvas also frequently triggers variableTree changes
|
||||
useEffect(() => {
|
||||
const editor = apiRef.current;
|
||||
|
||||
@@ -92,7 +92,7 @@ function Renderer({
|
||||
editor.updateWholeDecorations();
|
||||
}
|
||||
|
||||
// 值受控
|
||||
// value controlled
|
||||
useEffect(() => {
|
||||
const editor = apiRef.current;
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ function useLatest<T>(value: T): MutableRefObject<T> {
|
||||
return ref;
|
||||
}
|
||||
|
||||
// 解除 parent 导致的循环依赖(否则无法深比较)
|
||||
// Remove circular dependencies caused by parents (otherwise no deep comparison is possible)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function cloneWithout(target: any, keys: string[]) {
|
||||
// target 为 undefined 时会抛错
|
||||
// An error is thrown when target is undefined
|
||||
try {
|
||||
return JSON.parse(
|
||||
JSON.stringify(target, function (key, value) {
|
||||
@@ -85,7 +85,7 @@ function getSearchValue(textBefore: string) {
|
||||
const lastSegment =
|
||||
segments[segments.length - 1].type ===
|
||||
ExpressionEditorSegmentType.ArrayIndex
|
||||
? segments[segments.length - 2] // 数组索引属于上一层级,需要去除防止影响到搜索值
|
||||
? segments[segments.length - 2] // The array index belongs to the previous level and needs to be removed to prevent it from affecting the search value
|
||||
: segments[segments.length - 1];
|
||||
if (
|
||||
!lastSegment ||
|
||||
|
||||
@@ -31,7 +31,7 @@ interface ExpressionEditorCounterProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* 长度计数器
|
||||
* length counter
|
||||
*/
|
||||
export const ExpressionEditorCounter: FC<
|
||||
ExpressionEditorCounterProps
|
||||
|
||||
@@ -36,7 +36,7 @@ interface ExpressionEditorRenderProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* 应当只包含编辑器逻辑,业务无关
|
||||
* It should only contain editor logic, business-independent
|
||||
*/
|
||||
export const ExpressionEditorRender: React.FC<
|
||||
ExpressionEditorRenderProps
|
||||
@@ -57,7 +57,7 @@ export const ExpressionEditorRender: React.FC<
|
||||
editor={model.editor}
|
||||
initialValue={model.lines}
|
||||
onChange={value => {
|
||||
// eslint-disable-next-line @typescript-eslint/require-await -- 防止阻塞 slate 渲染
|
||||
// eslint-disable-next-line @typescript-eslint/require-await -- prevent blocking slate rendering
|
||||
const asyncOnChange = async () => {
|
||||
const lines = value as ExpressionEditorLine[];
|
||||
model.change(lines);
|
||||
|
||||
@@ -45,9 +45,9 @@ import {
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
/** 内置函数 */
|
||||
/** built-in function */
|
||||
namespace SuggestionViewUtils {
|
||||
/** 编辑器选中事件处理 */
|
||||
/** Editor selected event handling */
|
||||
export const editorSelectHandler = (params: {
|
||||
reducer: SuggestionReducer;
|
||||
payload: ExpressionEditorEventParams<ExpressionEditorEvent.Select>;
|
||||
@@ -55,7 +55,7 @@ namespace SuggestionViewUtils {
|
||||
const { reducer, payload } = params;
|
||||
const [state, dispatch] = reducer;
|
||||
|
||||
// 设置解析数据
|
||||
// Set up parse data
|
||||
const parseData = ExpressionEditorParser.parse({
|
||||
lineContent: payload.content,
|
||||
lineOffset: payload.offset,
|
||||
@@ -83,7 +83,7 @@ namespace SuggestionViewUtils {
|
||||
},
|
||||
});
|
||||
|
||||
// 重置UI组件内部状态
|
||||
// Reset UI component internal state
|
||||
const shouldRefresh = parseData.content.reachable === '';
|
||||
if (shouldRefresh) {
|
||||
dispatch({
|
||||
@@ -91,7 +91,7 @@ namespace SuggestionViewUtils {
|
||||
});
|
||||
}
|
||||
|
||||
// 设置选中值
|
||||
// Set the selected value
|
||||
const selected = SuggestionViewUtils.computeSelected({
|
||||
model: state.model,
|
||||
parseData,
|
||||
@@ -101,7 +101,7 @@ namespace SuggestionViewUtils {
|
||||
payload: selected,
|
||||
});
|
||||
|
||||
// 设置可见变量树
|
||||
// Set the visible variable tree
|
||||
const variableTree = SuggestionViewUtils.computeVariableTree({
|
||||
model: state.model,
|
||||
parseData,
|
||||
@@ -111,7 +111,7 @@ namespace SuggestionViewUtils {
|
||||
payload: variableTree,
|
||||
});
|
||||
|
||||
// 设置匹配树枝
|
||||
// Set matching branches
|
||||
const matchTreeBranch: ExpressionEditorTreeNode[] | undefined =
|
||||
ExpressionEditorTreeHelper.matchTreeBranch({
|
||||
tree: state.model.variableTree,
|
||||
@@ -122,7 +122,7 @@ namespace SuggestionViewUtils {
|
||||
payload: matchTreeBranch,
|
||||
});
|
||||
|
||||
// 设置空内容
|
||||
// Set empty content
|
||||
const emptyContent = SuggestionViewUtils.computeEmptyContent({
|
||||
parseData,
|
||||
fullVariableTree: state.model.variableTree,
|
||||
@@ -134,7 +134,7 @@ namespace SuggestionViewUtils {
|
||||
payload: emptyContent,
|
||||
});
|
||||
|
||||
// 设置UI相对坐标
|
||||
// Set UI relative coordinates
|
||||
const rect = SuggestionViewUtils.computeRect(state);
|
||||
dispatch({
|
||||
type: SuggestionActionType.SetRect,
|
||||
@@ -147,9 +147,9 @@ namespace SuggestionViewUtils {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: 设置搜索值,很hack的逻辑,后面建议重构不用semi组件,自己写一个
|
||||
// FIXME: Set the search value, very hacked logic. Later, it is recommended to refactor without semi components, and write one yourself.
|
||||
if (!state.ref.tree.current) {
|
||||
// 不设为可见获取不到ref
|
||||
// Not set to visible can't get ref
|
||||
dispatch({
|
||||
type: SuggestionActionType.SetVisible,
|
||||
payload: true,
|
||||
@@ -168,7 +168,7 @@ namespace SuggestionViewUtils {
|
||||
return 1;
|
||||
};
|
||||
|
||||
/** 计算可见时相对父容器坐标 */
|
||||
/** Calculate relative parent container coordinates when visible */
|
||||
export const computeRect = (
|
||||
state: SuggestionState,
|
||||
):
|
||||
@@ -194,12 +194,12 @@ namespace SuggestionViewUtils {
|
||||
left: (rect.left - containerRect.left) / getFinalScale(state),
|
||||
};
|
||||
} catch (e) {
|
||||
// slate DOM 计算报错可忽略
|
||||
// Slate DOM calculation error can be ignored
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
/** 计算当前选中变量 */
|
||||
/** Calculate the currently selected variable */
|
||||
export const computeSelected = (params: {
|
||||
model: ExpressionEditorModel;
|
||||
parseData: ExpressionEditorParseData;
|
||||
@@ -218,7 +218,7 @@ namespace SuggestionViewUtils {
|
||||
return treeBrach[treeBrach.length - 1];
|
||||
};
|
||||
|
||||
/** 计算当前搜索值 */
|
||||
/** Calculate the current search value */
|
||||
export const computeSearch = (
|
||||
parseData: ExpressionEditorParseData,
|
||||
): string => {
|
||||
@@ -229,7 +229,7 @@ namespace SuggestionViewUtils {
|
||||
const lastSegment =
|
||||
segments[segments.length - 1].type ===
|
||||
ExpressionEditorSegmentType.ArrayIndex
|
||||
? segments[segments.length - 2] // 数组索引属于上一层级,需要去除防止影响到搜索值
|
||||
? segments[segments.length - 2] // The array index belongs to the previous level and needs to be removed to prevent it from affecting the search value
|
||||
: segments[segments.length - 1];
|
||||
if (
|
||||
!lastSegment ||
|
||||
@@ -240,7 +240,7 @@ namespace SuggestionViewUtils {
|
||||
return lastSegment.objectKey;
|
||||
};
|
||||
|
||||
/** 计算裁剪层级的变量树 */
|
||||
/** Calculate the variable tree of the clipping level */
|
||||
export const computeVariableTree = (params: {
|
||||
model: ExpressionEditorModel;
|
||||
parseData: ExpressionEditorParseData;
|
||||
@@ -293,7 +293,7 @@ namespace SuggestionViewUtils {
|
||||
export const keyboardSelectedClassName = () =>
|
||||
styles['expression-editor-suggestion-keyboard-selected'];
|
||||
|
||||
/** 将选中项设为高亮 */
|
||||
/** Highlight the selected item */
|
||||
export const setUIOptionSelected = (uiOption: Element): void => {
|
||||
if (
|
||||
!uiOption?.classList?.add ||
|
||||
@@ -305,7 +305,7 @@ namespace SuggestionViewUtils {
|
||||
uiOption.classList.add(SuggestionViewUtils.keyboardSelectedClassName());
|
||||
};
|
||||
|
||||
/** 获取所有选项UI元素 */
|
||||
/** Get all options UI elements */
|
||||
export const computeUIOptions = (
|
||||
state: SuggestionState,
|
||||
):
|
||||
@@ -315,14 +315,14 @@ namespace SuggestionViewUtils {
|
||||
selectedOption?: Element;
|
||||
}
|
||||
| undefined => {
|
||||
// 获取所有的选项元素
|
||||
// Get all option elements
|
||||
const optionListDom =
|
||||
state.ref.suggestion.current?.children?.[0]?.children?.[1]?.children;
|
||||
if (!optionListDom) {
|
||||
return;
|
||||
}
|
||||
const optionList = Array.from(optionListDom);
|
||||
// 找到当前高亮的选项
|
||||
// Find the currently highlighted option
|
||||
const selectedIndex = optionList.findIndex(element =>
|
||||
element.classList.contains(keyboardSelectedClassName()),
|
||||
);
|
||||
@@ -333,7 +333,7 @@ namespace SuggestionViewUtils {
|
||||
};
|
||||
};
|
||||
|
||||
/** 禁止变更 visible 防止 ui 抖动 */
|
||||
/** Disable visible changes Prevent UI jitter */
|
||||
export const preventVisibleJitter = (
|
||||
reducer: SuggestionReducer,
|
||||
time = 150,
|
||||
@@ -354,18 +354,18 @@ namespace SuggestionViewUtils {
|
||||
}, time);
|
||||
};
|
||||
|
||||
/** 清空键盘UI选项 */
|
||||
/** Clear keyboard UI options */
|
||||
export const clearSelectedUIOption = (state: SuggestionState) => {
|
||||
const uiOptions = SuggestionViewUtils.computeUIOptions(state);
|
||||
if (uiOptions?.selectedOption) {
|
||||
// 清空键盘选中状态
|
||||
// Clear keyboard selection
|
||||
uiOptions.selectedOption.classList.remove(
|
||||
SuggestionViewUtils.keyboardSelectedClassName(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/** 默认键盘UI选项为第一项 */
|
||||
/** The default keyboard UI option is the first item */
|
||||
export const selectFirstUIOption = (state: SuggestionState) => {
|
||||
const uiOptions = SuggestionViewUtils.computeUIOptions(state);
|
||||
if (!uiOptions?.optionList) {
|
||||
@@ -375,12 +375,12 @@ namespace SuggestionViewUtils {
|
||||
if (!uiOptions?.optionList?.[0]?.classList?.add) {
|
||||
return;
|
||||
}
|
||||
// 默认首项高亮
|
||||
// Default first item highlighting
|
||||
SuggestionViewUtils.setUIOptionSelected(uiOptions.optionList[0]);
|
||||
};
|
||||
}
|
||||
|
||||
/** 选中节点 */
|
||||
/** selected node */
|
||||
export const useSelectNode = (reducer: SuggestionReducer) => {
|
||||
const [state] = reducer;
|
||||
return useCallback(
|
||||
@@ -403,7 +403,7 @@ export const useSelectNode = (reducer: SuggestionReducer) => {
|
||||
},
|
||||
};
|
||||
const insertText = `${ExpressionEditorToken.FullStart}${fullPath}${ExpressionEditorToken.FullEnd}`;
|
||||
// 替换文本
|
||||
// replacement text
|
||||
Transforms.insertText(state.model.editor, insertText, {
|
||||
at: selection,
|
||||
});
|
||||
@@ -412,12 +412,12 @@ export const useSelectNode = (reducer: SuggestionReducer) => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 挂载监听器 */
|
||||
/** mount the listener */
|
||||
export const useListeners = (reducer: SuggestionReducer) => {
|
||||
const [state, dispatch] = reducer;
|
||||
|
||||
useEffect(() => {
|
||||
// 挂载监听: 鼠标点击事件
|
||||
// Mount Listening: Mouse Click Events
|
||||
const mouseHandler = (e: MouseEvent) => {
|
||||
if (!state.visible || !state.ref.suggestion.current) {
|
||||
return;
|
||||
@@ -439,13 +439,13 @@ export const useListeners = (reducer: SuggestionReducer) => {
|
||||
window.removeEventListener('mousedown', mouseHandler);
|
||||
};
|
||||
return () => {
|
||||
// 销毁时卸载监听器防止内存泄露
|
||||
// Prevent memory leaks by uninstalling listeners when destroyed
|
||||
mouseDisposer();
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
useEffect(() => {
|
||||
// 挂载监听: 编辑器选择事件
|
||||
// Mount Listening: Editor Selection Event
|
||||
const editorSelectDisposer = state.model.on<ExpressionEditorEvent.Select>(
|
||||
ExpressionEditorEvent.Select,
|
||||
payload =>
|
||||
@@ -455,13 +455,13 @@ export const useListeners = (reducer: SuggestionReducer) => {
|
||||
}),
|
||||
);
|
||||
return () => {
|
||||
// 销毁时卸载监听器防止内存泄露
|
||||
// Prevent memory leaks by uninstalling listeners when destroyed
|
||||
editorSelectDisposer();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// 挂载监听: 编辑器拼音输入事件
|
||||
// Mount Monitor: Editor Pinyin Input Event
|
||||
const compositionStartDisposer =
|
||||
state.model.on<ExpressionEditorEvent.CompositionStart>(
|
||||
ExpressionEditorEvent.CompositionStart,
|
||||
@@ -472,13 +472,13 @@ export const useListeners = (reducer: SuggestionReducer) => {
|
||||
}),
|
||||
);
|
||||
return () => {
|
||||
// 销毁时卸载监听器防止内存泄露
|
||||
// Prevent memory leaks by uninstalling listeners when destroyed
|
||||
compositionStartDisposer();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化前首次渲染激活DOM
|
||||
// First render activation DOM before initialization
|
||||
if (state.initialized) {
|
||||
return;
|
||||
}
|
||||
@@ -489,7 +489,7 @@ export const useListeners = (reducer: SuggestionReducer) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化前监听到DOM激活后隐藏
|
||||
// Listen to DOM activation before initialization and hide after activation
|
||||
if (state.initialized || state.visible) {
|
||||
return;
|
||||
}
|
||||
@@ -503,14 +503,14 @@ export const useListeners = (reducer: SuggestionReducer) => {
|
||||
}, [state]);
|
||||
};
|
||||
|
||||
/** 键盘上下回车键选中节点 */
|
||||
/** Keyboard Enter up and down to select the node */
|
||||
export const useKeyboardSelect = (
|
||||
reducer: SuggestionReducer,
|
||||
selectNode: (node: ExpressionEditorTreeNode) => void,
|
||||
) => {
|
||||
const [state, dispatch] = reducer;
|
||||
|
||||
// 键盘上下
|
||||
// Keyboard up and down
|
||||
useEffect(() => {
|
||||
const keyboardArrowHandler = event => {
|
||||
if (
|
||||
@@ -526,34 +526,34 @@ export const useKeyboardSelect = (
|
||||
}
|
||||
const { optionList, selectedIndex } = uiOptions;
|
||||
if (optionList.length === 1) {
|
||||
// 仅有一项可选项的时候不做处理
|
||||
// Do not deal with when there is only one option
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
let newIndex = selectedIndex;
|
||||
if (event.key === 'ArrowDown') {
|
||||
// 如果当前没有高亮的选项或者是最后一个选项,则高亮第一个选项
|
||||
// If there is currently no highlighted option or the last option, highlight the first option
|
||||
newIndex =
|
||||
selectedIndex === -1 || selectedIndex === optionList.length - 1
|
||||
? 0
|
||||
: selectedIndex + 1;
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
// 如果当前没有高亮的选项或者是第一个选项,则高亮最后一个选项
|
||||
// If there is currently no highlighted option or the first option, highlight the last option
|
||||
newIndex =
|
||||
selectedIndex <= 0 ? optionList.length - 1 : selectedIndex - 1;
|
||||
}
|
||||
const selectedOption = optionList[newIndex];
|
||||
// 更新高亮选项
|
||||
// Update highlighting options
|
||||
if (selectedIndex !== -1) {
|
||||
optionList[selectedIndex].classList.remove(
|
||||
SuggestionViewUtils.keyboardSelectedClassName(),
|
||||
);
|
||||
}
|
||||
SuggestionViewUtils.setUIOptionSelected(selectedOption);
|
||||
// 将新选中的选项滚动到视图中
|
||||
// Scroll the newly selected option into view
|
||||
selectedOption.scrollIntoView({
|
||||
behavior: 'smooth', // 平滑滚动
|
||||
block: 'nearest', // 最接近的视图边界,可能是顶部或底部
|
||||
behavior: 'smooth', // Smooth scrolling
|
||||
block: 'nearest', // The closest view boundary, possibly the top or bottom
|
||||
});
|
||||
};
|
||||
document.addEventListener('keydown', keyboardArrowHandler);
|
||||
@@ -562,7 +562,7 @@ export const useKeyboardSelect = (
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
// 键盘回车
|
||||
// Keyboard Enter
|
||||
useEffect(() => {
|
||||
const keyboardEnterHandler = event => {
|
||||
if (
|
||||
@@ -596,13 +596,13 @@ export const useKeyboardSelect = (
|
||||
!variableTreeNode.variable?.children ||
|
||||
variableTreeNode.variable?.children.length === 0
|
||||
) {
|
||||
// 叶子节点
|
||||
// leaf node
|
||||
return;
|
||||
}
|
||||
// 非叶子节点,光标向前移动两格
|
||||
// Non-leaf node, move the cursor forward two spaces
|
||||
const { selection } = state.model.editor;
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
// 向前移动两个字符的光标
|
||||
// Move the cursor two characters forward
|
||||
Transforms.move(state.model.editor, { distance: 2, reverse: true });
|
||||
}
|
||||
SuggestionViewUtils.preventVisibleJitter(reducer);
|
||||
@@ -613,7 +613,7 @@ export const useKeyboardSelect = (
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
// 键盘 ESC 取消弹窗
|
||||
// Keyboard ESC cancel pop-up window
|
||||
useEffect(() => {
|
||||
const keyboardESCHandler = event => {
|
||||
if (
|
||||
@@ -635,17 +635,17 @@ export const useKeyboardSelect = (
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
// 默认选中首项
|
||||
// First item selected by default
|
||||
useEffect(() => {
|
||||
SuggestionViewUtils.selectFirstUIOption(state);
|
||||
}, [state]);
|
||||
};
|
||||
|
||||
/** 等待semi组件数据更新后的副作用 */
|
||||
/** Side effects of waiting for semi component data to be updated */
|
||||
export const useRenderEffect = (reducer: SuggestionReducer) => {
|
||||
const [state, dispatch] = reducer;
|
||||
|
||||
// 组件树状数据更新后设置搜索值
|
||||
// Set the search value after the component tree data is updated
|
||||
useEffect(() => {
|
||||
if (!state.renderEffect.search || !state.parseData) {
|
||||
return;
|
||||
@@ -667,7 +667,7 @@ export const useRenderEffect = (reducer: SuggestionReducer) => {
|
||||
});
|
||||
}, [state]);
|
||||
|
||||
// 搜索过滤后是否为空
|
||||
// Is it empty after searching for filters?
|
||||
useEffect(() => {
|
||||
if (!state.renderEffect.filtered) {
|
||||
return;
|
||||
|
||||
@@ -45,7 +45,7 @@ interface ExpressionEditorSuggestionProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动提示
|
||||
* autoprompt
|
||||
*/
|
||||
export const ExpressionEditorSuggestion: FC<
|
||||
ExpressionEditorSuggestionProps
|
||||
|
||||
@@ -85,7 +85,7 @@ export const suggestionReducer = (
|
||||
action.payload as SuggestionActionPayload<SuggestionActionType.SetVisible>;
|
||||
if (state.entities.selectorBoxConfig) {
|
||||
if (visible) {
|
||||
state.entities.selectorBoxConfig.disabled = true; // 防止鼠标拖选不触发点击
|
||||
state.entities.selectorBoxConfig.disabled = true; // Prevent mouse dragging from triggering clicks
|
||||
}
|
||||
if (!visible) {
|
||||
state.entities.selectorBoxConfig.disabled = false;
|
||||
@@ -166,7 +166,7 @@ export const suggestionReducer = (
|
||||
return state;
|
||||
};
|
||||
|
||||
/** 获取状态 */
|
||||
/** Get status */
|
||||
export const useSuggestionReducer = (
|
||||
initialState: Omit<
|
||||
SuggestionState,
|
||||
@@ -182,15 +182,15 @@ export const useSuggestionReducer = (
|
||||
): SuggestionReducer => {
|
||||
const [state, dispatch]: SuggestionReducer = useReducer(suggestionReducer, {
|
||||
...initialState,
|
||||
initialized: false, // 初始化
|
||||
version: 0, // 更新状态计数
|
||||
key: 0, // 用于触发 react 重新渲染组件
|
||||
variableTree: [], // 用于展示的组件树
|
||||
visible: true, // 默认显示,让ref能访问到DOM
|
||||
hiddenDOM: true, // 默认隐藏,让用户看不到UI
|
||||
allowVisibleChange: true, // 允许visible变更
|
||||
initialized: false, // initialization
|
||||
version: 0, // update status count
|
||||
key: 0, // Used to trigger react to re-render components
|
||||
variableTree: [], // Component tree for presentation
|
||||
visible: true, // Default display, allowing ref to access the DOM
|
||||
hiddenDOM: true, // Hidden by default, so that users cannot see the UI.
|
||||
allowVisibleChange: true, // Allow visible changes
|
||||
renderEffect: {
|
||||
// 渲染副作用
|
||||
// rendering side effects
|
||||
search: false,
|
||||
filtered: false,
|
||||
},
|
||||
|
||||
@@ -60,27 +60,27 @@ export class ExpressionEditorModel {
|
||||
this.innerLines = ExpressionEditorParser.deserialize(initialValue);
|
||||
}
|
||||
|
||||
/** 设置变量树 */
|
||||
/** Set variable tree */
|
||||
public setVariableTree(variableTree: ExpressionEditorTreeNode[]): void {
|
||||
this.innerVariableTree = variableTree;
|
||||
}
|
||||
|
||||
/** 获取变量树 */
|
||||
/** Get variable tree */
|
||||
public get variableTree(): ExpressionEditorTreeNode[] {
|
||||
return this.innerVariableTree;
|
||||
}
|
||||
|
||||
/** 获取行数据 */
|
||||
/** Get row data */
|
||||
public get lines(): ExpressionEditorLine[] {
|
||||
return this.innerLines;
|
||||
}
|
||||
|
||||
/** 获取序列化值 */
|
||||
/** Get Serialized Value */
|
||||
public get value(): string {
|
||||
return this.innerValue;
|
||||
}
|
||||
|
||||
/** 外部设置模型值 */
|
||||
/** External setting model values */
|
||||
public setValue(value: string): void {
|
||||
if (value === this.innerValue) {
|
||||
return;
|
||||
@@ -90,22 +90,22 @@ export class ExpressionEditorModel {
|
||||
this.syncEditorValue();
|
||||
}
|
||||
|
||||
/** 同步选中状态 */
|
||||
/** Synchronize selected state */
|
||||
public setFocus(focus: boolean): void {
|
||||
if (this.innerFocus === focus) {
|
||||
return;
|
||||
}
|
||||
this.innerFocus = focus;
|
||||
if (focus) {
|
||||
// 首次选中时主动触发选区事件,主动触发变量推荐
|
||||
// Active trigger selection event when first selected, active trigger variable recommendation
|
||||
this.select(this.lines);
|
||||
} else if (this.innerValue !== '' && this.editor.children.length !== 0) {
|
||||
// 触发失焦且编辑器内容不为空,则重置选区
|
||||
// Trigger out of focus and editor content is not empty, reset the selection
|
||||
Transforms.select(this.editor, Editor.start(this.editor, []));
|
||||
}
|
||||
}
|
||||
|
||||
/** 注册事件 */
|
||||
/** Register an event */
|
||||
public on<T extends ExpressionEditorEvent>(
|
||||
event: T,
|
||||
callback: (params: ExpressionEditorEventParams<T>) => void,
|
||||
@@ -116,7 +116,7 @@ export class ExpressionEditorModel {
|
||||
};
|
||||
}
|
||||
|
||||
/** 数据变更事件 */
|
||||
/** data change event */
|
||||
public change(lines: ExpressionEditorLine[]): void {
|
||||
const isAstChange = this.editor.operations.some(
|
||||
op => 'set_selection' !== op.type,
|
||||
@@ -132,7 +132,7 @@ export class ExpressionEditorModel {
|
||||
});
|
||||
}
|
||||
|
||||
/** 选中事件 */
|
||||
/** selected event */
|
||||
public select(lines: ExpressionEditorLine[]): void {
|
||||
const { selection } = this.editor;
|
||||
if (!selection || !Range.isCollapsed(selection)) {
|
||||
@@ -143,7 +143,7 @@ export class ExpressionEditorModel {
|
||||
selection.anchor.path[0] !== selection.focus.path[0] ||
|
||||
selection.anchor.path[1] !== selection.focus.path[1]
|
||||
) {
|
||||
// 框选
|
||||
// box selection
|
||||
this.emitter.emit(ExpressionEditorEvent.Select, {
|
||||
content: '',
|
||||
offset: -1,
|
||||
@@ -169,7 +169,7 @@ export class ExpressionEditorModel {
|
||||
});
|
||||
}
|
||||
|
||||
/** 键盘事件 */
|
||||
/** keyboard event */
|
||||
public keydown(
|
||||
event: Parameters<KeyboardEventHandler<HTMLDivElement>>[0],
|
||||
): void {
|
||||
@@ -184,7 +184,7 @@ export class ExpressionEditorModel {
|
||||
reverse: true,
|
||||
});
|
||||
setTimeout(() => {
|
||||
// slate UI 渲染滞后
|
||||
// Slate UI Rendering
|
||||
this.select(this.innerLines);
|
||||
}, 0);
|
||||
}
|
||||
@@ -203,7 +203,7 @@ export class ExpressionEditorModel {
|
||||
}
|
||||
}
|
||||
|
||||
/** 开始输入拼音 */
|
||||
/** Start typing pinyin */
|
||||
public compositionStart(
|
||||
event: CompositionEventHandler<HTMLDivElement>,
|
||||
): void {
|
||||
@@ -212,7 +212,7 @@ export class ExpressionEditorModel {
|
||||
});
|
||||
}
|
||||
|
||||
/** 装饰叶子节点 */
|
||||
/** Decorative leaf node */
|
||||
public get decorate(): ([node, path]: NodeEntry) => ExpressionEditorRange[] {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
@@ -221,7 +221,7 @@ export class ExpressionEditorModel {
|
||||
if (!Text.isText(node)) {
|
||||
return ranges;
|
||||
}
|
||||
// 计算表达式合法/非法
|
||||
// Evaluation expressions are legal/illegal
|
||||
const validateList = ExpressionEditorValidator.lineTextValidate({
|
||||
lineText: node.text,
|
||||
tree: self.innerVariableTree,
|
||||
@@ -247,7 +247,7 @@ export class ExpressionEditorModel {
|
||||
if (!this.innerFocus) {
|
||||
return ranges;
|
||||
}
|
||||
// 以下是计算当前选中表达式逻辑
|
||||
// The following is the logic for evaluating the currently selected expression
|
||||
const selectedItem = self.isValidateSelectPath([node, path]);
|
||||
const selectedValidItem = validateList.find(
|
||||
validateData =>
|
||||
@@ -274,17 +274,17 @@ export class ExpressionEditorModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步编辑器实例内容
|
||||
* > **NOTICE:** *为确保不影响性能,应仅在外部值变更导致编辑器值与模型值不一致时调用*
|
||||
* Synchronize editor instance content
|
||||
* > ** NOTICE: ** * To ensure that performance is not affected, it should only be called when an external value change causes the editor value to be inconsistent with the model value.
|
||||
*/
|
||||
private syncEditorValue(): void {
|
||||
// 删除编辑器内所有行
|
||||
// Delete all lines in the editor
|
||||
this.editor.children.forEach((line, index) => {
|
||||
Transforms.removeNodes(this.editor, {
|
||||
at: [index],
|
||||
});
|
||||
});
|
||||
// 重新在编辑器插入当前行内容
|
||||
// Reinsert the current line content in the editor
|
||||
this.lines.forEach((line, index) => {
|
||||
Transforms.insertNodes(this.editor, line, {
|
||||
at: [this.editor.children.length],
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
} from '../constant';
|
||||
|
||||
export namespace ExpressionEditorParserBuiltin {
|
||||
/** 计算开始和结束标识的序号 */
|
||||
/** Calculate the serial number of the start and end tags */
|
||||
export const tokenOffset = (line: {
|
||||
lineContent: string;
|
||||
lineOffset: number;
|
||||
@@ -52,7 +52,7 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
firstEndTokenOffset + 2,
|
||||
);
|
||||
if (endChars !== ExpressionEditorToken.FullEnd) {
|
||||
// 结束符号 "}}" 不完整
|
||||
// End symbol "}}" is incomplete
|
||||
return;
|
||||
}
|
||||
const lastStartTokenOffset = content.lastIndexOf(
|
||||
@@ -64,7 +64,7 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
lastStartTokenOffset + 1,
|
||||
);
|
||||
if (startChars !== ExpressionEditorToken.FullStart) {
|
||||
// 开始符号 "{{" 不完整
|
||||
// The opening symbol "{{" is incomplete
|
||||
return;
|
||||
}
|
||||
return {
|
||||
@@ -73,7 +73,7 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
};
|
||||
};
|
||||
|
||||
/** 从行内容提取内容 */
|
||||
/** Extract content from line content */
|
||||
export const extractContent = (params: {
|
||||
lineContent: string;
|
||||
lineOffset: number;
|
||||
@@ -102,7 +102,7 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
};
|
||||
};
|
||||
|
||||
/** 根据 offset 将文本内容切分为可用与不可用 */
|
||||
/** Split text content into available and unavailable by offset */
|
||||
export const sliceReachable = (params: {
|
||||
content: string;
|
||||
offset: number;
|
||||
@@ -119,29 +119,29 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
};
|
||||
};
|
||||
|
||||
/** 切分文本 */
|
||||
/** Split text */
|
||||
export const splitText = (pathString: string): string[] => {
|
||||
// 得到的分割数组,初始为原字符串以"."分割的结果
|
||||
// The obtained split array, initially the result of splitting the original string with "."
|
||||
const segments = pathString.split(ExpressionEditorToken.Separator);
|
||||
|
||||
// 定义结果数组,并处理连续的"."导致的空字符串
|
||||
// Define the result array and handle empty strings resulting from consecutive "."
|
||||
const result: string[] = [];
|
||||
|
||||
segments.forEach(segment => {
|
||||
if (!segment.match(/\[\d+\]/)) {
|
||||
// 如果不是数组索引,直接加入结果数组,即使是空字符串也加入以保持正确的分割
|
||||
// If it is not an array index, add the result array directly, even if it is an empty string, to maintain the correct segmentation
|
||||
result.push(segment);
|
||||
return;
|
||||
}
|
||||
// 如果当前段是数组索引,将前面的字符串和当前数组索引分别加入结果数组
|
||||
// If the current segment is an array index, add the previous string and the current array index to the result array, respectively
|
||||
const lastSegmentIndex = segment.lastIndexOf(
|
||||
ExpressionEditorToken.ArrayStart,
|
||||
);
|
||||
const key = segment.substring(0, lastSegmentIndex);
|
||||
const index = segment.substring(lastSegmentIndex);
|
||||
// {{array[0]}} 中的 array
|
||||
// Array in {{array [0]}}
|
||||
result.push(key);
|
||||
// {{array[0]}} 中的 [0]
|
||||
// [0] in {{array [0]}}
|
||||
result.push(index);
|
||||
return;
|
||||
});
|
||||
@@ -149,14 +149,14 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
return result;
|
||||
};
|
||||
|
||||
/** 字符串解析为路径 */
|
||||
/** String parsed as path */
|
||||
export const toSegments = (
|
||||
text: string,
|
||||
): ExpressionEditorSegment[] | undefined => {
|
||||
const textSegments = ExpressionEditorParserBuiltin.splitText(text);
|
||||
const segments: ExpressionEditorSegment[] = [];
|
||||
const validate = textSegments.every((textSegment, index) => {
|
||||
// 数组下标
|
||||
// array subscript
|
||||
if (
|
||||
textSegment.startsWith(ExpressionEditorToken.ArrayStart) &&
|
||||
textSegment.endsWith(ExpressionEditorToken.ArrayEnd)
|
||||
@@ -164,7 +164,7 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
const arrayIndexString = textSegment.slice(1, -1);
|
||||
const arrayIndex = Number(arrayIndexString);
|
||||
if (arrayIndexString === '' || Number.isNaN(arrayIndex)) {
|
||||
// index 必须是数字
|
||||
// Index must be a number
|
||||
return false;
|
||||
}
|
||||
const lastSegment = segments[segments.length - 1];
|
||||
@@ -172,7 +172,7 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
!lastSegment ||
|
||||
lastSegment.type !== ExpressionEditorSegmentType.ObjectKey
|
||||
) {
|
||||
// 数组索引必须在 key 之后
|
||||
// The array index must be after the key
|
||||
return false;
|
||||
}
|
||||
segments.push({
|
||||
@@ -181,7 +181,7 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
arrayIndex,
|
||||
});
|
||||
}
|
||||
// 最后一行空文本
|
||||
// The last empty line of text
|
||||
else if (index === textSegments.length - 1 && textSegment === '') {
|
||||
segments.push({
|
||||
type: ExpressionEditorSegmentType.EndEmpty,
|
||||
@@ -207,11 +207,11 @@ export namespace ExpressionEditorParserBuiltin {
|
||||
}
|
||||
|
||||
export namespace ExpressionEditorParser {
|
||||
/** 序列化 */
|
||||
/** Serialization */
|
||||
export const serialize = (value: ExpressionEditorLine[]) =>
|
||||
value.map(n => Node.string(n)).join('\n');
|
||||
|
||||
/** 反序列化 */
|
||||
/** deserialization */
|
||||
export const deserialize = (text: string): ExpressionEditorLine[] => {
|
||||
const lines = text.split('\n');
|
||||
return lines.map(line => ({
|
||||
|
||||
@@ -165,7 +165,7 @@ export namespace ExpressionEditorTreeHelper {
|
||||
const lastSegment = segments[segments.length - 1];
|
||||
const segmentsRemovedLast =
|
||||
lastSegment.type === ExpressionEditorSegmentType.ArrayIndex
|
||||
? segments.slice(0, segments.length - 2) // 数组索引属于上一层级,需要去除两层
|
||||
? segments.slice(0, segments.length - 2) // The array index belongs to the previous level, and two layers need to be removed.
|
||||
: segments.slice(0, segments.length - 1);
|
||||
let treeLayer = tree;
|
||||
segmentsRemovedLast.forEach(segment => {
|
||||
@@ -193,7 +193,7 @@ export namespace ExpressionEditorTreeHelper {
|
||||
const pathList: { objectKey: string; arrayIndex?: number }[] = [];
|
||||
while (current) {
|
||||
if (current.variable?.type === ViewVariableType.ArrayObject) {
|
||||
// 默认第0个
|
||||
// Default 0th
|
||||
pathList.unshift({
|
||||
objectKey: current.label,
|
||||
arrayIndex: 0,
|
||||
@@ -213,7 +213,7 @@ export namespace ExpressionEditorTreeHelper {
|
||||
const pathItem = pathList[pathIndex];
|
||||
pathIndex++;
|
||||
if (pathItem.objectKey !== segment.objectKey) {
|
||||
// 退出循环
|
||||
// exit the loop
|
||||
return true;
|
||||
}
|
||||
const nextSegment = segments[index + 1];
|
||||
@@ -253,7 +253,7 @@ export namespace ExpressionEditorTreeHelper {
|
||||
return false;
|
||||
};
|
||||
const beforeTreeNode = treeBranch[treeBranch.length - 1];
|
||||
// 确认非法情况:是否对非数组类型使用数组索引
|
||||
// Verify Illegal Case: Whether to Use Array Indexing for Non-Array Types
|
||||
if (
|
||||
segment.type === ExpressionEditorSegmentType.ArrayIndex &&
|
||||
beforeTreeNode &&
|
||||
@@ -262,7 +262,7 @@ export namespace ExpressionEditorTreeHelper {
|
||||
) {
|
||||
return itemInvalid();
|
||||
}
|
||||
// 确认非法情况:数组只能跟随数组下标
|
||||
// Confirm illegal condition: Array can only follow array subscript
|
||||
if (
|
||||
beforeTreeNode?.variable?.type &&
|
||||
ViewVariableType.isArrayType(beforeTreeNode.variable.type) &&
|
||||
@@ -270,12 +270,12 @@ export namespace ExpressionEditorTreeHelper {
|
||||
) {
|
||||
return itemInvalid();
|
||||
}
|
||||
// 忽略
|
||||
// ignore
|
||||
if (segment.type !== ExpressionEditorSegmentType.ObjectKey) {
|
||||
return itemValid();
|
||||
}
|
||||
const treeNode = treeLayer.find(node => node.label === segment.objectKey);
|
||||
// 确认非法情况:每一个 object key 必须对应一个 variable node
|
||||
// Verify illegal condition: each object key must correspond to a variable node
|
||||
if (!treeNode) {
|
||||
return itemInvalid();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useParams } from 'react-router-dom';
|
||||
export const useOpenWorkflowDetail = () => {
|
||||
const { bot_id: botId } = useParams<DynamicParams>();
|
||||
|
||||
/** 打开流程详情页 */
|
||||
/** Open the process details page */
|
||||
const openWorkflowDetailPage = ({
|
||||
workflowId,
|
||||
spaceId,
|
||||
|
||||
@@ -103,7 +103,7 @@ interface WorkflowListReturn {
|
||||
const defaultPageSize = 20;
|
||||
|
||||
/**
|
||||
* 流程列表
|
||||
* process list
|
||||
*/
|
||||
export function useWorkflowList({
|
||||
pageSize = defaultPageSize,
|
||||
@@ -112,7 +112,7 @@ export function useWorkflowList({
|
||||
fetchWorkflowListApi = workflowApi.GetWorkFlowList.bind(workflowApi),
|
||||
}: {
|
||||
pageSize?: number;
|
||||
/** 是否开启数据获取 */
|
||||
/** Whether to enable data acquisition */
|
||||
enabled?: boolean;
|
||||
from?: WorkflowModalFrom;
|
||||
fetchWorkflowListApi?: (
|
||||
@@ -208,7 +208,7 @@ export function useWorkflowList({
|
||||
if (params.bind_biz_type === BindBizType.Scene && params.bind_biz_id) {
|
||||
const resp = await workflowApi.WorkflowListByBindBiz(params);
|
||||
result.total = (resp.data.total as number) ?? 0;
|
||||
// 设置流程权限
|
||||
// Set process permissions
|
||||
result.workflow_list = (resp.data.workflow_list ?? []).map(
|
||||
(item): WorkflowInfo => {
|
||||
const authInfo = {
|
||||
@@ -223,7 +223,7 @@ export function useWorkflowList({
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// 多人协作场景,DEV 模式需要展示 Blockwise workflow(除了流程列表引用)
|
||||
// Multiplayer collaboration scenarios, DEV mode needs to demonstrate Blockwise workflow (except for process list references)
|
||||
Object.assign(params, {
|
||||
schema_type_list: [SchemaType.FDL],
|
||||
checker:
|
||||
@@ -233,15 +233,15 @@ export function useWorkflowList({
|
||||
});
|
||||
|
||||
const isDouyinBot = params.bind_biz_type === BindBizType.DouYinBot;
|
||||
// 如果不是抖音分身模式,搜索参数不携带 bind_biz_id 参数
|
||||
// 否则会导致某个工作流关联到 Agent 后0,之后在该工作流添加子工作流时看不到工作流列表
|
||||
// If not Douyin doppelganger mode, search parameters do not carry bind_biz_id parameters
|
||||
// Otherwise, it will cause a workflow to be associated with the agent after 0, and then the workflow list will not be visible when a child workflow is added to the workflow
|
||||
const fetchParams = isDouyinBot
|
||||
? params
|
||||
: omit(params, ['bind_biz_id']);
|
||||
|
||||
const resp = await fetchWorkflowListApi(fetchParams);
|
||||
result.total = (resp.data.total as number) ?? 0;
|
||||
// 设置流程权限
|
||||
// Set process permissions
|
||||
result.workflow_list = (resp.data.workflow_list ?? []).map(
|
||||
(item): WorkflowInfo => {
|
||||
let authInfo = {
|
||||
@@ -339,13 +339,13 @@ export function useWorkflowList({
|
||||
return pageData.pages[pageData.pages.length - 1].total ?? 0;
|
||||
}, [pageData]);
|
||||
|
||||
// 复制
|
||||
// copy
|
||||
const handleCopy = async (item: WorkflowInfo) => {
|
||||
if (!item.workflow_id || !spaceId) {
|
||||
throw new CustomError('normal_error', 'miss workflowId or spaceID');
|
||||
}
|
||||
|
||||
// 检查复制权限
|
||||
// Check copy permissions
|
||||
if (!item.authInfo.can_copy) {
|
||||
throw new CustomError('normal_error', 'no copy permission');
|
||||
}
|
||||
@@ -388,10 +388,10 @@ export function useWorkflowList({
|
||||
},
|
||||
});
|
||||
|
||||
// 兜底服务主从延迟
|
||||
// Bottom line leader/follower delay
|
||||
await wait(300);
|
||||
|
||||
// 刷新列表
|
||||
// refresh list
|
||||
refetch();
|
||||
} catch (error) {
|
||||
reporter.error({
|
||||
@@ -402,13 +402,13 @@ export function useWorkflowList({
|
||||
}
|
||||
};
|
||||
|
||||
// 删除
|
||||
// delete
|
||||
const handleDelete = async (item: WorkflowInfo) => {
|
||||
if (!item.workflow_id || !spaceId) {
|
||||
throw new CustomError('normal_error', 'miss workflowId or spaceID');
|
||||
}
|
||||
|
||||
// 先检查删除权限
|
||||
// Check the delete permission first
|
||||
if (!item.authInfo.can_delete) {
|
||||
throw new CustomError('normal_error', 'no delete permission');
|
||||
}
|
||||
@@ -422,7 +422,7 @@ export function useWorkflowList({
|
||||
|
||||
let deleteType = DeleteType.CanDelete;
|
||||
|
||||
// 从服务端查询删除模式
|
||||
// Delete mode from server level query
|
||||
const resp = await workflowApi.GetDeleteStrategy({
|
||||
space_id: spaceId,
|
||||
workflow_id: item.workflow_id,
|
||||
@@ -461,10 +461,10 @@ export function useWorkflowList({
|
||||
message: 'workflow_list_delete_row_success',
|
||||
});
|
||||
|
||||
// 兜底服务主从延迟
|
||||
// Bottom line leader/follower delay
|
||||
await wait(300);
|
||||
|
||||
// 刷新列表
|
||||
// refresh list
|
||||
refetch();
|
||||
} catch (error) {
|
||||
reporter.error({
|
||||
@@ -478,17 +478,17 @@ export function useWorkflowList({
|
||||
}
|
||||
};
|
||||
return {
|
||||
/** 是否可删除 */
|
||||
/** Can it be deleted */
|
||||
canDelete,
|
||||
/** 删除策略 */
|
||||
/** delete policy */
|
||||
deleteType,
|
||||
/** 删除方法 */
|
||||
/** Delete method */
|
||||
handleDelete: canDelete ? deleteFuc : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
// 列表筛选状态
|
||||
// list filter status
|
||||
flowType,
|
||||
setFlowType,
|
||||
flowMode,
|
||||
@@ -505,31 +505,31 @@ export function useWorkflowList({
|
||||
setOrderBy,
|
||||
loginUserCreate,
|
||||
setLoginUserCreate,
|
||||
/** 更新筛选参数 */
|
||||
/** Update filter parameters */
|
||||
updatePageParam,
|
||||
// 列表获取
|
||||
/** 流程列表数据 */
|
||||
// list acquisition
|
||||
/** process list data */
|
||||
workflowList,
|
||||
/** 流程总数 */
|
||||
/** total number of processes */
|
||||
total,
|
||||
/** 获取列表请求错误 */
|
||||
/** Get list request error */
|
||||
queryError,
|
||||
/** 拉取下一页数据 */
|
||||
/** Pull the next page of data */
|
||||
fetchNextPage,
|
||||
/** 是否有下一页 */
|
||||
/** Is there a next page? */
|
||||
hasNextPage,
|
||||
/** 获取数据中 */
|
||||
/** Acquiring data */
|
||||
isFetching,
|
||||
/** 获取下一页数据中 */
|
||||
/** Get the next page of data */
|
||||
isFetchingNextPage,
|
||||
/** 加载状态 */
|
||||
/** loading status */
|
||||
loadingStatus,
|
||||
/** 重新加载 */
|
||||
/** Reload */
|
||||
refetch,
|
||||
// 列表操作
|
||||
/** 复制流程 */
|
||||
// list operation
|
||||
/** replication process */
|
||||
handleCopy,
|
||||
// /** 删除流程 */
|
||||
// /** Delete process */process */
|
||||
handleDelete,
|
||||
} as const;
|
||||
}
|
||||
|
||||
@@ -26,23 +26,23 @@ import { type ResourceInfo } from '@coze-arch/bot-api/plugin_develop';
|
||||
export { type ResourceInfo };
|
||||
|
||||
export interface WorkflowResourceActionProps {
|
||||
/* 刷新列表函数 */
|
||||
/* refresh list function */
|
||||
refreshPage?: () => void;
|
||||
spaceId?: string;
|
||||
/* 当前登录用户 id */
|
||||
/* Current login user id */
|
||||
userId?: string;
|
||||
getCommonActions?: (
|
||||
libraryResource: ResourceInfo,
|
||||
) => NonNullable<TableActionProps['actionList']>;
|
||||
}
|
||||
export interface WorkflowResourceActionReturn {
|
||||
/* 打开 workflow 创建弹窗 */
|
||||
/* Open the workflow creation pop-up window */
|
||||
openCreateModal: (flowMode?: WorkflowMode) => void;
|
||||
/* 创建、删除等操作的全局弹窗,直接挂载到列表父容器上 */
|
||||
/* Global pop-ups for create, delete, etc. are directly mounted on the list parent container */
|
||||
workflowResourceModals: ReactNode[];
|
||||
/* 在 Table 组件的 columns 的 render 里调用,返回 Table.TableAction 组件 */
|
||||
/* Called in the render of the columns of the Table component, returning the Table. TableAction component */
|
||||
renderWorkflowResourceActions: (record: ResourceInfo) => ReactNode;
|
||||
/* 资源 item 点击 */
|
||||
/* Resource item click */
|
||||
handleWorkflowResourceClick: (record: ResourceInfo) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import { type CommonActionProps, type CommonActionReturn } from './type';
|
||||
export const useCopyAction = (props: CommonActionProps): CommonActionReturn => {
|
||||
const { spaceId } = props;
|
||||
const navigate = useNavigate();
|
||||
// 复制
|
||||
// copy
|
||||
const handleCopy = async (item: ResourceInfo) => {
|
||||
if (!item.res_id || !spaceId) {
|
||||
throw new CustomError('normal_error', 'miss workflowId or spaceID');
|
||||
@@ -73,9 +73,9 @@ export const useCopyAction = (props: CommonActionProps): CommonActionReturn => {
|
||||
},
|
||||
});
|
||||
|
||||
// 兜底服务主从延迟
|
||||
// Bottom line leader/follower delay
|
||||
await wait(300);
|
||||
// 复制后跳转到详情页
|
||||
// After copying, jump to the details page
|
||||
navigate(
|
||||
`/work_flow?workflow_id=${data.workflow_id}&space_id=${spaceId}`,
|
||||
);
|
||||
|
||||
@@ -47,13 +47,13 @@ export const useCreateWorkflowModal = ({
|
||||
nameValidators,
|
||||
}: WorkflowResourceActionProps & {
|
||||
from?: WorkflowModalFrom;
|
||||
/** 当前项目 id,只在项目内的 workflow 有该字段 */
|
||||
/** The current project id, only the workflow within the project has this field */
|
||||
projectId?: string;
|
||||
bindBizType?: BindBizType;
|
||||
bindBizId?: string;
|
||||
onCreateSuccess?: ({ workflowId }: { workflowId: string }) => void;
|
||||
goWorkflowDetail?: (workflowId?: string, spaceId?: string) => void;
|
||||
/** 隐藏通过模板创建入口 */
|
||||
/** Hide entry created through template */
|
||||
hiddenTemplateEntry?: boolean;
|
||||
nameValidators?: RuleItem[];
|
||||
}) => {
|
||||
@@ -89,7 +89,7 @@ export const useCreateWorkflowModal = ({
|
||||
};
|
||||
|
||||
const workflowModalInitState = useMemo(() => {
|
||||
// 即将支持,敬请期待
|
||||
// Support soon, so stay tuned.
|
||||
if (isWorkflowMode || FLAGS['bot.community.store_imageflow']) {
|
||||
return {
|
||||
productCategory: 'all',
|
||||
@@ -154,14 +154,14 @@ export const useCreateWorkflowModal = ({
|
||||
onCreateSuccess({ workflowId });
|
||||
return;
|
||||
}
|
||||
// 编辑模式,不跳转,刷新当前列表
|
||||
// Edit mode, do not jump, refresh the current list
|
||||
if (formMode === 'update') {
|
||||
refreshPage?.();
|
||||
return;
|
||||
}
|
||||
|
||||
const navigateDelay = 500;
|
||||
// 由于后端数据同步慢,这里delay 500 ms
|
||||
// Due to slow back-end data synchronization, there is a delay of 500 ms
|
||||
setTimeout(() => {
|
||||
goWorkflowDetail?.(workflowId, spaceId);
|
||||
}, navigateDelay);
|
||||
|
||||
@@ -39,7 +39,7 @@ export const useDeleteAction = (
|
||||
const [deleteModalConfig, setDeleteModalConfig] =
|
||||
useState<DeleteModalConfig>();
|
||||
/**
|
||||
* 逻辑来自 useWorkflowList(@coze-workflow/components),由于入参变了,不再复用
|
||||
* Logic comes from useWorkflowList (@code-workflow/components), no longer reused because imported parameters have changed
|
||||
* @param item
|
||||
*/
|
||||
const handleDeleteWorkflowResource = async (item: ResourceInfo) => {
|
||||
@@ -56,7 +56,7 @@ export const useDeleteAction = (
|
||||
|
||||
let deleteType = DeleteType.CanDelete;
|
||||
|
||||
// 从服务端查询删除模式
|
||||
// Delete mode from server level query
|
||||
const resp = await workflowApi.GetDeleteStrategy({
|
||||
space_id: spaceId,
|
||||
workflow_id: item.res_id,
|
||||
@@ -95,10 +95,10 @@ export const useDeleteAction = (
|
||||
message: 'workflow_list_delete_row_success',
|
||||
});
|
||||
|
||||
// 兜底服务主从延迟
|
||||
// Bottom line leader/follower delay
|
||||
await wait(300);
|
||||
|
||||
// 刷新列表
|
||||
// refresh list
|
||||
refreshPage?.();
|
||||
} catch (error) {
|
||||
reporter.error({
|
||||
|
||||
@@ -40,8 +40,8 @@ export const usePublishAction = ({
|
||||
});
|
||||
|
||||
/**
|
||||
* NOTICE: 此函数由商店侧维护, 可联系 @gaoding
|
||||
* 发布/更新流程商品
|
||||
* NOTICE: This function is maintained by the store side, you can contact @gaoding.
|
||||
* Release/Update Process Product
|
||||
*/
|
||||
const onPublishStore = (item: ResourceInfo) => {
|
||||
setFlowMode(
|
||||
@@ -49,7 +49,7 @@ export const usePublishAction = ({
|
||||
? WorkflowMode.Imageflow
|
||||
: WorkflowMode.Workflow,
|
||||
);
|
||||
// 商店渲染流程需要 spaceId 信息, 在这个场景需要手动设置对应信息
|
||||
// The store rendering process requires spaceId information, and in this scene, the corresponding information needs to be set manually
|
||||
publishWorkflowModalHook.setSpace(spaceId);
|
||||
publishWorkflowModalHook.showModal({
|
||||
type: PublishWorkflowModal.WORKFLOW_INFO,
|
||||
|
||||
@@ -32,7 +32,7 @@ export const useWorkflowResourceClick = (spaceId?: string) => {
|
||||
goWorkflowDetail(workflowId, spaceId);
|
||||
};
|
||||
|
||||
/** 打开流程编辑页 */
|
||||
/** Open the process edit page */
|
||||
const goWorkflowDetail = (workflowId?: string, sId?: string) => {
|
||||
if (!workflowId || !sId) {
|
||||
return;
|
||||
|
||||
@@ -125,8 +125,8 @@ export const useWorkflowResourceMenuActions = (
|
||||
...(getCommonActions?.(record) ?? []),
|
||||
{
|
||||
hide:
|
||||
!enablePublishEntry || // 上架入口加白
|
||||
(!FLAGS['bot.community.store_imageflow'] && isImageFlow) || // Imageflow 不支持商店
|
||||
!enablePublishEntry || // The entrance on the shelf is white.
|
||||
(!FLAGS['bot.community.store_imageflow'] && isImageFlow) || // Imageflow does not support stores
|
||||
!isSelfCreator ||
|
||||
bizExtend?.plugin_id === '0',
|
||||
actionKey: 'publishWorkflowProduct',
|
||||
|
||||
@@ -45,7 +45,7 @@ export const parseWorkflowResourceBizExtend = (
|
||||
};
|
||||
};
|
||||
/**
|
||||
* 转换 ResourceInfo 为编辑 workflow 所需的 WorkflowInfoLocal 结构
|
||||
* Convert ResourceInfo to WorkflowInfoLocal structure required to edit workflow
|
||||
* @param resource
|
||||
*/
|
||||
export const transformResourceToWorkflowEditInfo = (
|
||||
|
||||
@@ -22,49 +22,49 @@ import { CustomError } from '@coze-arch/bot-error';
|
||||
import { FileBizType } from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
|
||||
/** 图片上传错误码 */
|
||||
/** image upload error code */
|
||||
export enum ImgUploadErrNo {
|
||||
Success = 0,
|
||||
/** 缺少文件 */
|
||||
/** Missing document */
|
||||
NoFile,
|
||||
/** 上传失败 */
|
||||
/** Upload failed. */
|
||||
UploadFail,
|
||||
/** 上传超时 */
|
||||
/** Upload timed out */
|
||||
UploadTimeout,
|
||||
/** 获取 URL 失败 */
|
||||
/** Failed to get URL */
|
||||
GetUrlFail,
|
||||
/** 校验异常, 但是不明确具体异常 */
|
||||
/** Check exception, but not specific exception */
|
||||
ValidateError,
|
||||
/** 文件尺寸超出限制 */
|
||||
/** File size exceeds limit */
|
||||
MaxSizeError,
|
||||
/** 文件类型不支持 */
|
||||
/** File type not supported */
|
||||
SuffixError,
|
||||
/** 最大宽度限制 */
|
||||
/** Maximum width limit */
|
||||
MaxWidthError,
|
||||
/** 最大高度限制 */
|
||||
/** Maximum height limit */
|
||||
MaxHeightError,
|
||||
/** 最小宽度限制 */
|
||||
/** minimum width limit */
|
||||
MinWidthError,
|
||||
/** 最小高度限制 */
|
||||
/** Minimum height limit */
|
||||
MinHeightError,
|
||||
/** 固定宽高比 */
|
||||
/** Fixed Aspect Ratio */
|
||||
AspectRatioError,
|
||||
}
|
||||
|
||||
export interface ImageRule {
|
||||
/** 文件大小限制, 单位 b, 1M = 1 * 1024 * 1024 */
|
||||
/** File size limit, unit b, 1M = 1 * 1024 * 1024 */
|
||||
maxSize?: number;
|
||||
/** 文件后缀 */
|
||||
/** file suffix */
|
||||
suffix?: string[];
|
||||
/** 最大宽度限制 */
|
||||
/** Maximum width limit */
|
||||
maxWidth?: number;
|
||||
/** 最大高度限制 */
|
||||
/** Maximum height limit */
|
||||
maxHeight?: number;
|
||||
/** 最小宽度限制 */
|
||||
/** minimum width limit */
|
||||
minWidth?: number;
|
||||
/** 最小高度限制 */
|
||||
/** Minimum height limit */
|
||||
minHeight?: number;
|
||||
/** 固定宽高比 */
|
||||
/** Fixed Aspect Ratio */
|
||||
aspectRatio?: number;
|
||||
}
|
||||
|
||||
@@ -82,34 +82,34 @@ type UploadResult =
|
||||
};
|
||||
|
||||
/**
|
||||
* Workflow 图片上传
|
||||
* Workflow image upload
|
||||
*/
|
||||
class ImageUploader {
|
||||
/** 任务 ID, 用于避免 ABA 问题 */
|
||||
/** Task ID to avoid ABA issues */
|
||||
private taskId = 0;
|
||||
/**
|
||||
* 上传模式
|
||||
* - api 直接使用接口上传
|
||||
* - uploader 上传到视频云服务, 走 workflow 服务. !海外版未经过测试
|
||||
* upload mode
|
||||
* - API directly uses the interface to upload
|
||||
* - uploader to Video Cloud as a Service, go workflow service.! Overseas version has not been tested
|
||||
*/
|
||||
mode: 'uploader' | 'api' = 'uploader';
|
||||
/** 校验规则 */
|
||||
/** validation rule */
|
||||
rules?: ImageRule;
|
||||
/** 上传的文件 */
|
||||
/** Uploaded file */
|
||||
file?: File;
|
||||
/** 展示 Url, 添加文件后生成, 用于预览 */
|
||||
/** Show URLs, generated after adding files, for preview */
|
||||
displayUrl?: string;
|
||||
/** 上传状态 */
|
||||
/** Upload Status */
|
||||
isUploading = false;
|
||||
/** 超时时间 */
|
||||
/** timeout */
|
||||
timeout?: number;
|
||||
/** 校验结果 */
|
||||
/** verification result */
|
||||
validateResult?: {
|
||||
isSuccess: boolean;
|
||||
errNo: ImgUploadErrNo;
|
||||
msg?: string;
|
||||
};
|
||||
/** 上传结果 */
|
||||
/** Upload result */
|
||||
uploadResult?: UploadResult;
|
||||
|
||||
constructor(config?: {
|
||||
@@ -122,7 +122,7 @@ class ImageUploader {
|
||||
this.timeout = config?.timeout ?? this.timeout;
|
||||
}
|
||||
|
||||
/** 选择待上传文件 */
|
||||
/** Select the file to upload */
|
||||
async select(file: File) {
|
||||
if (!file) {
|
||||
throw new CustomError('normal_error', '选择文件为空');
|
||||
@@ -140,16 +140,16 @@ class ImageUploader {
|
||||
});
|
||||
}
|
||||
|
||||
/** 上传图片 */
|
||||
/** Upload image */
|
||||
async upload() {
|
||||
// 未选择文件或文件不符合要求
|
||||
// No file was selected or the file does not meet the requirements
|
||||
if (!this.file || !this.validateResult?.isSuccess || this.isUploading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isUploading = true;
|
||||
|
||||
// 添加任务 ID,避免 ABA 问题
|
||||
// Add task IDs to avoid ABA issues
|
||||
this.taskId += 1;
|
||||
const currentId = this.taskId;
|
||||
|
||||
@@ -208,7 +208,7 @@ class ImageUploader {
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
// 获取 url
|
||||
// Get URL
|
||||
const resp = await workflowApi
|
||||
.SignImageURL(
|
||||
{
|
||||
@@ -301,7 +301,7 @@ class ImageUploader {
|
||||
reset() {
|
||||
this.file = undefined;
|
||||
if (this.displayUrl) {
|
||||
// 是内部链接
|
||||
// Is an internal link
|
||||
URL.revokeObjectURL(this.displayUrl);
|
||||
this.displayUrl = undefined;
|
||||
}
|
||||
@@ -319,7 +319,7 @@ class ImageUploader {
|
||||
|
||||
const rules = this.rules || {};
|
||||
|
||||
// 文件尺寸
|
||||
// file size
|
||||
if (rules.maxSize) {
|
||||
if (this.file.size > rules.maxSize) {
|
||||
this.validateResult = {
|
||||
@@ -333,7 +333,7 @@ class ImageUploader {
|
||||
}
|
||||
}
|
||||
|
||||
// 文件后缀
|
||||
// file suffix
|
||||
if (Array.isArray(rules.suffix) && rules.suffix.length > 0) {
|
||||
const fileExtension = getFileExtension(this.file.name);
|
||||
if (!rules.suffix.includes(fileExtension)) {
|
||||
@@ -348,7 +348,7 @@ class ImageUploader {
|
||||
}
|
||||
}
|
||||
|
||||
// 图片尺寸
|
||||
// image size
|
||||
const { width, height } = await getImageSize(this.displayUrl);
|
||||
|
||||
if (!width || !height) {
|
||||
@@ -445,14 +445,14 @@ function getBase64(file: Blob): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取文件名后缀 */
|
||||
/** Get filename suffix */
|
||||
function getFileExtension(name: string) {
|
||||
const index = name.lastIndexOf('.');
|
||||
return name.slice(index + 1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url 获取图片宽高
|
||||
* @Param url Get image width and height
|
||||
*/
|
||||
function getImageSize(url: string): Promise<{ width: number; height: number }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -468,9 +468,9 @@ function getImageSize(url: string): Promise<{ width: number; height: number }> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param bytes 文件大小
|
||||
* @param decimals 小数位数, 默认 2 位
|
||||
* Format file size
|
||||
* @param bytes file size
|
||||
* @Param decimals, default 2 digits
|
||||
* @example
|
||||
* formatBytes(1024); // 1KB
|
||||
* formatBytes('1024'); // 1KB
|
||||
|
||||
@@ -52,7 +52,7 @@ interface ImageUploaderProps {
|
||||
style?: CSSProperties;
|
||||
readonly?: boolean;
|
||||
disabled?: boolean;
|
||||
/** 图片上传限制 */
|
||||
/** image upload restrictions */
|
||||
rules?: ImageRule;
|
||||
value?: { url: string; uri: string } | undefined;
|
||||
validateStatus?: SelectProps['validateStatus'];
|
||||
@@ -61,13 +61,13 @@ interface ImageUploaderProps {
|
||||
}
|
||||
|
||||
interface ImagePopoverWrapperProps {
|
||||
/** 图片地址 */
|
||||
/** Image address */
|
||||
url?: string;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
minWidth?: number;
|
||||
minHeight?: number;
|
||||
/** 是否支持预览 */
|
||||
/** Whether to support preview */
|
||||
enablePreview?: boolean;
|
||||
children?: React.ReactElement;
|
||||
}
|
||||
@@ -215,7 +215,7 @@ const ImageUploader: FC<ImageUploaderProps> = ({
|
||||
return 'image/*';
|
||||
}, [rules?.suffix]);
|
||||
|
||||
/** 整体区域支持交互 */
|
||||
/** Overall Area Support Interaction */
|
||||
const wrapCanAction = useMemo(
|
||||
() => !uri && !loading && !isError && !disabled && !readonly,
|
||||
[uri, loading, isError, disabled, readonly],
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
@@ -24,36 +24,36 @@ import { Toast } from '@coze-arch/coze-design';
|
||||
import ImageUploader, { ImgUploadErrNo } from './image-uploader';
|
||||
|
||||
interface UseImageUploaderParams {
|
||||
/** 图片限制条件 */
|
||||
/** image restrictions */
|
||||
rules?: ImageUploader['rules'];
|
||||
/** 上传模式 */
|
||||
/** upload mode */
|
||||
mode?: ImageUploader['mode'];
|
||||
/** 上传配置 */
|
||||
/** upload configuration */
|
||||
timeout?: ImageUploader['timeout'];
|
||||
}
|
||||
|
||||
interface UseImageUploaderReturn {
|
||||
/** 图片标识, 用于提交给服务 */
|
||||
/** Image ID for submission to the service */
|
||||
uri: string;
|
||||
/** 图片展示地址 */
|
||||
/** Picture display address */
|
||||
url: string;
|
||||
/** 文件名 */
|
||||
/** file name */
|
||||
fileName: string;
|
||||
/** 上传中状态 */
|
||||
/** Uploading status */
|
||||
loading: boolean;
|
||||
/** 上传失败状态 */
|
||||
/** Upload failed status */
|
||||
isError: boolean;
|
||||
/** 上传图片 */
|
||||
/** Upload image */
|
||||
uploadImg: (file: File) => Promise<ImageUploader['uploadResult']>;
|
||||
/** 清除已上传图片 */
|
||||
/** Clear uploaded images */
|
||||
clearImg: () => void;
|
||||
/** 上传失败后重试 */
|
||||
/** Retry after upload failure */
|
||||
retryUploadImg: () => Promise<ImageUploader['uploadResult']>;
|
||||
/**
|
||||
* 设置初始状态, 用于回显服务下发的数据
|
||||
* Set the initial state for echoing data sent by the service
|
||||
*
|
||||
* @param val 对应值
|
||||
* @param isMerge 是否 merge 模式, merge 模式仅更新传入字段. 默认 false
|
||||
* @param val corresponding value
|
||||
* @Param isMerge Whether to merge mode, merge mode only updates incoming fields. Default false
|
||||
*/
|
||||
setImgValue: (
|
||||
val: { uri?: string; url?: string; fileName?: string },
|
||||
@@ -61,7 +61,7 @@ interface UseImageUploaderReturn {
|
||||
) => void;
|
||||
}
|
||||
|
||||
/** 缓存文件名 */
|
||||
/** cache filename */
|
||||
const fileNameCache: Record<string, string> = Object.create(null);
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
@@ -102,14 +102,14 @@ export default function useImageUploader(
|
||||
setFileName(targetFileName);
|
||||
}
|
||||
|
||||
// 非 Merge 模式, 未设置的值清空
|
||||
// Non-Merge mode, unset values are cleared
|
||||
if (!isMerge) {
|
||||
setUrl(targetDisplayUrl ?? '');
|
||||
setUri(targetUri ?? '');
|
||||
setFileName(targetFileName ?? '');
|
||||
}
|
||||
|
||||
// 文件名特殊逻辑, 根据 uri 从缓存重映射文件名
|
||||
// Filename special logic, remapping filenames from cache based on URIs
|
||||
if (!targetFileName) {
|
||||
if (targetUri && fileNameCache[targetUri]) {
|
||||
setFileName(fileNameCache[targetUri]);
|
||||
@@ -130,12 +130,12 @@ export default function useImageUploader(
|
||||
const uploadImg = useCallback(
|
||||
async (file: File): Promise<ImageUploader['uploadResult'] | undefined> => {
|
||||
await uploaderRef.current.select(file);
|
||||
// 图片校验不通过
|
||||
// The picture verification failed.
|
||||
if (!uploaderRef.current.validateResult?.isSuccess) {
|
||||
Toast.error(
|
||||
uploaderRef.current.validateResult?.msg || '图片不符合要求',
|
||||
);
|
||||
// @ts-expect-error 此处 validateResult.isSuccess 为 false
|
||||
// @ts-expect-error here validateResult.isSuccess is false
|
||||
return uploaderRef.current.validateResult;
|
||||
}
|
||||
|
||||
@@ -146,10 +146,10 @@ export default function useImageUploader(
|
||||
await uploaderRef.current.upload();
|
||||
setLoading(false);
|
||||
|
||||
// 上传结果
|
||||
// Upload result
|
||||
const { uploadResult } = uploaderRef.current;
|
||||
|
||||
// 无上传结果说明上传取消
|
||||
// No upload result indicates that the upload is cancelled.
|
||||
if (!uploadResult) {
|
||||
return;
|
||||
}
|
||||
@@ -159,7 +159,7 @@ export default function useImageUploader(
|
||||
if (uploadResult.isSuccess) {
|
||||
Toast.success(I18n.t('file_upload_success'));
|
||||
setUri(uploadResult.uri);
|
||||
// FIXME: 合理的设计应该用 uri 进行缓存, 但是 Imageflow 初期只存储了 url, 使用 url 作为临时方案
|
||||
// FIXME: A reasonable design should cache with URIs, but Imageflow initially only stored URLs, using URLs as a temporary solution
|
||||
fileNameCache[uploadResult.url] = `${file.name}`;
|
||||
} else {
|
||||
Toast.error(uploadResult.msg);
|
||||
@@ -172,7 +172,7 @@ export default function useImageUploader(
|
||||
const retryUploadImg = useCallback(async (): Promise<
|
||||
ImageUploader['uploadResult']
|
||||
> => {
|
||||
// 重传前置检查, 有文件且校验通过
|
||||
// Resend pre-check, there is a file and the verification is passed
|
||||
if (
|
||||
!uploaderRef.current?.file ||
|
||||
!uploaderRef.current?.validateResult?.isSuccess
|
||||
@@ -189,7 +189,7 @@ export default function useImageUploader(
|
||||
await uploaderRef.current.upload();
|
||||
setLoading(false);
|
||||
|
||||
// 上传结果
|
||||
// Upload result
|
||||
const uploadResult = uploaderRef.current.uploadResult || {
|
||||
isSuccess: false,
|
||||
errNo: ImgUploadErrNo.UploadFail,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** Text 组件,超出自动 ... 并且展示 tooltip */
|
||||
/** Text component, beyond automatic... and show tooltip */
|
||||
import { type FC } from 'react';
|
||||
|
||||
import { Typography } from '@coze-arch/coze-design';
|
||||
|
||||
@@ -26,35 +26,35 @@ interface Auth {
|
||||
export type WorkflowInfo = Workflow & Auth;
|
||||
|
||||
/**
|
||||
* 打开弹窗场景, 主要用于 log
|
||||
* Open the pop-up scene, mainly for logging
|
||||
*
|
||||
* WorkflowAddNode 场景有特殊处理
|
||||
* WorkflowAddNode scenes have special handling
|
||||
*/
|
||||
export enum WorkflowModalFrom {
|
||||
/** 流程详情添加子流程 */
|
||||
/** Process details Add subprocess */
|
||||
WorkflowAddNode = 'workflow_addNode',
|
||||
/** 在 bot skills 打开 */
|
||||
/** Open in bot skills */
|
||||
BotSkills = 'bot_skills',
|
||||
/** 在抖音分身场景的 ide 打开 */
|
||||
/** Open ide in Douyin doppelganger scene */
|
||||
BotSkillsDouyin = 'bot_skills_douyin_ide',
|
||||
/** 在 bot 多 agent skills 打开 */
|
||||
/** Open in bot multi-agent skills */
|
||||
BotMultiSkills = 'bot_multi_skills',
|
||||
/** 在 bot triggers 打开 */
|
||||
/** Open in bot triggers */
|
||||
BotTrigger = 'bot_trigger',
|
||||
/** bot 快捷方式打开 */
|
||||
/** Bot shortcut open */
|
||||
BotShortcut = 'bot_shortcut',
|
||||
/** 空间下流程列表 */
|
||||
/** process list under space */
|
||||
SpaceWorkflowList = 'space_workflow_list',
|
||||
/** 来自 workflow as agent */
|
||||
/** From workflow as agent */
|
||||
WorkflowAgent = 'workflow_agent',
|
||||
/** 社会场景 workflow 列表 */
|
||||
/** Social scene workflow list */
|
||||
SocialSceneHost = 'social_scene_host',
|
||||
/** 项目引入资源库文件 */
|
||||
/** project import repository file */
|
||||
ProjectImportLibrary = 'project_import_library',
|
||||
/** 项目内 workflow 画布添加子流程 */
|
||||
/** Add subflow to workflow canvas in project */
|
||||
ProjectWorkflowAddNode = 'project_workflow_addNode',
|
||||
/**
|
||||
* 项目内 workflow 资源列表添加 workflow 资源
|
||||
* List of workflow resources in the project Add workflow resources
|
||||
*/
|
||||
ProjectAddWorkflowResource = 'project_add_workflow_resource',
|
||||
}
|
||||
|
||||
@@ -18,37 +18,37 @@ import cronstrue from 'cronstrue/i18n';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
const langMap = {
|
||||
// 简体中文
|
||||
// Simplified Chinese
|
||||
'zh-CN': 'zh_CN',
|
||||
zh: 'zh-CN',
|
||||
// 繁体中文
|
||||
// Traditional Chinese
|
||||
zh_TW: 'zh_TW',
|
||||
// 英语
|
||||
// English
|
||||
'en-US': 'en',
|
||||
en: 'en',
|
||||
// 日语
|
||||
// Japanese
|
||||
'ja-JP': 'ja',
|
||||
ja: 'ja',
|
||||
// 韩语
|
||||
// Korean
|
||||
'ko-KR': 'ko',
|
||||
ko: 'ko',
|
||||
// 法语
|
||||
// French
|
||||
'fr-FR': 'fr',
|
||||
fr: 'fr',
|
||||
// 德语
|
||||
// German
|
||||
'de-DE': 'de',
|
||||
de: 'de',
|
||||
// 意大利语
|
||||
// Italian
|
||||
'it-IT': 'it',
|
||||
it: 'it',
|
||||
// 西班牙语
|
||||
// Spanish
|
||||
'es-ES': 'es',
|
||||
es: 'es',
|
||||
};
|
||||
|
||||
// 校验使用 cronjob 翻译结果
|
||||
// Verify translation results using cronjob
|
||||
export const isCronJobVerify = cronJob => {
|
||||
// 仅支持 6 位 cronjob(后端限制)
|
||||
// Only 6-bit cronjobs are supported (backend limit).
|
||||
const parts = cronJob?.split(' ');
|
||||
if (parts?.length !== 6) {
|
||||
return false;
|
||||
@@ -59,9 +59,9 @@ export const isCronJobVerify = cronJob => {
|
||||
throwExceptionOnParseError: true,
|
||||
});
|
||||
|
||||
// 额外校验一下字符串是否包含 null undefined
|
||||
// 1 2 3 31 1- 1 在上午 03:02:01, 限每月 31 号, 或者为星期一, 一月至undefined
|
||||
// 1 2 3 31 1 1#6 在上午 03:02:01, 限每月 31 号, 限每月的null 星期一, 仅于一月份
|
||||
// Extra check if the string contains null undefined
|
||||
// 1 2 3 31 1- 1 at 03:02:01 am, limited to the 31st of each month, or for Monday, January to undefined
|
||||
// 1 2 3 31 1 1 #6 at 03:02:01 am, limited to the 31st of each month, limited to null Mondays of each month, only in January
|
||||
if (rs.includes('null') || rs.includes('undefined')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import dayjs from 'dayjs';
|
||||
|
||||
let getIsIPadCache: boolean | undefined;
|
||||
/**
|
||||
* gpt-4 提供的代码
|
||||
* Code provided by gpt-4
|
||||
*/
|
||||
export const getIsIPad = () => {
|
||||
if (typeof getIsIPadCache === 'undefined') {
|
||||
@@ -34,7 +34,7 @@ export const getIsIPad = () => {
|
||||
return getIsIPadCache;
|
||||
};
|
||||
|
||||
/* 时间戳转文本,并省略年份或日期*/
|
||||
/* Timestamp converts text and omits the year or date*/
|
||||
export const formatOmittedDateTime = time => {
|
||||
if (!time) {
|
||||
return '';
|
||||
@@ -46,20 +46,20 @@ export const formatOmittedDateTime = time => {
|
||||
let formatStr: string;
|
||||
|
||||
if (!today.isSame(day, 'year')) {
|
||||
// 不是当年,展示年份
|
||||
// Not the year, show the year
|
||||
formatStr = 'YYYY-MM-DD HH:mm';
|
||||
} else if (!today.isSame(day, 'day')) {
|
||||
// 不是当天, 展示日期
|
||||
// Not the day, the display date.
|
||||
formatStr = 'MM-DD HH:mm';
|
||||
} else {
|
||||
// 当天只展示时间
|
||||
// Show time only on the day
|
||||
formatStr = 'HH:mm';
|
||||
}
|
||||
|
||||
return day.format(formatStr);
|
||||
};
|
||||
|
||||
/** 等待 */
|
||||
/** wait */
|
||||
export const wait = (ms: number) =>
|
||||
new Promise(r => {
|
||||
setTimeout(r, ms);
|
||||
@@ -68,7 +68,7 @@ export const wait = (ms: number) =>
|
||||
import { reporter as infraReporter } from '@coze-arch/logger';
|
||||
|
||||
/**
|
||||
* 流程使用的 slardar 上报实例
|
||||
* The slardar reporting instance used by the process
|
||||
*/
|
||||
export const reporter = infraReporter.createReporterWithPreset({
|
||||
namespace: 'workflow',
|
||||
|
||||
@@ -37,7 +37,7 @@ import { type WorkflowCommitListProps } from './type';
|
||||
export interface CommitItemProps {
|
||||
className?: string;
|
||||
data: VersionMetaInfo;
|
||||
/** 是否选中 */
|
||||
/** Is it selected? */
|
||||
isActive?: boolean;
|
||||
readonly?: WorkflowCommitListProps['readonly'];
|
||||
enablePublishPPE?: WorkflowCommitListProps['enablePublishPPE'];
|
||||
@@ -45,9 +45,9 @@ export interface CommitItemProps {
|
||||
onPublishPPE?: WorkflowCommitListProps['onPublishPPE'];
|
||||
onResetToCommit?: WorkflowCommitListProps['onResetToCommit'];
|
||||
onShowCommit?: WorkflowCommitListProps['onShowCommit'];
|
||||
/** 隐藏操作下拉菜单 */
|
||||
/** Hide action drop-down menu */
|
||||
hiddenActionMenu?: boolean;
|
||||
/** 隐藏 commitId */
|
||||
/** Hide commitId */
|
||||
hideCommitId?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,11 +76,11 @@ const WorkflowCommitListComp: FC<WorkflowCommitListProps> = withQueryClient(
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
/** scroll的container */
|
||||
/** Scroll container */
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
/** 监听触底的observer */
|
||||
/** Monitor the bottom observer */
|
||||
const intersectionObserverDom = useRef<HTMLDivElement>(null);
|
||||
// 是否触底
|
||||
// Is it bottoming out?
|
||||
const [inViewPort] = useInViewport(intersectionObserverDom, {
|
||||
root: () => scrollContainerRef.current,
|
||||
threshold: 0.8,
|
||||
@@ -90,9 +90,9 @@ const WorkflowCommitListComp: FC<WorkflowCommitListProps> = withQueryClient(
|
||||
updatePageParam({ type });
|
||||
}, [type, updatePageParam]);
|
||||
|
||||
// 首次effect不执行,这个是切换状态的effect
|
||||
// The first effect is not executed, this is the effect of switching the state
|
||||
useUpdateEffect(() => {
|
||||
// 当筛选项改变时,回到顶部
|
||||
// When the filter item changes, return to the top
|
||||
if (scrollContainerRef.current) {
|
||||
scrollContainerRef.current.scrollTo({
|
||||
top: 0,
|
||||
@@ -100,7 +100,7 @@ const WorkflowCommitListComp: FC<WorkflowCommitListProps> = withQueryClient(
|
||||
}
|
||||
}, [queryParams]);
|
||||
|
||||
// 获取下一页逻辑
|
||||
// Get next page logic
|
||||
useEffect(() => {
|
||||
if (!inViewPort) {
|
||||
return;
|
||||
@@ -142,12 +142,12 @@ const WorkflowCommitListComp: FC<WorkflowCommitListProps> = withQueryClient(
|
||||
}
|
||||
|
||||
const timelineType = (data: VersionMetaInfo, index) => {
|
||||
// PPE 历史, 在线激活
|
||||
// PPE history, online activation
|
||||
if (type === OperateType.PubPPEOperate) {
|
||||
return !data.offline ? 'ongoing' : 'default';
|
||||
}
|
||||
|
||||
// 提交历史和发布历史, 最新的激活
|
||||
// Submission history and release history, latest activations
|
||||
return index === 0 ? 'ongoing' : 'default';
|
||||
};
|
||||
|
||||
|
||||
@@ -19,33 +19,33 @@ import {
|
||||
type OperateType,
|
||||
} from '@coze-workflow/base/api';
|
||||
|
||||
/** 流程提交历史列表组件 */
|
||||
/** Process Submission History List Component */
|
||||
export interface WorkflowCommitListProps {
|
||||
className?: string;
|
||||
spaceId: string;
|
||||
workflowId: string;
|
||||
/** 操作类型 */
|
||||
/** operation type */
|
||||
type: OperateType;
|
||||
/** 只读模式, 只读历史卡片不可点, 不影响 action */
|
||||
/** Read-only mode, read-only history cards cannot be clicked, does not affect action */
|
||||
readonly?: boolean;
|
||||
/** 每页拉取数量, 默认 10 */
|
||||
/** Number of pulls per page, default 10 */
|
||||
limit?: number;
|
||||
/** 当前选中项 */
|
||||
/** current selection */
|
||||
value?: string;
|
||||
/** 是否展示当前节点 */
|
||||
/** Whether to display the current node */
|
||||
showCurrent?: boolean;
|
||||
/** 是否支持发布到 PPE 功能 */
|
||||
/** Whether to support publishing to PPE function */
|
||||
enablePublishPPE?: boolean;
|
||||
/** 隐藏 commitId (commitId可读性较差,非专业用户不需要感知) */
|
||||
/** Hide the commitId (the commitId is less readable, and non-professional users do not need to perceive it) */
|
||||
hideCommitId?: boolean;
|
||||
/** 卡片点击 */
|
||||
/** Card click */
|
||||
onItemClick?: (item: VersionMetaInfo) => void;
|
||||
/** 恢复到某版本点击 */
|
||||
/** Restore to a certain version Click */
|
||||
onResetToCommit?: (item: VersionMetaInfo) => void;
|
||||
/** 查看某版本点击 */
|
||||
/** To view a version click */
|
||||
onShowCommit?: (item: VersionMetaInfo) => void;
|
||||
/** 发布到多环境点击 */
|
||||
/** Publish to Multi-environment Click */
|
||||
onPublishPPE?: (item: VersionMetaInfo) => void;
|
||||
/** 点击[现在] */
|
||||
/** Click [Now] */
|
||||
onCurrentClick?: (currentKey: string) => void;
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ interface VersionHistoryParams {
|
||||
spaceId: string;
|
||||
workflowId: string;
|
||||
type: OperateType;
|
||||
/** 每页请求数量, 默认 10 */
|
||||
/** Number of requests per page, default 10 */
|
||||
pageSize?: number;
|
||||
/** 是否启动请求, 默认 false */
|
||||
/** Whether to start the request, default false */
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import { CustomError } from '@coze-arch/bot-error';
|
||||
import { FileBizType, IconType } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import s from './index.module.less';
|
||||
/** 输入合规校验异常的错误码 */
|
||||
/** Enter error code for compliance exception */
|
||||
const sensitiveWordsErrorCode = ['702095075', '702095081'];
|
||||
|
||||
const { Checkbox } = Form;
|
||||
@@ -62,13 +62,13 @@ export interface RuleItem {
|
||||
}
|
||||
|
||||
interface EditWorkFlowPropsInner {
|
||||
/** 流程类型 */
|
||||
/** process type */
|
||||
flowMode?: WorkflowMode;
|
||||
mode: 'update' | 'add';
|
||||
visible: boolean;
|
||||
// 默认confirm的disabled
|
||||
// Default confirmed disabled
|
||||
|
||||
/** 自定义弹窗标题 */
|
||||
/** Custom pop-up title */
|
||||
customTitleRender?: (title: React.ReactNode) => React.ReactNode;
|
||||
|
||||
initConfirmDisabled?: boolean;
|
||||
@@ -78,17 +78,17 @@ interface EditWorkFlowPropsInner {
|
||||
flowMode?: EditWorkFlowPropsInner['flowMode'];
|
||||
}) => void;
|
||||
onCancel?: () => void;
|
||||
/** @deprecated 未使用 */
|
||||
/** @deprecated unused */
|
||||
spaceID?: string;
|
||||
getLatestWorkflowJson?: () => Promise<WorkflowJSON>;
|
||||
bindBizId?: string;
|
||||
bindBizType?: BindBizType;
|
||||
/** 当前项目 id,只在项目内的 workflow 有该字段 */
|
||||
/** The current project id, only the workflow within the project has this field */
|
||||
projectId?: string;
|
||||
nameValidators?: RuleItem[];
|
||||
}
|
||||
|
||||
/** 表单值 */
|
||||
/** form value */
|
||||
interface FormValue {
|
||||
icon_uri: UploadValue;
|
||||
name: string;
|
||||
@@ -97,7 +97,7 @@ interface FormValue {
|
||||
create_conversation?: boolean;
|
||||
}
|
||||
|
||||
/** 获取弹窗标题 */
|
||||
/** Get pop-up title */
|
||||
function getModalTitle(
|
||||
mode: EditWorkFlowPropsInner['mode'],
|
||||
flowMode: EditWorkFlowPropsInner['flowMode'],
|
||||
@@ -204,7 +204,7 @@ export function CreateWorkflowModal({
|
||||
name: values?.name,
|
||||
desc: values?.target ? values.target : '',
|
||||
space_id: workFlow.space_id || '',
|
||||
// 更新头像等信息不需要重新test run
|
||||
// There is no need to re-test run to update the avatar and other information.
|
||||
ignore_status_transfer: true,
|
||||
schema_type: values?.schema_type || workFlow?.schema_type,
|
||||
};
|
||||
@@ -437,7 +437,7 @@ export function CreateWorkflowModal({
|
||||
data-testid="workflow.list.create.name.input"
|
||||
/>
|
||||
|
||||
{/* 只有项目内创建 Chatflow 时才可以绑定会话 */}
|
||||
{/* Sessions can only be bound when Chatflow is created within the project */}
|
||||
{mode === 'add' && projectId && flowMode === WorkflowMode.ChatFlow ? (
|
||||
<Checkbox
|
||||
fieldClassName={s['conversation-field']}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { WORKFLOW_LIST_STATUS_ALL } from '@/workflow-modal/type';
|
||||
|
||||
/** 流程所有者选项, 全部/我的 */
|
||||
/** Process Owner Options, All/Mine */
|
||||
export const scopeOptions = [
|
||||
{
|
||||
label: I18n.t('workflow_list_scope_all'),
|
||||
@@ -31,7 +31,7 @@ export const scopeOptions = [
|
||||
},
|
||||
];
|
||||
|
||||
/** 流程状态选项, 全部/已发布/未发布 */
|
||||
/** Process Status Options, All/Published/Unpublished */
|
||||
export const statusOptions = [
|
||||
{
|
||||
label: I18n.t('workflow_list_status_all'),
|
||||
@@ -47,7 +47,7 @@ export const statusOptions = [
|
||||
},
|
||||
];
|
||||
|
||||
/** 流程排序选项, 创建时间/更新时间 */
|
||||
/** Process sorting options, creation time/update time */
|
||||
export const sortOptions = [
|
||||
{
|
||||
label: I18n.t('workflow_list_sort_create_time'),
|
||||
|
||||
@@ -98,7 +98,7 @@ export const WorkflowBotButton: FC<WorkflowBotButtonProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已添加,展示已添加按钮
|
||||
// If added, display the Added button
|
||||
if (isAdded) {
|
||||
return (
|
||||
<Popconfirm
|
||||
@@ -117,8 +117,8 @@ export const WorkflowBotButton: FC<WorkflowBotButtonProps> = ({
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
// 未添加,判断发布状态
|
||||
// 未发布,展示下面的按钮
|
||||
// Not added, judge the release status
|
||||
// Unpublished, show the button below
|
||||
if (!canAdd) {
|
||||
let key: I18nKeysNoOptionsType = 'workflow_add_not_allow_before_publish';
|
||||
if (isFromWorkflow) {
|
||||
@@ -143,7 +143,7 @@ export const WorkflowBotButton: FC<WorkflowBotButtonProps> = ({
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
// 已发布并且未添加,展示添加按钮
|
||||
// Published and not added, show the add button
|
||||
if (!isAdded) {
|
||||
return (
|
||||
<LoadingButton
|
||||
|
||||
@@ -36,7 +36,7 @@ export const DeleteButton = ({
|
||||
};
|
||||
|
||||
const handleDelete = () =>
|
||||
// 使用 promise 让按钮出现 loading 的效果,参见
|
||||
// Use promises to make the button appear loading, see
|
||||
// https://semi.design/zh-CN/feedback/popconfirm
|
||||
new Promise((resolve, reject) => {
|
||||
onDelete?.()
|
||||
@@ -45,7 +45,7 @@ export const DeleteButton = ({
|
||||
resolve(true);
|
||||
})
|
||||
.catch(error => {
|
||||
// 处理错误
|
||||
// Handle errors
|
||||
logger.error({
|
||||
error: error as Error,
|
||||
eventName: 'delete workflow error',
|
||||
|
||||
@@ -120,7 +120,7 @@ export const WorkflowCard: FC<WorkflowCardProps> = props => {
|
||||
: undefined;
|
||||
|
||||
const renderStatusValue = () => {
|
||||
// 添加项目里的工作流节点、官方示例不展示发布状态
|
||||
// Add workflow nodes in the project, official examples do not show release status
|
||||
if (
|
||||
isSelectProjectCategory(context?.modalState) ||
|
||||
workflowCategory === WorkflowCategory.Example
|
||||
@@ -138,7 +138,7 @@ export const WorkflowCard: FC<WorkflowCardProps> = props => {
|
||||
return null;
|
||||
};
|
||||
const renderBottomLeftDesc = () => {
|
||||
// 商品底部
|
||||
// bottom of the product
|
||||
if (!isTypeWorkflow(data)) {
|
||||
const timeRender = `${I18n.t('workflow_add_list_updated')} ${formatTime(
|
||||
data.meta_info.listed_at,
|
||||
@@ -175,7 +175,7 @@ export const WorkflowCard: FC<WorkflowCardProps> = props => {
|
||||
);
|
||||
}
|
||||
|
||||
// 用户创建的,展示修改时间
|
||||
// User-created, showing modification time
|
||||
if (isSpaceWorkflow || workflowCategory === WorkflowCategory.Example) {
|
||||
const showCreator =
|
||||
(creator !== MineActiveEnum.Mine && isTeam) ||
|
||||
@@ -214,7 +214,7 @@ export const WorkflowCard: FC<WorkflowCardProps> = props => {
|
||||
);
|
||||
}
|
||||
|
||||
// 官方模板,展示创作者
|
||||
// Official template, showcasing creators
|
||||
if (!isSpaceWorkflow) {
|
||||
return (
|
||||
<div className={styles.creator}>
|
||||
|
||||
@@ -148,7 +148,7 @@ export const WorkflowParameters: FC<WorkflowParametersProps> = ({
|
||||
return context?.modalState.dataSourceType === DataSourceType.Workflow;
|
||||
}
|
||||
const getParameters = (): Array<WorkflowParameterItem> => {
|
||||
// 这么拆分虽然有点冗余, 但是可以正确进行类型推导
|
||||
// Although this split is a bit redundant, it allows for correct type derivation
|
||||
if (isTypeWorkflow(data)) {
|
||||
return (
|
||||
data.start_node?.node_param?.input_parameters?.map(item => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user