chore: replace all cn comments of fe to en version by volc api (#320)
This commit is contained in:
@@ -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 => {
|
||||
|
||||
@@ -97,7 +97,7 @@ const WorkflowModalContent: FC<WorkFlowModalModeProps> = props => {
|
||||
pageSize: 10,
|
||||
enabled: context?.modalState.dataSourceType === DataSourceType.Product,
|
||||
});
|
||||
// 转换筛选参数
|
||||
// conversion filter parameters
|
||||
useEffect(() => {
|
||||
if (!context) {
|
||||
return;
|
||||
@@ -127,7 +127,7 @@ const WorkflowModalContent: FC<WorkFlowModalModeProps> = props => {
|
||||
let status: WorkflowModalState['status'] | undefined = undefined;
|
||||
if (modalState.isSpaceWorkflow) {
|
||||
status =
|
||||
// isAddProjectWorkflow:项目里添加子工作流,没有发布状态概念,筛选状态传 undefined
|
||||
// isAddProjectWorkflow: Add sub-workflow to the project, no release state concept, filter state pass undefined
|
||||
modalState.status === WORKFLOW_LIST_STATUS_ALL || isAddProjectWorkflow
|
||||
? undefined
|
||||
: modalState.status;
|
||||
@@ -188,7 +188,7 @@ const WorkflowModalContent: FC<WorkFlowModalModeProps> = props => {
|
||||
})),
|
||||
);
|
||||
|
||||
// 子流程节点 map,例如 { 'workflowId': [node1, node2, ...] }
|
||||
// Subprocess node map, e.g. {'workflowId': [node1, node2,...]}
|
||||
const workflowNodesMap = useMemo(() => {
|
||||
const subFlowNodes = nodes.filter(
|
||||
v => v.type === StandardNodeType.SubWorkflow,
|
||||
@@ -209,28 +209,28 @@ const WorkflowModalContent: FC<WorkFlowModalModeProps> = props => {
|
||||
);
|
||||
}, [excludedWorkflowIds, workflowList]);
|
||||
|
||||
/** 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,
|
||||
});
|
||||
|
||||
// 首次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,
|
||||
});
|
||||
}
|
||||
// 只要是query中非page改变,就执行此effect
|
||||
// Perform this effect whenever a non-page change is made in the query
|
||||
}, [context?.modalState]);
|
||||
|
||||
// 获取下一页逻辑
|
||||
// Get next page logic
|
||||
useEffect(() => {
|
||||
if (!inViewPort) {
|
||||
return;
|
||||
@@ -339,7 +339,7 @@ const WorkflowModalContent: FC<WorkFlowModalModeProps> = props => {
|
||||
wrapperClassName={s.spin}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
>
|
||||
{/* Workflow as agent 支持添加带自定义入参的对话流 */}
|
||||
{/* Workflow as agent support for adding dialog flows with custom imported parameters */}
|
||||
{/* {isAgentWorkflow ? (
|
||||
<div className="coz-mg-hglt px-[36px] py-[8px] mx-[24px] my-[0] rounded-[8px]">
|
||||
{I18n.t('wf_chatflow_133')}
|
||||
@@ -349,7 +349,7 @@ const WorkflowModalContent: FC<WorkFlowModalModeProps> = props => {
|
||||
className={`${s['workflow-content']} new-workflow-modal-content`}
|
||||
ref={scrollContainerRef}
|
||||
>
|
||||
{/* 内容渲染 */}
|
||||
{/* content rendering */}
|
||||
{targetLoadingStatus !== 'pending' && targetList.length > 0 && (
|
||||
<UICompositionModalMain.Content
|
||||
style={{
|
||||
@@ -357,7 +357,7 @@ const WorkflowModalContent: FC<WorkFlowModalModeProps> = props => {
|
||||
paddingBottom: isAgentWorkflow ? '60px' : 0,
|
||||
}}
|
||||
>
|
||||
{/* 数据呈现样式, 列表样式/卡片样式. 展示图像流商品列表时使用卡片样式 */}
|
||||
{/* Data rendering style, list style/card style. Use card style when displaying image stream item lists */}
|
||||
<>
|
||||
{targetList.map((item: WorkflowInfo | ProductInfo) => (
|
||||
<WorkflowCard
|
||||
|
||||
@@ -56,7 +56,7 @@ const flowModeOptions = [
|
||||
value: WorkflowMode.ChatFlow,
|
||||
},
|
||||
].filter(item => {
|
||||
// 开源版本暂不支持对话流
|
||||
// The open-source version does not currently support conversation streaming
|
||||
if (item.value === WorkflowMode.ChatFlow && IS_OPEN_SOURCE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -39,17 +39,17 @@ export enum ModalI18nKey {
|
||||
}
|
||||
|
||||
/**
|
||||
* i18n 文案有变量时使用这个结构
|
||||
* Use this structure when the i18n text has variables
|
||||
*/
|
||||
interface I18nKeyWithOptions {
|
||||
/* i18n 文案的 key */
|
||||
/* I18n copy key */
|
||||
key: string;
|
||||
/* 变量参数对象 */
|
||||
/* variable parameter object */
|
||||
options?: Record<string, ReactNode>;
|
||||
}
|
||||
export type I18nKey = string | I18nKeyWithOptions;
|
||||
|
||||
// 用于存放 workflow 和 imageflow 的各个 i18n 文案的 key
|
||||
// The key to each i18n copy used to store workflow and imageflow
|
||||
export const WORKFLOW_MODAL_I18N_KEY_MAP: {
|
||||
[WorkflowMode.Workflow]: Record<ModalI18nKey, I18nKey>;
|
||||
[WorkflowMode.Imageflow]: Record<ModalI18nKey, I18nKey>;
|
||||
@@ -102,7 +102,7 @@ export const WORKFLOW_MODAL_I18N_KEY_MAP: {
|
||||
[ModalI18nKey.TabMine]: 'workflow_add_created_tab_mine',
|
||||
[ModalI18nKey.ListEmptyTitle]: 'scene_workflow_popup_search_empty',
|
||||
[ModalI18nKey.CreatedListEmptyTitle]: 'scene_workflow_popup_list_empty',
|
||||
// 场景工作流没有描述
|
||||
// Scenario workflow not described
|
||||
[ModalI18nKey.CreatedListEmptyDescription]: '',
|
||||
[ModalI18nKey.NavigationCreate]: 'workflow_add_navigation_create',
|
||||
[ModalI18nKey.ListError]: 'workflow_add_list_added_id_empty',
|
||||
@@ -122,7 +122,7 @@ export const WORKFLOW_MODAL_I18N_KEY_MAP: {
|
||||
};
|
||||
|
||||
/**
|
||||
* 自动根据 flowMode 返回对应国际化文案
|
||||
* Automatically returns the corresponding internationalization copy according to flowMode
|
||||
*/
|
||||
export function useI18nText() {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
|
||||
@@ -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 { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useContext, useEffect } from 'react';
|
||||
@@ -51,9 +51,9 @@ import {
|
||||
import { reporter, wait } from '../../utils';
|
||||
|
||||
/**
|
||||
* 特殊错误码
|
||||
* - 788664021: 由于模型原因,暂不支持复制商店中的工作流
|
||||
* - 788664024: 模板未购买,请前往模板详情页购买后再复制
|
||||
* special error code
|
||||
* - 788664021: Due to model reasons, replicating workflows in the store is not currently supported
|
||||
* - 788664024: The template has not been purchased, please go to the template details page to buy and then copy it.
|
||||
*/
|
||||
const copyProductErrorCodes = ['788664021', '788664024'];
|
||||
|
||||
@@ -70,7 +70,7 @@ export interface WorkflowCardProps extends WorkFlowModalModeProps {
|
||||
pluginId: string;
|
||||
}>;
|
||||
/**
|
||||
* workflow 删除按钮点击时触发的 handler
|
||||
* Workflow delete the handler that is triggered when the button is clicked
|
||||
* @param row
|
||||
*/
|
||||
handleDeleteWorkflow?: (row: WorkflowInfo) => Promise<{
|
||||
@@ -81,25 +81,25 @@ export interface WorkflowCardProps extends WorkFlowModalModeProps {
|
||||
| undefined;
|
||||
}>;
|
||||
/**
|
||||
* 是否为专业版特供
|
||||
* Is it a special offer for the professional version?
|
||||
*/
|
||||
isProfessionalTemplate?: boolean;
|
||||
}
|
||||
|
||||
interface UseWorkflowActionReturn {
|
||||
/** 复制官方流程模板 */
|
||||
/** Copy the official process template */
|
||||
dupWorkflowTpl: () => Promise<void>;
|
||||
/** 复制流程商品 */
|
||||
/** Copy process product */
|
||||
dupProduct: () => Promise<void>;
|
||||
/** 添加流程 */
|
||||
/** Add process */
|
||||
addWorkflow: () => Promise<boolean>;
|
||||
/** 移除流程 */
|
||||
/** removal process */
|
||||
removeWorkflow: () => void;
|
||||
/**
|
||||
* 删除流程
|
||||
* delete process
|
||||
*/
|
||||
deleteWorkflow: () => Promise<void>;
|
||||
/** 流程项点击 */
|
||||
/** Process item click */
|
||||
itemClick: () => void;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ export function useWorkflowAction({
|
||||
},
|
||||
);
|
||||
|
||||
// 先获取工作流的信息
|
||||
// Get the workflow information first
|
||||
const workflowInfos = resp.data ?? [];
|
||||
if (!workflowInfos?.length) {
|
||||
Toast.error(I18n.t('workflow_add_list_added_id_empty'));
|
||||
@@ -168,7 +168,7 @@ export function useWorkflowAction({
|
||||
return workflowInfos.at(0) as BotPluginWorkFlowItem;
|
||||
}
|
||||
/**
|
||||
* 通过插件 ID 构造新 workflowItem
|
||||
* Construct a new workflowItem by plugin ID
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
async function getWorkflowItemByPluginId(config: {
|
||||
@@ -199,7 +199,7 @@ export function useWorkflowAction({
|
||||
},
|
||||
);
|
||||
|
||||
// 先获取工作流的信息
|
||||
// Get the workflow information first
|
||||
const pluginInfos = resp.data?.plugin_list ?? [];
|
||||
if (!pluginInfos?.length) {
|
||||
Toast.error(
|
||||
@@ -236,7 +236,7 @@ export function useWorkflowAction({
|
||||
return context?.modalState.dataSourceType === DataSourceType.Workflow;
|
||||
}
|
||||
|
||||
/** 打开流程详情页 */
|
||||
/** Open the process details page */
|
||||
function openWorkflowDetailPage(workflow: WorkflowInfo | ProductInfo) {
|
||||
const flowData = workflow as Workflow;
|
||||
const wId = (flowData as WorkflowInfo).workflow_id ?? '';
|
||||
@@ -258,7 +258,7 @@ export function useWorkflowAction({
|
||||
}
|
||||
|
||||
if (isProfessionalTemplate && !isCozePro) {
|
||||
// 跳转到专业版登录
|
||||
// Jump to professional version login
|
||||
navigate(
|
||||
`/sign/oauth?redirect=${encodeURIComponent(
|
||||
'/store/bot',
|
||||
@@ -288,7 +288,7 @@ export function useWorkflowAction({
|
||||
return;
|
||||
}
|
||||
|
||||
// 延迟刷新列表, 兜底服务端主从延迟导致问题
|
||||
// Delayed refresh list, server level leader/follower delay caused problems
|
||||
await wait(100);
|
||||
|
||||
try {
|
||||
@@ -301,14 +301,14 @@ export function useWorkflowAction({
|
||||
ProductEntityType.ImageflowTemplateV2,
|
||||
});
|
||||
|
||||
// 构造新的绑定的工作流列表
|
||||
// Construct a new bound workflow list
|
||||
onWorkFlowListChange?.([...(workFlowList ?? []), newWorkflow]);
|
||||
onAdd?.(newWorkflow, { isDup: true, spaceId: context.spaceId });
|
||||
|
||||
if (onDupSuccess) {
|
||||
onDupSuccess(newWorkflow);
|
||||
} else {
|
||||
// 复制商品成功
|
||||
// Copy product successfully
|
||||
Toast.success({
|
||||
content: (
|
||||
<Space spacing={6}>
|
||||
@@ -383,7 +383,7 @@ export function useWorkflowAction({
|
||||
return;
|
||||
}
|
||||
|
||||
// 延迟刷新列表, 兜底服务端主从延迟导致问题
|
||||
// Delayed refresh list, server level leader/follower delay caused problems
|
||||
await wait(100);
|
||||
|
||||
try {
|
||||
@@ -400,7 +400,7 @@ export function useWorkflowAction({
|
||||
newWorkflow.flow_mode = sourceFlowMode;
|
||||
}
|
||||
|
||||
// 构造新的绑定的工作流列表
|
||||
// Construct a new bound workflow list
|
||||
onWorkFlowListChange?.([...(workFlowList ?? []), newWorkflow]);
|
||||
onAdd?.(newWorkflow, { isDup: true, spaceId: context.spaceId });
|
||||
|
||||
@@ -475,14 +475,14 @@ export function useWorkflowAction({
|
||||
newWorkflow.flow_mode = data?.flow_mode;
|
||||
}
|
||||
|
||||
// 构造新的绑定的工作流列表
|
||||
// Construct a new bound workflow list
|
||||
onWorkFlowListChange?.([...(workFlowList ?? []), newWorkflow]);
|
||||
const addResult = await onAdd?.(newWorkflow, {
|
||||
isDup: false,
|
||||
spaceId: context.spaceId,
|
||||
});
|
||||
/**
|
||||
* 允许外部业务逻辑添加失败
|
||||
* Allow external business logic to add failure
|
||||
*/
|
||||
if (isBoolean(addResult)) {
|
||||
return addResult as unknown as boolean;
|
||||
@@ -536,7 +536,7 @@ export function useWorkflowAction({
|
||||
reporter.info({ message: 'workflow_modal: itemClick' });
|
||||
|
||||
if (onItemClick) {
|
||||
// @ts-expect-error 符合预期
|
||||
// @ts-expect-error meets expectations
|
||||
const item: WorkflowItemType = {
|
||||
item: data,
|
||||
type: context.modalState.dataSourceType,
|
||||
|
||||
@@ -58,8 +58,8 @@ import { WorkflowModalContent } from '../content';
|
||||
import { reporter } from '../../utils';
|
||||
import { ModalI18nKey, WORKFLOW_MODAL_I18N_KEY_MAP } from './use-i18n-text';
|
||||
/**
|
||||
* 返回流程弹窗的各部分组件, 内容,侧边,筛选组件, 拆分组件可用于不同布局
|
||||
* 本用于流程选择
|
||||
* Return to the parts of the process pop-up window Components, content, sides, filter components, split components can be used for different layouts
|
||||
* This is for process selection
|
||||
*/
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function
|
||||
export const useWorkflowModalParts = (props: WorkFlowModalModeProps) => {
|
||||
@@ -102,7 +102,7 @@ export const useWorkflowModalParts = (props: WorkFlowModalModeProps) => {
|
||||
[modalState],
|
||||
);
|
||||
|
||||
// 排序规则(流程数据源)
|
||||
// Sorting rules (process data sources)
|
||||
const [orderBy, setOrderBy] = useState(OrderBy.UpdateTime);
|
||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||
|
||||
@@ -162,7 +162,7 @@ export const useWorkflowModalParts = (props: WorkFlowModalModeProps) => {
|
||||
const isBindDouyin = bindBizType === BindBizType.DouYinBot;
|
||||
const hideSidebar = hideSider || isBindDouyin;
|
||||
|
||||
/** 侧边栏组件 */
|
||||
/** Sidebar Component */
|
||||
const sider = hideSidebar ? null : (
|
||||
<QueryClientProvider client={workflowQueryClient}>
|
||||
<WorkflowModalContext.Provider value={contextValue}>
|
||||
@@ -171,7 +171,7 @@ export const useWorkflowModalParts = (props: WorkFlowModalModeProps) => {
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
/** 流程列表组件 */
|
||||
/** process list component */
|
||||
const content = (
|
||||
<QueryClientProvider client={workflowQueryClient}>
|
||||
<WorkflowModalContext.Provider value={contextValue}>
|
||||
@@ -215,7 +215,7 @@ export const useWorkflowModalParts = (props: WorkFlowModalModeProps) => {
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
/** 筛选组件 */
|
||||
/** filter component */
|
||||
let filter = (
|
||||
<QueryClientProvider client={workflowQueryClient}>
|
||||
<WorkflowModalContext.Provider value={contextValue}>
|
||||
@@ -228,7 +228,7 @@ export const useWorkflowModalParts = (props: WorkFlowModalModeProps) => {
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
// 隐藏 sider 后,把 title 放到 filter 上边
|
||||
// After hiding the sider, put the title on the filter
|
||||
if (hideSidebar && !isBindDouyin) {
|
||||
const title = I18n.t(
|
||||
WORKFLOW_MODAL_I18N_KEY_MAP[flowMode]?.[
|
||||
|
||||
@@ -200,7 +200,7 @@ export function useWorkflowProductList({
|
||||
return { workflowId, pluginId };
|
||||
};
|
||||
return {
|
||||
// 筛选条件
|
||||
// filter criteria
|
||||
updatePageParam,
|
||||
|
||||
//
|
||||
@@ -213,7 +213,7 @@ export function useWorkflowProductList({
|
||||
isFetchingNextPage,
|
||||
hasNextPage,
|
||||
|
||||
// 操作
|
||||
// operation
|
||||
copyProduct,
|
||||
} as const;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function useWorkflowSearch() {
|
||||
const context = useContext(WorkflowModalContext);
|
||||
const { run: debounceChangeSearch, cancel } = useDebounceFn(
|
||||
(search: string) => {
|
||||
/** 搜索最大字符数 */
|
||||
/** Search maximum number of characters */
|
||||
const maxCount = 100;
|
||||
if (search.length > maxCount) {
|
||||
updateSearchQuery(search.substring(0, maxCount));
|
||||
@@ -49,7 +49,7 @@ export function useWorkflowSearch() {
|
||||
const updateSearchQuery = (search?: string) => {
|
||||
const newState: Partial<WorkflowModalState> = { query: search ?? '' };
|
||||
if (dataSourceType === DataSourceType.Workflow) {
|
||||
// 搜索时如果有标签, 重置全部
|
||||
// If there are tags when searching, reset all
|
||||
newState.workflowTag = isSpaceWorkflow ? 0 : 1;
|
||||
newState.sortType = undefined;
|
||||
}
|
||||
@@ -73,11 +73,11 @@ export function useWorkflowSearch() {
|
||||
data-testid="workflow.modal.search"
|
||||
onSearch={search => {
|
||||
if (!search) {
|
||||
// 如果search清空了,那么立即更新query
|
||||
// If the search is empty, update the query immediately
|
||||
cancel();
|
||||
updateSearchQuery('');
|
||||
} else {
|
||||
// 如果search有值,那么防抖更新
|
||||
// If search has a value, then anti-shake update
|
||||
debounceChangeSearch(search);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -53,12 +53,12 @@ export const CreateWorkflowBtn: FC<
|
||||
}
|
||||
const { createModalVisible, setCreateModalVisible, bindBizType } = context;
|
||||
|
||||
// 如果是抖音分身场景,此时只展示一个【创建工作流】按钮
|
||||
// If it is a Douyin doppelganger scene, only one [Create Workflow] button will be displayed at this time.
|
||||
const showSingleButton =
|
||||
bindBizType === BindBizType.DouYinBot ||
|
||||
from === WorkflowModalFrom.WorkflowAgent;
|
||||
|
||||
/** 打开流程详情页 */
|
||||
/** Open the process details page */
|
||||
const menuConfig = [
|
||||
{
|
||||
label: I18n.t('workflow_add_navigation_create'),
|
||||
@@ -148,7 +148,7 @@ export const CreateWorkflowBtn: FC<
|
||||
'create workflow failed, no workflow id',
|
||||
);
|
||||
}
|
||||
// 由于服务端创建 workflow 的主备数据同步有延迟,所以在创建完后如果直接跳转,有可能查不到 workflowId,所以前端延迟下,降低问题触发的概率
|
||||
// Due to the delay in the synchronization of the main and standby data of the workflow created by the server level, if you jump directly after the creation, the workflowId may not be found, so the front-end delay reduces the probability of the problem triggering
|
||||
await wait(500);
|
||||
|
||||
if (onCreateSuccess) {
|
||||
|
||||
@@ -105,7 +105,7 @@ const WorkflowFilter = forwardRef<
|
||||
}
|
||||
|
||||
const resp = await ProductApi.PublicGetProductCategoryList({
|
||||
// 模版分类对于 工作流 / 图像流 通用
|
||||
// Template classification, for workflow/image circulation
|
||||
entity_type: ProductEntityType.TemplateCommon,
|
||||
need_empty_category: false,
|
||||
});
|
||||
@@ -172,7 +172,7 @@ const WorkflowFilter = forwardRef<
|
||||
useImperativeHandle(ref, () => ({
|
||||
getCurrent: () => tags?.data.find(item => item.type === currentValue),
|
||||
}));
|
||||
/** 展示空间流程, 我的/团队的 */
|
||||
/** Showcase space flow, my/team's */
|
||||
const clickSpaceContent = (category?: WorkflowCategory) => {
|
||||
context?.updateModalState({
|
||||
isSpaceWorkflow: category !== WorkflowCategory.Example,
|
||||
|
||||
@@ -41,9 +41,9 @@ export interface BotPluginWorkFlowItem extends WorkflowDetailData {
|
||||
|
||||
export type GetProductListRequest = public_api.GetProductListRequest;
|
||||
/**
|
||||
* 商品类型
|
||||
* Product type
|
||||
*
|
||||
* 由于类型同名问题, 直接导出 ProductInfo 指向的是后台的类型不是目标类型,需要使用本方法转一下
|
||||
* Due to the problem of the same name of the type, the direct export of ProductInfo points to the type in the background, not the target type. You need to use this method to transfer it.
|
||||
*/
|
||||
export type ProductInfo = public_api.ProductInfo;
|
||||
|
||||
@@ -52,11 +52,11 @@ export enum MineActiveEnum {
|
||||
Mine = '2',
|
||||
}
|
||||
|
||||
/** 数据来源 */
|
||||
/** data source */
|
||||
export enum DataSourceType {
|
||||
/** 流程 */
|
||||
/** process */
|
||||
Workflow = 'workflow',
|
||||
/** @deprecated 流程商店 */
|
||||
/** @deprecated process store */
|
||||
Product = 'product',
|
||||
}
|
||||
|
||||
@@ -66,136 +66,136 @@ export type WorkflowItemType =
|
||||
|
||||
export const WORKFLOW_LIST_STATUS_ALL = 'all';
|
||||
/**
|
||||
* 项目内的工作流添加子流程时的分类中,资源库/项目工作流分类
|
||||
* In the category of workflows within a project when subprocesses are added, the resource library/project workflow category
|
||||
*/
|
||||
export enum WorkflowCategory {
|
||||
/**
|
||||
* 项目工作流
|
||||
* project workflow
|
||||
*/
|
||||
Project = 'project',
|
||||
/**
|
||||
* 资源库工作流
|
||||
* repository workflow
|
||||
*/
|
||||
Library = 'library',
|
||||
/**
|
||||
* 官方示例
|
||||
* official example
|
||||
*/
|
||||
Example = 'example',
|
||||
}
|
||||
/** 流程弹窗状态 */
|
||||
/** Process pop-up status */
|
||||
export interface WorkflowModalState {
|
||||
/** 流程状态 */
|
||||
/** process state */
|
||||
status: WorkFlowListStatus | typeof WORKFLOW_LIST_STATUS_ALL;
|
||||
/** @deprecated 数据类型, 当前请求的是流程数据还是商店数据 */
|
||||
/** @Deprecated data type, is the process data or store data being requested? */
|
||||
dataSourceType: DataSourceType;
|
||||
/** 创建者 */
|
||||
/** creator */
|
||||
creator: MineActiveEnum;
|
||||
/** @deprecated 工作流模板标签 */
|
||||
/** @deprecated workflow template tag */
|
||||
workflowTag: number;
|
||||
/** @deprecated 商品标签 */
|
||||
/** @deprecated product label */
|
||||
productCategory: string;
|
||||
/** 搜索关键字 */
|
||||
/** Search keywords */
|
||||
query: string;
|
||||
/** @deprecated 是否请求当前空间流程 */
|
||||
/** @Deprecated whether to request the current space flow */
|
||||
isSpaceWorkflow: boolean;
|
||||
/** 选中的 workflow 分类 */
|
||||
/** Selected workflow category */
|
||||
workflowCategory?: WorkflowCategory;
|
||||
/** @deprecated 商店产品下的排序方式 */
|
||||
/** Sort by @deprecated store products */
|
||||
sortType?: SortType;
|
||||
/** 弹窗内列表筛选的工作流类型,可以的值是 All、Workflow、Chatflow。用于列表里工作流类型筛选,此时 Imageflow 已经合并到 Workflow 类型中了 */
|
||||
/** The workflow type for list filtering in the pop-up window, the possible values are All, Workflow, Chatflow. Used for workflow type filtering in the list, at this time Imageflow has been merged into the Workflow type */
|
||||
listFlowMode: WorkflowMode;
|
||||
}
|
||||
|
||||
/** 流程弹窗 */
|
||||
/** process pop-up */
|
||||
export interface WorkFlowModalModeProps {
|
||||
/** 当前弹窗来源,默认不传 */
|
||||
/** The source of the current pop-up window is not uploaded by default. */
|
||||
from?: WorkflowModalFrom;
|
||||
/** 流程类型, 工作流还是图像流, 默认工作流 */
|
||||
/** Process type, workflow or image flow, default workflow */
|
||||
flowMode?: WorkflowMode;
|
||||
/** 隐藏的流程 */
|
||||
/** hidden flow */
|
||||
excludedWorkflowIds?: string[];
|
||||
/**
|
||||
* filter 状态筛选组件是否显示全部状态选项,默认为 false
|
||||
* Filter status Whether the filter component displays all status options, the default is false
|
||||
*/
|
||||
filterOptionShowAll?: boolean;
|
||||
/**
|
||||
* 是否隐藏侧边栏,默认 false。用于场景详情页选择 workflow。
|
||||
* Whether to hide the sidebar, the default is false. Use the scene details page to select workflow.
|
||||
*/
|
||||
hideSider?: boolean;
|
||||
/* 是否隐藏作者筛选菜单 */
|
||||
/* Whether to hide the author filter menu */
|
||||
hideCreatorSelect?: boolean;
|
||||
/**
|
||||
* workflow item 是否显示删除按钮,默认 false,用于场景 workflow 以及抖音分身工作流
|
||||
* Whether the workflow item shows the delete button, the default is false, used for scene workflow and Douyin doppelganger workflow
|
||||
*/
|
||||
itemShowDelete?: boolean;
|
||||
/** @deprecated 是否隐藏空间下 Workflow 列表模块 */
|
||||
/** @Deprecated Whether to hide the Workflow list module under the space */
|
||||
hiddenSpaceList?: boolean;
|
||||
/**
|
||||
* @deprecated 使用 hiddenWorkflowCategories
|
||||
* 是否隐藏资源库模块
|
||||
* @deprecated hiddenWorkflowCategories
|
||||
* Whether to hide the library module
|
||||
*/
|
||||
hiddenLibrary?: boolean;
|
||||
/** 是否隐藏创建工作流入口 */
|
||||
/** Whether to hide the creation workflow entry */
|
||||
hiddenCreate?: boolean;
|
||||
/**
|
||||
* @deprecated 探索分类已改为官方示例,使用 hiddenWorkflowCategories
|
||||
* 隐藏探索分类
|
||||
* @Deprecated Explore category has been changed to official example, using hiddenWorkflowCategories
|
||||
* Hidden Exploration Classification
|
||||
*/
|
||||
hiddenExplore?: boolean;
|
||||
/**
|
||||
* 隐藏的工作流分类,用法同 hiddenLibrary、hiddenExplore,
|
||||
* Hidden workflow classification, the usage is the same as hiddenLibrary, hiddenExplore,
|
||||
*/
|
||||
hiddenWorkflowCategories?: WorkflowCategory[];
|
||||
/**
|
||||
* 隐藏工作流列表类型筛选
|
||||
* Hidden workflow list type filter
|
||||
*/
|
||||
hiddenListFlowModeFilter?: boolean;
|
||||
/** 复制按钮文案, 默认「复制并添加」 */
|
||||
/** Copy button copy, default "copy and add" */
|
||||
dupText?: string;
|
||||
/** 初始状态, 配置各筛选项 */
|
||||
/** Initial state, configure each filter */
|
||||
initState?: Partial<WorkflowModalState>;
|
||||
/** 已选流程列表 */
|
||||
/** list of selected processes */
|
||||
workFlowList?: BotPluginWorkFlowItem[];
|
||||
/** 已选流程列表变化 */
|
||||
/** Selected process list changes */
|
||||
onWorkFlowListChange?: (newList: BotPluginWorkFlowItem[]) => void;
|
||||
/** 选择流程 */
|
||||
/** selection process */
|
||||
onAdd?: (
|
||||
item: BotPluginWorkFlowItem,
|
||||
config: {
|
||||
/** 是否来源于复制 */
|
||||
/** Does it come from copying? */
|
||||
isDup: boolean;
|
||||
/** 目标空间 */
|
||||
/** Target space */
|
||||
spaceId: string;
|
||||
},
|
||||
) => void;
|
||||
/** 移除流程 */
|
||||
/** removal process */
|
||||
onRemove?: (item: BotPluginWorkFlowItem) => void;
|
||||
/**
|
||||
* 删除流程后的回调 hooks,同时会执行 removeWorkflow 移除和 bot/场景 的关联
|
||||
* Removes the callback hooks after the process, and removeWorkflow removes the association with the bot/scene
|
||||
* @param item
|
||||
*/
|
||||
onDelete?: (item: BotPluginWorkFlowItem) => void;
|
||||
/**
|
||||
* 列表项点击
|
||||
* List item click
|
||||
*
|
||||
* 配置可覆盖默认行为: 新开页面打开详情页
|
||||
* @returns 返回 { handled: true } 或 undefined 不执行默认操作,否则执行内部默认的点击事件
|
||||
* Configuration overrides default behavior: Open a new page Open the details page
|
||||
* @Returns returns {handled: true} or undefined does not perform the default action, otherwise the internal default click event is executed
|
||||
*/
|
||||
onItemClick?:
|
||||
| ((
|
||||
item: WorkflowItemType,
|
||||
/** 弹窗状态, 可用于初始化弹窗 */
|
||||
/** Pop-up status, which can be used to initialize the pop-up */
|
||||
modalState: WorkflowModalState,
|
||||
) => { handled: boolean })
|
||||
| ((
|
||||
item: WorkflowItemType,
|
||||
/** 弹窗状态, 可用于初始化弹窗 */
|
||||
/** Pop-up status, which can be used to initialize the pop-up */
|
||||
modalState: WorkflowModalState,
|
||||
) => void);
|
||||
/**
|
||||
* 创建流程成功
|
||||
* Successful creation process
|
||||
*
|
||||
* 配置可覆盖默认行为: 新页面打开复制后的流程详情, 带参数 from=createSuccess
|
||||
* Configuration can override default behavior: new page opens process details after replication, with parameter from = createSuccess
|
||||
*/
|
||||
onCreateSuccess?: (info: {
|
||||
spaceId: string;
|
||||
@@ -203,12 +203,12 @@ export interface WorkFlowModalModeProps {
|
||||
flowMode: WorkflowMode;
|
||||
}) => void;
|
||||
/**
|
||||
* 复制流程成功
|
||||
* The replication process was successful.
|
||||
*
|
||||
* 配置可覆盖默认行为: Toast 提示复制成功, 继续编辑
|
||||
* Configuration can override default behavior: Toast prompt Copy successful, continue editing
|
||||
*/
|
||||
onDupSuccess?: (item: BotPluginWorkFlowItem) => void;
|
||||
/** 项目内引入资源库文件触发的回调 */
|
||||
/** Callback triggered by introducing a repository file into the project */
|
||||
onImport?: (
|
||||
item: Pick<BotPluginWorkFlowItem, 'workflow_id' | 'name'>,
|
||||
) => void;
|
||||
@@ -217,10 +217,10 @@ export interface WorkFlowModalModeProps {
|
||||
projectId?: string;
|
||||
onClose?: () => void;
|
||||
/**
|
||||
* 创建 workflow 弹窗内命名校验
|
||||
* Create name check in workflow pop-up
|
||||
*/
|
||||
nameValidators?: RuleItem[];
|
||||
/** 自定义 i18n 文案 */
|
||||
/** Custom i18n copy */
|
||||
i18nMap?: Partial<Record<ModalI18nKey, I18nKey>>;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { type WorkflowModalState, WorkflowCategory } from './type';
|
||||
|
||||
/**
|
||||
* workflow modal 当前是否选中了 project 工具流分类
|
||||
* Workflow modal Whether the project toolflow category is currently selected
|
||||
* @param modalState
|
||||
*/
|
||||
export const isSelectProjectCategory = (modalState?: WorkflowModalState) =>
|
||||
|
||||
@@ -31,22 +31,22 @@ export interface WorkflowModalContextValue {
|
||||
spaceType: SpaceType;
|
||||
bindBizId?: string;
|
||||
bindBizType?: BindBizType;
|
||||
/** 当前项目 id,只在项目内的 workflow 有该字段 */
|
||||
/** The current project id, only the workflow within the project has this field */
|
||||
projectId?: string;
|
||||
/** 工作流类型,此参数由 WorkflowModal 弹窗创建时由 props 传进来,可能的值是 Workflow、Imageflow。用于区分添加哪种工作流 */
|
||||
/** Workflow type, this parameter is passed in by props when created by WorkflowModal pop-up window, possible values are Workflow, Imageflow. Used to distinguish which workflow to add */
|
||||
flowMode: WorkflowMode;
|
||||
modalState: WorkflowModalState;
|
||||
/** 更新弹窗状态, merge 的模式 */
|
||||
/** Update popup status, merge mode */
|
||||
updateModalState: (newState: Partial<WorkflowModalState>) => void;
|
||||
orderBy: OrderBy;
|
||||
setOrderBy: React.Dispatch<React.SetStateAction<OrderBy>>;
|
||||
createModalVisible: boolean;
|
||||
setCreateModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
/** 获取当前弹窗状态, 可用于恢复弹窗状态 */
|
||||
/** Get the current pop-up state, which can be used to restore the pop-up state */
|
||||
getModalState: (ctx: WorkflowModalContextValue) => WorkflowModalState;
|
||||
|
||||
/** 自定义 i18n 文案 */
|
||||
/** Custom i18n copy */
|
||||
i18nMap?: Partial<Record<ModalI18nKey, I18nKey>>;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
'./node_modules/@coze-arch/coze-design/dist/**/*.{js,jsx,css}',
|
||||
],
|
||||
corePlugins: {
|
||||
preflight: false, // 关闭@tailwind base默认样式,避免对现有样式影响
|
||||
preflight: false, // Turn off @tailwind base default styles to avoid affecting existing styles
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
plugins: [require('@coze-arch/tailwind-config/coze')],
|
||||
|
||||
Reference in New Issue
Block a user