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,31 @@
import { mergeConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: config =>
mergeConfig(config, {
plugins: [
svgr({
svgrOptions: {
native: false,
},
}),
],
}),
};
export default config;

View File

@@ -0,0 +1,14 @@
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View File

@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,16 @@
# @coze-workflow/code-editor-adapter
> Project template for react component with storybook.
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,47 @@
{
"name": "@coze-workflow/code-editor-adapter",
"version": "0.0.1",
"description": "code editor",
"license": "Apache-2.0",
"author": "liji.leej@bytedance.com",
"maintainers": [],
"main": "src/index.tsx",
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.34.1",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-editor/editor": "0.1.0-alpha.d92d50",
"@coze-workflow/base": "workspace:*",
"classnames": "^2.3.2"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"stylelint": "^15.11.0",
"vite-plugin-svgr": "~3.3.0",
"vitest": "~3.0.5"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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, { useRef, useState } from 'react';
import { I18n } from '@coze-arch/i18n';
import { IconCozWarningCircleFill } from '@coze-arch/coze-design/icons';
import { Modal } from '@coze-arch/coze-design';
import { Layout } from '../layout';
import { type EditorProps, type LanguageType } from '../../interface';
import { Editor } from './editor';
export const BizEditor = (props: EditorProps) => {
const editorApi = useRef<undefined | { getValue?: () => string }>(undefined);
const [language, setLanguage] = useState<LanguageType>(props.defaultLanguage);
const [content, setContent] = useState<string | undefined>(
props.defaultContent,
);
const handleLanguageChange = (value: LanguageType) => {
const langTemplate = props.languageTemplates?.find(
e => e.language === value,
);
const preLangTemplate = props.languageTemplates?.find(
e => e.language === language,
);
if (preLangTemplate?.template === editorApi.current?.getValue?.()) {
setLanguage(value);
setContent(langTemplate?.template);
props.onChange?.(langTemplate?.template || '', value);
return;
}
Modal.warning({
icon: (
<IconCozWarningCircleFill
style={{ color: 'rgba(var(--coze-yellow-5), 1)' }}
/>
),
title: I18n.t('code_node_switch_language'),
content: I18n.t('code_node_switch_language_description'),
okType: 'warning',
okText: I18n.t('Confirm'),
cancelText: I18n.t('Cancel'),
closable: true,
width: 448,
height: 160,
onOk: () => {
setLanguage(value);
setContent(langTemplate?.template);
props.onChange?.(langTemplate?.template || '', value);
},
});
};
return (
<>
<Layout
{...props}
language={language}
onLanguageSelect={handleLanguageChange}
>
<Editor
{...props}
defaultContent={content}
language={language}
didMount={api => {
editorApi.current = api;
}}
/>
</Layout>
</>
);
};

View File

@@ -0,0 +1,54 @@
/*
* 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, { type FC, lazy, Suspense, useMemo } from 'react';
import { type EditorOtherProps, type EditorProps } from '../../interface';
const LazyPythonEditor: FC<EditorProps> = lazy(async () => {
const { PythonEditor } = await import('./python-editor');
return { default: PythonEditor };
});
const PythonEditor: FC<EditorProps> = props => (
<Suspense>
<LazyPythonEditor {...props} />
</Suspense>
);
const LazyTypescriptEditor: FC<EditorProps> = lazy(async () => {
const { TypescriptEditor } = await import('./typescript-editor');
return { default: TypescriptEditor };
});
const TypescriptEditor: FC<EditorProps> = props => (
<Suspense>
<LazyTypescriptEditor {...props} />
</Suspense>
);
export const Editor = (props: EditorProps & EditorOtherProps) => {
const language = useMemo(
() => props.language || props.defaultLanguage,
[props.defaultLanguage, props.language],
);
if (language === 'python') {
return <PythonEditor {...props} />;
}
return <TypescriptEditor {...props} />;
};

View File

@@ -0,0 +1,18 @@
/*
* 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 { Editor } from './editor';
export { BizEditor } from './biz-editor';

View File

@@ -0,0 +1,41 @@
/*
* 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 { api, type InferEditorAPIFromPlugins } from '@coze-editor/editor/react';
import preset from '@coze-editor/editor/preset-code';
import { type EditorView } from '@codemirror/view';
// 忽略 readOnly 强制设置值
const forceSetValue =
({ view }: { view: EditorView }) =>
(value: string) => {
const { state } = view;
view.dispatch(
state.update({
changes: {
from: 0,
to: state.doc.length,
insert: value ?? '',
},
}),
);
};
const customPreset = [...preset, api('forceSetValue', forceSetValue)];
export type EditorAPI = InferEditorAPIFromPlugins<typeof customPreset>;
export default customPreset;

View File

@@ -0,0 +1,85 @@
/*
* 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 { Renderer, EditorProvider } from '@coze-editor/editor/react';
import { languages } from '@coze-editor/editor/preset-code';
import { python } from '@coze-editor/editor/language-python';
import { EditorView } from '@codemirror/view';
import { type EditorOtherProps, type EditorProps } from '../../interface';
import preset from './preset';
languages.register('python', python);
export const PythonEditor = (props: EditorProps & EditorOtherProps) => {
const {
defaultContent,
uuid,
readonly,
height,
didMount,
onChange,
defaultLanguage,
} = props;
return (
<EditorProvider>
<Renderer
plugins={preset}
domProps={{
style: {
height: 'calc(100% - 48px)',
},
}}
didMount={api => {
didMount?.(api);
api.$on('change', ({ value }) => {
onChange?.(value, defaultLanguage);
});
}}
defaultValue={defaultContent}
extensions={[
EditorView.theme({
'&.cm-focused': {
outline: 'none',
},
'&.cm-editor': {
height: height || 'unset',
},
'.cm-content': {
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
},
'.cm-content *': {
fontFamily: 'inherit',
},
}),
]}
options={{
uri: `file:///py_editor_${uuid}.py`,
languageId: 'python',
theme: 'code-editor-dark',
height,
readOnly: readonly,
editable: !readonly,
fontSize: 12,
tabSize: 4,
}}
/>
</EditorProvider>
);
};

View File

@@ -0,0 +1,161 @@
/*
* 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 { ViewVariableType } from '@coze-workflow/base';
import { languages } from '@coze-editor/editor/preset-code';
import { typescript } from '@coze-editor/editor/language-typescript';
import { type Input, ModuleDetectionKind, type Output } from '../../interface';
export const initTypescriptServer = () => {
languages.register('typescript', typescript);
const tsWorker = new Worker(
new URL('@coze-editor/editor/language-typescript/worker', import.meta.url),
{ type: 'module' },
);
typescript.languageService.initialize(tsWorker, {
compilerOptions: {
// eliminate Promise error
lib: ['es2015'],
moduleDetection: ModuleDetectionKind.Force,
},
});
};
const mapVariableType = (type?: ViewVariableType): string => {
switch (type) {
case ViewVariableType.String:
return 'string';
case ViewVariableType.Integer:
return 'number';
case ViewVariableType.Boolean:
return 'boolean';
case ViewVariableType.Number:
return 'number';
case ViewVariableType.Object:
return 'object';
case ViewVariableType.Image:
case ViewVariableType.File:
case ViewVariableType.Doc:
case ViewVariableType.Code:
case ViewVariableType.Ppt:
case ViewVariableType.Txt:
case ViewVariableType.Excel:
case ViewVariableType.Audio:
case ViewVariableType.Zip:
case ViewVariableType.Video:
case ViewVariableType.Svg:
case ViewVariableType.Voice:
return 'string'; // Assuming file-like types are represented as strings (e.g., file paths or URLs)
case ViewVariableType.Time:
return 'Date';
case ViewVariableType.ArrayString:
return 'string[]';
case ViewVariableType.ArrayInteger:
return 'number[]';
case ViewVariableType.ArrayBoolean:
return 'boolean[]';
case ViewVariableType.ArrayNumber:
return 'number[]';
case ViewVariableType.ArrayObject:
return 'object[]';
case ViewVariableType.ArrayImage:
case ViewVariableType.ArrayFile:
case ViewVariableType.ArrayDoc:
case ViewVariableType.ArrayCode:
case ViewVariableType.ArrayPpt:
case ViewVariableType.ArrayTxt:
case ViewVariableType.ArrayExcel:
case ViewVariableType.ArrayAudio:
case ViewVariableType.ArrayZip:
case ViewVariableType.ArrayVideo:
case ViewVariableType.ArraySvg:
case ViewVariableType.ArrayVoice:
return 'string[]';
case ViewVariableType.ArrayTime:
return 'Date[]';
default:
return 'any'; // Fallback type
}
};
export function generateTypeDefinition(output: Output, indent = ' '): string {
let definition = '';
if (output.type === ViewVariableType.Object) {
definition += `${indent}${output.name}: {\n`;
if (output.children && output.children.length > 0) {
output.children.forEach(child => {
definition += `${indent} ${generateTypeDefinition(child, `${indent} `)}`;
});
}
definition += `${indent}}\n`;
return definition;
}
if (output.type === ViewVariableType.ArrayObject) {
definition += `${indent}${output.name}: {\n`;
if (output.children && output.children.length > 0) {
output.children.forEach(child => {
definition += `${indent} ${generateTypeDefinition(child, `${indent} `)}`;
});
definition += `${indent}}[]\n`;
}
return definition;
}
definition += `${indent}${output.name}: ${mapVariableType(output.type)};\n`;
definition += '\n';
return definition;
}
export const initInputAndOutput = async (
inputs: Input[] = [],
outputs: Output[] = [],
uuid = '',
): Promise<void> => {
const typeDefinition = `
declare module '/ts_editor_${uuid}' {
interface Args {
${generateTypeDefinition(
{
name: 'params',
type: ViewVariableType.Object,
children: inputs,
},
' ',
)}
}
interface Output {
${outputs.map(output => generateTypeDefinition(output, ' ')).join('\n')}
}
}
export {}
`;
await typescript.languageService.addExtraFiles({
[`/ts_editor_${uuid}.d.ts`]: typeDefinition,
});
};

View File

@@ -0,0 +1,95 @@
/*
* 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, { useEffect } from 'react';
import { Renderer, EditorProvider } from '@coze-editor/editor/react';
import { EditorView } from '@codemirror/view';
import { type EditorOtherProps, type EditorProps } from '../../interface';
import {
initInputAndOutput,
initTypescriptServer,
} from './typescript-editor-utils';
import preset from './preset';
initTypescriptServer();
export const TypescriptEditor = (props: EditorProps & EditorOtherProps) => {
const {
defaultContent,
uuid,
readonly,
height,
didMount,
onChange,
defaultLanguage,
input,
output,
} = props;
const uri = `file:///ts_editor_${uuid}.ts`;
useEffect(() => {
initInputAndOutput(input, output, uuid);
}, [uuid]);
return (
<EditorProvider>
<Renderer
plugins={preset}
domProps={{
style: {
height: 'calc(100% - 48px)',
},
}}
didMount={api => {
didMount?.(api);
api.$on('change', ({ value }) => {
onChange?.(value, defaultLanguage);
});
}}
defaultValue={defaultContent}
extensions={[
EditorView.theme({
'&.cm-focused': {
outline: 'none',
},
'&.cm-editor': {
height: height || 'unset',
},
'.cm-content': {
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
},
'.cm-content *': {
fontFamily: 'inherit',
},
}),
]}
options={{
uri,
languageId: 'typescript',
theme: 'code-editor-dark',
height,
readOnly: readonly,
editable: !readonly,
fontSize: 12,
tabSize: 4,
}}
/>
</EditorProvider>
);
};

View File

@@ -0,0 +1,150 @@
/*
* 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, { useMemo, type ReactNode } from 'react';
import { concatTestId, useNodeTestId } from '@coze-workflow/base';
import {
IconCozCodeFill,
IconCozPlayCircle,
IconCozSideCollapse,
} from '@coze-arch/coze-design/icons';
import {
Select,
Tooltip,
Button,
IconButton,
Typography,
} from '@coze-arch/coze-design';
const { Text } = Typography;
import { type EditorProps, type LanguageType } from '../../interface';
import style from './style.module.less';
import { I18n } from '@coze-arch/i18n';
const HELP_DOCUMENT_LINK = IS_OVERSEA
? '/docs/guides/code_node?_lang=en'
: '/docs/guides/code_node';
interface Props extends EditorProps {
children: ReactNode;
onLanguageSelect?: (language: LanguageType) => void;
language: LanguageType;
}
export const Layout = ({
children,
title,
language,
onClose,
onTestRun,
testRunIcon,
onLanguageSelect,
languageTemplates,
}: Props) => {
const optionList = useMemo(
() =>
languageTemplates?.map(e => ({
value: e.language,
label: e.displayName,
})),
[languageTemplates],
);
const { getNodeSetterId } = useNodeTestId();
const setterTestId = getNodeSetterId('biz-editor-layout');
return (
<div className={style.container}>
<div className={style.header}>
<div className={style.title}>
<div className={style['title-icon']}>
<IconCozCodeFill />
</div>
<div className={style['title-content']}>{title}</div>
<Tooltip
content={
<div>
{I18n.t('code_node_more_info')}
<Text link={{ href: HELP_DOCUMENT_LINK, target: '_blank' }}>
{I18n.t('code_node_help_doc')}
</Text>
</div>
}
theme={'dark'}
>
<Select
onChange={value => onLanguageSelect?.(value as LanguageType)}
value={language}
data-testid={concatTestId(setterTestId, 'language-select')}
renderSelectedItem={item => (
<span
style={{
fontSize: 12,
color: 'var(--coz-fg-secondary)',
}}
>
{I18n.t('code_node_language')} {item.label}
</span>
)}
size={'small'}
optionList={optionList}
></Select>
</Tooltip>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<Button
color={'highlight'}
data-testid={concatTestId(setterTestId, 'test-run')}
icon={
testRunIcon ? (
<span
style={{
fontSize: 14,
display: 'flex',
alignItems: 'center',
}}
>
{testRunIcon}
</span>
) : (
<IconCozPlayCircle style={{ fontSize: 14 }} />
)
}
size={'small'}
onClick={onTestRun}
>
{I18n.t('code_node_test_code')}
</Button>
<IconButton
onClick={onClose}
color={'secondary'}
size={'small'}
icon={<IconCozSideCollapse style={{ fontSize: 18 }} />}
data-testid={concatTestId(setterTestId, 'expand-button')}
/>
</div>
</div>
{children}
</div>
);
};

View File

@@ -0,0 +1,48 @@
.container {
width: 100%;
height: 100%;
.header {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
min-width: 214px;
height: 48px;
margin-bottom: 0;
padding: 12px;
}
.title {
display: flex;
flex: 1;
gap: 8px;
align-items: center;
}
.title-icon {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
font-size: 18px;
color: #fff;
background-color: #28CAC8;
border-radius: 4px;
}
.title-content {
min-width: 0;
max-width: calc(100% - 160px);
font-size: 16px;
font-weight: 500;
color: var(--coz-fg-primary);
white-space: nowrap;
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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, { useEffect, useRef } from 'react';
import { type EditorAPI } from '../editor/preset';
import { Editor } from '../editor';
import { type PreviewerProps } from '../../interface';
export const Previewer = (props: PreviewerProps) => {
const apiRef = useRef<EditorAPI | null>();
useEffect(() => {
if (!apiRef.current) {
return;
}
if (props.content !== apiRef.current.getValue()) {
apiRef.current.forceSetValue(props.content);
}
}, [props.content]);
return (
<Editor
uuid={`previewer_${new Date().getTime()}`}
height={`${props.height}px`}
defaultLanguage={props.language}
defaultContent={props.content}
readonly
didMount={api => {
apiRef.current = api;
}}
/>
);
};

View File

@@ -0,0 +1,60 @@
/*
* 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 { createTheme } from '@coze-editor/editor/preset-code';
import { type Extension } from '@codemirror/state';
const colors = {
background: '#151B27',
};
export const createDarkTheme: () => Extension = () =>
createTheme({
variant: 'dark',
settings: {
background: colors.background,
foreground: '#fff',
caret: '#AEAFAD',
selection: '#d9d9d942',
gutterBackground: colors.background,
gutterForeground: '#FFFFFF63',
gutterBorderColor: 'transparent',
gutterBorderWidth: 0,
lineHighlight: '#272e3d36',
bracketColors: ['#FFEF61', '#DD99FF', '#78B0FF'],
tooltip: {
backgroundColor: '#363D4D',
color: '#fff',
border: 'none',
},
completionItemHover: {
backgroundColor: '#FFFFFF0F',
},
completionItemSelected: {
backgroundColor: '#FFFFFF17',
},
completionItemIcon: {
color: '#FFFFFFC9',
},
completionItemLabel: {
color: '#FFFFFFC9',
},
completionItemDetail: {
color: '#FFFFFF63',
},
},
styles: [],
});

View File

@@ -0,0 +1,33 @@
/*
* 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 { themes } from '@coze-editor/editor/preset-code';
import { convertSchema } from './utils';
import { type PreviewerProps, type EditorProps } from './interface';
import { createDarkTheme } from './components/theme';
import { Previewer } from './components/previewer';
import { BizEditor as Editor } from './components/editor';
themes.register('code-editor-dark', createDarkTheme());
export {
Previewer,
Editor,
type PreviewerProps,
type EditorProps,
convertSchema,
};

View File

@@ -0,0 +1,93 @@
/*
* 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 { type ReactNode } from 'react';
import type {
ViewVariableTreeNode,
ViewVariableType,
} from '@coze-workflow/base';
import { type EditorAPI } from './components/editor/preset';
export interface Input {
name?: string;
type?: ViewVariableType;
children?: ViewVariableTreeNode[];
}
export interface Output {
name?: string;
type?: ViewVariableType;
children?: Output[];
}
// javascript 为历史数据,目前只会有 python typescript
export type LanguageType = 'python' | 'typescript' | 'javascript';
export interface PreviewerProps {
content: string;
language: LanguageType;
height?: number;
}
export interface EditorProps {
defaultContent?: string;
uuid: string;
defaultLanguage: LanguageType;
spaceId?: string;
height?: string;
width?: string;
title?: string;
readonly?: boolean;
input?: Input[];
output?: Output[];
region?: string;
locale?: string;
onClose?: () => void;
onChange?: (code: string, language: LanguageType) => void;
languageTemplates?: Array<{
language: 'typescript' | 'python';
displayName: string;
template: string;
}>;
onTestRun?: () => void;
testRunIcon?: ReactNode;
/**
* @deprecated onTestRunStateChange 已失效,线上也未使用到
*/
onTestRunStateChange?: (status: string) => void;
}
export interface EditorOtherProps {
didMount?: (api: EditorAPI) => void;
language?: LanguageType;
}
export enum ModuleDetectionKind {
/**
* Files with imports, exports and/or import.meta are considered modules
*/
Legacy = 1,
/**
* Legacy, but also files with jsx under react-jsx or react-jsxdev and esm mode files under moduleResolution: node16+
*/
Auto = 2,
/**
* Consider all non-declaration files modules, regardless of present syntax
*/
Force = 3,
}

View File

@@ -0,0 +1,18 @@
/*
* 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 const IS_OVERSEA: boolean;

View File

@@ -0,0 +1,122 @@
/*
* 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 ParamSchemaType {
name: string;
type: number;
children?: ParamSchemaType[];
}
export function convertSchema(object, maxDepth = 20, currentDepth = 1) {
if (currentDepth > maxDepth) {
throw new Error('Max depth exceeded');
}
const paramSchema: ParamSchemaType[] = [];
Object.keys(object).forEach(key => {
const value = object[key];
switch (typeof value) {
case 'string':
paramSchema.push({
name: key,
type: 1 /* String */,
});
break;
case 'number':
if (Number.isInteger(value)) {
paramSchema.push({
name: key,
type: 2 /* Integer */,
});
} else {
paramSchema.push({
name: key,
type: 4 /* Number */,
});
}
break;
case 'boolean':
paramSchema.push({
name: key,
type: 3 /* Boolean */,
});
break;
case 'object':
if (value === null) {
break;
}
if (Array.isArray(value)) {
if (value.length > 0) {
switch (typeof value[0]) {
case 'string':
paramSchema.push({
name: key,
type: 99 /* ArrayString */,
});
break;
case 'number':
if (Number.isInteger(value[0])) {
paramSchema.push({
name: key,
type: 100 /* ArrayInteger */,
});
} else {
paramSchema.push({
name: key,
type: 102 /* ArrayNumber */,
});
}
break;
case 'boolean':
paramSchema.push({
name: key,
type: 101 /* ArrayBoolean */,
});
break;
case 'object':
paramSchema.push({
name: key,
type: 103 /* ArrayObject */,
children: convertSchema(value[0], maxDepth, currentDepth + 1),
});
break;
default:
paramSchema.push({
name: key,
type: 99 /* ArrayString */,
});
}
} else {
paramSchema.push({
name: key,
type: 99 /* ArrayString */,
});
}
} else {
paramSchema.push({
name: key,
type: 6 /* Object */,
children: convertSchema(value, maxDepth, currentDepth + 1),
});
}
break;
default:
console.log('value,to default', value);
throw new Error('ContainsInvalidValue');
}
});
return paramSchema;
}

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 { convertSchema } from './convert-schema';

View File

@@ -0,0 +1,37 @@
/*
* 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 { DemoComponent } from '../src';
export default {
title: 'Example/Demo',
component: DemoComponent,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {},
};
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Base = {
args: {
name: 'tecvan',
},
};

View File

@@ -0,0 +1,34 @@
import { Meta } from "@storybook/blocks";
<Meta title="Hello world" />
<div className="sb-container">
<div className='sb-section-title'>
# Hello world
Hello world
</div>
</div>
<style>
{`
.sb-container {
margin-bottom: 48px;
}
.sb-section {
width: 100%;
display: flex;
flex-direction: row;
gap: 20px;
}
img {
object-fit: cover;
}
.sb-section-title {
margin-bottom: 32px;
}
`}
</style>

View File

@@ -0,0 +1,39 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"jsx": "react-jsx",
"lib": ["DOM", "ESNext"],
"module": "ESNext",
"target": "ES2020",
"moduleResolution": "bundler",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"exclude": ["node_modules", "dist"],
"references": [
{
"path": "../../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../../arch/i18n/tsconfig.build.json"
},
{
"path": "../../base/tsconfig.build.json"
},
{
"path": "../../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../../config/stylelint-config/tsconfig.build.json"
},
{
"path": "../../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../../config/vitest-config/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"exclude": ["**/*"],
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
]
}

View File

@@ -0,0 +1,20 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"jsx": "react-jsx",
"lib": ["DOM", "ESNext"],
"module": "ESNext",
"target": "ES2020",
"moduleResolution": "bundler"
},
"include": ["__tests__", "vitest.config.ts", "stories"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,22 @@
/*
* 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 { defineConfig } from '@coze-arch/vitest-config';
export default defineConfig({
dirname: __dirname,
preset: 'web',
});