feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { BottomPanel } from './panel';

View File

@@ -0,0 +1,48 @@
.base-panel {
display: flex;
flex-direction: column;
border: 1px solid var(--coz-stroke-primary);
border-radius: 8px;
background-color: var(--coz-bg-plus);
position: relative;
min-width: 540px;
}
.dragging {
cursor: row-resize;
user-select: none;
pointer-events: none;
}
.panel-header {
height: 48px;
border-bottom: 1px solid var(--coz-stroke-primary);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
flex-shrink: 0;
column-gap: 8px;
}
.panel-content {
height: 100%;
flex-shrink: 1;
overflow-y: auto;
}
.panel-footer {
border-top: 1px solid var(--coz-stroke-primary);;
}
.resize-bar {
width: 100%;
height: 5px;
position: absolute;
top: 0;
left: 0;
cursor: row-resize;
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { isObject } from 'lodash-es';
import { clsx } from 'clsx';
import { IconCozCross } from '@coze-arch/coze-design/icons';
import { IconButton } from '@coze-arch/coze-design';
import { useResize } from './use-resize';
import css from './panel.module.less';
interface BasePanelProps {
className?: string;
/**
* 面板头,不传不渲染
*/
header?: React.ReactNode;
/**
* 面板脚,不传不渲染
*/
footer?: React.ReactNode;
/**
* 默认初始高度,不支持响应式
*/
height?: number;
/**
* 是否可拖拽改变高度
*/
resizable?:
| boolean
| {
min?: number;
max?: number;
};
/**
* 点击关闭事件,仅当渲染面板头时可能触发
*/
onClose?: () => void;
}
export const BottomPanel: React.FC<React.PropsWithChildren<BasePanelProps>> = ({
className,
header,
footer,
height,
resizable,
onClose,
children,
}) => {
const {
height: innerHeight,
bind,
ref,
dragging,
} = useResize({
default: height,
...(isObject(resizable) ? resizable : {}),
});
return (
<div
className={clsx(css['base-panel'], className, dragging && css.dragging)}
style={{ height: innerHeight }}
ref={ref}
>
{resizable ? (
<div className={css['resize-bar']} onMouseDown={bind} />
) : null}
{header ? (
<div className={css['panel-header']}>
{header}
<IconButton
icon={<IconCozCross className={'text-[18px]'} />}
color="secondary"
onClick={onClose}
/>
</div>
) : null}
<div className={css['panel-content']}>{children}</div>
{footer ? <div className={css['panel-footer']}>{footer}</div> : null}
</div>
);
};

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState, useRef, useCallback } from 'react';
import { useMemoizedFn } from 'ahooks';
interface Config {
default?: number;
min?: number;
max?: number;
}
/**
* 目前仅支持高度可变
*/
export const useResize = (config: Config) => {
const [dragging, setDragging] = useState(false);
const [height, setHeight] = useState(config.default);
const ref = useRef<HTMLDivElement>(null);
/**
* 拖拽过程中
*/
const resizing = useRef(false);
/**
* y 轴变化
*/
const startY = useRef(0);
/** 开始位置 */
const start = useRef(0);
const handleMouseMove = useMemoizedFn(e => {
if (resizing.current) {
const newHeight = start.current - (e.clientY - startY.current); // 计算新的高度
if (config.max && newHeight > config.max) {
setHeight(config.max);
} else if (config.min && newHeight < config.min) {
setHeight(config.min);
} else {
setHeight(newHeight);
}
}
});
const handleMouseUp = useCallback(() => {
resizing.current = false;
setDragging(false);
document.removeEventListener('mousemove', handleMouseMove); // 取消监听
document.removeEventListener('mouseup', handleMouseUp); // 取消监听
}, [handleMouseMove]);
const handleMouseDown = useMemoizedFn(e => {
resizing.current = true;
setDragging(true);
startY.current = e.clientY; // 记录鼠标开始拖拽时的 Y 轴坐标
start.current = ref.current?.offsetHeight || 0;
document.addEventListener('mousemove', handleMouseMove); // 监听鼠标移动事件
document.addEventListener('mouseup', handleMouseUp); // 监听鼠标抬起事件
});
return {
height,
bind: handleMouseDown,
ref,
dragging,
};
};

View File

@@ -0,0 +1,179 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EditorProvider, createRenderer } from '@coze-editor/editor/react';
import preset, {
languages,
createTheme,
tags,
} from '@coze-editor/editor/preset-code';
import { json } from '@coze-editor/editor/language-json';
import { EditorView, tooltips } from '@codemirror/view';
const colors = {
background: '#F7F7FC',
// syntax
comment: '#000A298A',
key: '#00818C',
string: '#D1009D',
number: '#C74200',
boolean: '#2B57D9',
null: '#2B57D9',
separator: '#0F1529D1',
};
languages.register('json', json);
const JSONEditor: any = createRenderer(preset, [
EditorView.theme({
'&': {
borderRadius: '8px',
},
'.cm-scroller': {
transition: 'height .3s ease',
},
'.cm-content': {
paddingTop: '6px',
paddingBottom: '6px',
},
'.cm-completionIcon-property': {
backgroundImage:
'url("' +
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMWVtIiBoZWlnaHQ9IjFlbSIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMi4zNTc2IDguMTAzNTVDMTIuMTYyMyA3LjkwODI5IDExLjg0NTcgNy45MDgyOSAxMS42NTA1IDguMTAzNTVMOC4xMDM1NSAxMS42NTA1QzcuOTA4MjkgMTEuODQ1NyA3LjkwODI5IDEyLjE2MjMgOC4xMDM1NSAxMi4zNTc2TDExLjY1MDUgMTUuOTA0NUMxMS44NDU3IDE2LjA5OTggMTIuMTYyMyAxNi4wOTk4IDEyLjM1NzYgMTUuOTA0NUwxNS45MDQ1IDEyLjM1NzZDMTYuMDk5OCAxMi4xNjIzIDE2LjA5OTggMTEuODQ1NyAxNS45MDQ1IDExLjY1MDVMMTIuMzU3NiA4LjEwMzU1WiIgZmlsbD0iIzA2MDcwOUNDIi8+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMS4wMDI2IDEuNDU1NDVDMTEuNjIxNCAxLjA5ODE4IDEyLjM4MzggMS4wOTgxOCAxMy4wMDI2IDEuNDU1NDVMMjAuNjM4IDUuODYzNzRDMjEuMjU2OCA2LjIyMSAyMS42MzggNi44ODEyNiAyMS42MzggNy41OTU3OVYxNi40MTI0QzIxLjYzOCAxNy4xMjY5IDIxLjI1NjggMTcuNzg3MiAyMC42MzggMTguMTQ0NEwxMy4wMDI2IDIyLjU1MjdDMTIuMzgzOCAyMi45MSAxMS42MjE0IDIyLjkxIDExLjAwMjYgMjIuNTUyN0wzLjM2NzE5IDE4LjE0NDRDMi43NDgzOSAxNy43ODcyIDIuMzY3MTkgMTcuMTI2OSAyLjM2NzE5IDE2LjQxMjRWNy41OTU3OUMyLjM2NzE5IDYuODgxMjYgMi43NDgzOSA2LjIyMTAxIDMuMzY3MTkgNS44NjM3NEwxMS4wMDI2IDEuNDU1NDVaTTEyLjAwMjYgMy4xODc1TDE5LjYzOCA3LjU5NTc5VjE2LjQxMjRMMTIuMDAyNiAyMC44MjA3TDQuMzY3MTkgMTYuNDEyNEw0LjM2NzE5IDcuNTk1NzlMMTIuMDAyNiAzLjE4NzVaIiBmaWxsPSIjMDYwNzA5Q0MiLz48L3N2Zz4=' +
'")',
backgroundSize: '11px 11px',
backgroundRepeat: 'no-repeat',
width: '11px',
height: '11px',
},
'.cm-completionIcon-property::after': {
content: '""',
},
'.cm-selectionBackground': {
borderRadius: '4px',
},
'.cm-activeLineGutter': {
borderRadius: '4px 0 0 4px',
},
'.cm-activeLine': {
borderRadius: '0 4px 4px 0',
},
'&.cm-focused': {
outline: 'none',
},
'& *': {
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
},
'.cm-tooltip': {
wordBreak: 'break-all',
},
'.cm-lineNumbers .cm-gutterElement': {
transform: 'translate(0, 1px)',
},
'.cm-foldGutter .cm-gutterElement > div': {
display: 'flex',
alignItems: 'center',
},
'.cm-completionIcon': {
fontSize: '11px',
},
}),
createTheme({
variant: 'light',
settings: {
background: colors.background,
foreground: '#4D4D4C',
caret: '#AEAFAD',
selection: '#52649A21',
gutterBackground: colors.background,
gutterForeground: '#000A298A',
gutterBorderColor: 'transparent',
gutterBorderWidth: 0,
lineHighlight: '#efefef78',
bracketColors: ['#E4D129', '#AC05FF', '#2B57D9'],
tooltip: {
backgroundColor: 'var(--coz-bg-max)',
color: 'var(--coz-fg-primary)',
border: 'solid 1px var(--coz-stroke-plus)',
boxShadow: 'var(--coz-shadow-default)',
borderRadius: '8px',
},
tooltipCompletion: {
backgroundColor: '#FFFFFF',
color: '#060709CC',
},
completionItemHover: {
backgroundColor: '#5768A114',
},
completionItemSelected: {
backgroundColor: '#52649A21',
},
completionItemIcon: {
color: '#060709CC',
},
completionItemLabel: {
color: '#060709CC',
},
completionItemDetail: {
color: '#2029459E',
},
},
styles: [
// JSON
{
tag: tags.comment,
color: colors.comment,
},
{
tag: [tags.propertyName],
color: colors.key,
},
{
tag: [tags.string],
color: colors.string,
},
{
tag: [tags.number],
color: colors.number,
},
{
tag: [tags.bool],
color: colors.boolean,
},
{
tag: [tags.null],
color: colors.null,
},
{
tag: [tags.separator],
color: colors.separator,
},
],
}),
tooltips({
parent: document.body,
tooltipSpace() {
return {
left: 16,
top: 16,
right: window.innerWidth - 16,
bottom: window.innerHeight - 16,
};
},
}),
]);
export { JSONEditor, EditorProvider };

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { JsonEditor } from './json-editor';

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useCallback, useEffect, useRef, useState } from 'react';
import { uniqueId } from 'lodash-es';
import { type EditorAPI } from '@coze-editor/editor/preset-code';
import { json } from '@coze-editor/editor/language-json';
import { JSONEditor, EditorProvider } from './base';
interface JsonEditorProps {
value?: string;
height?: string;
disabled?: boolean;
extensions?: any[];
jsonSchema?: any;
onChange?: (v?: string) => void;
onBlur?: () => void;
onFocus?: () => void;
didMount?: (editor: any) => void;
}
export const JsonEditor: React.FC<JsonEditorProps> = ({
value,
height,
disabled,
extensions,
jsonSchema,
onChange,
onBlur,
onFocus,
didMount,
}) => {
const [focus, setFocus] = useState(false);
const [uri] = useState(() => `file:///${uniqueId()}.json`);
const editorRef = useRef<EditorAPI | null>(null);
const handleChange = val => {
onChange?.(val || undefined);
};
const handleBlur = useCallback(() => {
setFocus(false);
onBlur?.();
}, [onBlur]);
const handleFocus = useCallback(() => {
setFocus(true);
onFocus?.();
}, [onFocus]);
useEffect(() => {
const schemaURI = `file:///${uniqueId()}`;
json.languageService.configureSchemas({
uri: schemaURI,
fileMatch: [uri],
schema: jsonSchema || {},
});
editorRef.current?.validate();
return () => {
json.languageService.deleteSchemas(schemaURI);
};
}, [uri, jsonSchema]);
useEffect(() => {
if (!editorRef.current) {
return;
}
if (value !== editorRef.current.getValue()) {
editorRef.current.setValue(value || '');
}
}, [value]);
return (
<EditorProvider>
<JSONEditor
defaultValue={value ?? ''}
options={{
uri,
languageId: 'json',
fontSize: 12,
height: height ? height : focus ? '264px' : '120px',
readOnly: disabled,
editable: !disabled,
}}
extensions={extensions}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={e => handleChange(e.value)}
didMount={_ => {
editorRef.current = _;
didMount?.(_);
}}
/>
</EditorProvider>
);
};

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types='@coze-arch/bot-typings' />
declare module '*.otf' {
const content: string;
export default content;
}
declare module '*.ttf' {
const content: string;
export default content;
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* TestRun Shared
*/
export { JsonEditor } from './components/json-editor';
export { safeFormatJsonString, safeJsonParse, gotoDebugFlow } from './utils';
export { BottomPanel } from './components/bottom-panel';

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
interface DebugUrlParams {
spaceId: string;
workflowId: string;
executeId: string;
nodeId?: string;
subExecuteId?: string;
}
/**
* 计算 DebugUrl
*/
const getDebugUrl = (params: DebugUrlParams) => {
const { spaceId, workflowId, executeId, subExecuteId, nodeId } = params;
const search = new URLSearchParams({
space_id: spaceId,
workflow_id: workflowId,
execute_id: executeId,
node_id: nodeId || '',
sub_execute_id: subExecuteId || '',
});
return `/work_flow?${search.toString()}`;
};
export const gotoDebugFlow = (params: DebugUrlParams, op?: boolean) => {
if (op) {
const { workflowId, executeId, subExecuteId, nodeId } = params;
const search = new URLSearchParams({
workflow_id: workflowId,
execute_id: executeId,
node_id: nodeId || '',
sub_execute_id: subExecuteId || '',
});
window.open(`${window.location.pathname}?${search.toString()}`);
}
const url = getDebugUrl(params);
window.open(url);
};

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { safeFormatJsonString } from './safe-format-json-string';
export { safeJsonParse } from './safe-json-parse';
export { gotoDebugFlow } from './debug-url';

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isString } from 'lodash-es';
export const safeFormatJsonString = (val: unknown): any => {
if (!isString(val)) {
return val;
}
try {
return JSON.stringify(JSON.parse(val), null, 2);
} catch {
return val;
}
};

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
interface TypeSafeJSONParseOptions {
emptyValue?: any;
needReport?: boolean;
enableBigInt?: boolean;
}
export const safeJsonParse = (
v: unknown,
options?: TypeSafeJSONParseOptions,
): unknown => {
if (typeof v === 'object') {
return v;
}
try {
return JSON.parse(String(v));
} catch {
return options?.emptyValue;
}
};