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,84 @@
.content {
overflow: hidden;
width: 276px;
.title {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 16px;
font-size: 18px;
font-weight: 600;
line-height: 24px;
color: rgb(6 7 9 / 80%);
}
.description {
margin-bottom: 16px;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: rgb(6 7 9 / 50%);
}
.table {
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: rgb(6 7 9 / 80%);
}
.icon {
font-size: 16px;
color: #060709;
}
}
.row {
&:last-child {
* {
border-bottom: none !important;
}
}
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
}
.icon-cell {
width: 24px;
height: 24px;
padding: 0 !important;
}
.cell-column {
width: 24px !important;
min-width: none !important;
}
.mark-column {
width: 68px;
min-width: none !important;
padding-right: 0 !important;
/* stylelint-disable-next-line declaration-no-important */
padding-left: 0 !important
}
.example-column {
/* stylelint-disable-next-line declaration-no-important */
padding-right: 0 !important;
padding-left: 0 !important;
}

View File

@@ -0,0 +1,209 @@
/*
* 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.
*/
/* eslint-disable @coze-arch/no-deep-relative-import -- 纯 ui 渲染 */
import { type PropsWithChildren, type ComponentProps } from 'react';
import { I18n } from '@coze-arch/i18n';
import { IconButton, Popover, Table } from '@coze-arch/bot-semi';
import { IconCloseNoCycle } from '@coze-arch/bot-icons';
import { ReactComponent as Strikethrough } from '../../../../assets/markdown-icon/strikethrough.svg';
import { ReactComponent as Quote } from '../../../../assets/markdown-icon/quote.svg';
import { ReactComponent as NumberedList } from '../../../../assets/markdown-icon/numbered-list.svg';
import { ReactComponent as Italic } from '../../../../assets/markdown-icon/italic.svg';
import { ReactComponent as H3 } from '../../../../assets/markdown-icon/h3.svg';
import { ReactComponent as H2 } from '../../../../assets/markdown-icon/h2.svg';
import { ReactComponent as H1 } from '../../../../assets/markdown-icon/h1.svg';
import { ReactComponent as Code } from '../../../../assets/markdown-icon/code.svg';
import { ReactComponent as CodeBlock } from '../../../../assets/markdown-icon/code-block.svg';
import { ReactComponent as BulletedList } from '../../../../assets/markdown-icon/bulleted-list.svg';
import { ReactComponent as Bold } from '../../../../assets/markdown-icon/bold.svg';
import styles from './index.module.less';
export type MarkdownDescriptionPopoverProps = Pick<
ComponentProps<typeof Popover>,
'visible' | 'onVisibleChange'
>;
interface MarkdownDescription {
mark: string;
example: string;
iconKey: string;
}
type TableProps = ComponentProps<typeof Table>;
const IconMap: Record<
string,
React.FunctionComponent<React.SVGProps<SVGSVGElement>>
> = {
h1: props => <H1 {...props} />,
h2: props => <H2 {...props} />,
h3: props => <H3 {...props} />,
bold: props => <Bold {...props} />,
italic: props => <Italic {...props} />,
strikethrough: props => <Strikethrough {...props} />,
quote: props => <Quote {...props} />,
code: props => <Code {...props} />,
codeBlock: props => <CodeBlock {...props} />,
numberedList: props => <NumberedList {...props} />,
bulletedList: props => <BulletedList {...props} />,
};
const columns: Required<TableProps>['columns'] = [
{
title: '',
dataIndex: 'icon',
className: styles['cell-column'],
onCell: () => ({
className: styles['icon-cell'],
}),
render: (_, record: MarkdownDescription) => {
const IconComponent = IconMap[record.iconKey];
if (!IconComponent) {
return null;
}
return (
<div className={styles['icon-wrapper']}>
<IconComponent className={styles.icon} />
</div>
);
},
},
{
title: '',
dataIndex: 'mark',
className: styles['mark-column'],
align: 'left',
},
{
title: '',
dataIndex: 'example',
align: 'right',
className: styles['example-column'],
},
];
const getData: () => MarkdownDescription[] = () => [
{
mark: I18n.t('markdown_heading1'),
example: I18n.t('markdown_heading1_syntax', { space: I18n.t('space') }),
iconKey: 'h1',
},
{
mark: I18n.t('markdown_heading2'),
example: I18n.t('markdown_heading2_syntax', { space: I18n.t('space') }),
iconKey: 'h2',
},
{
mark: I18n.t('markdown_heading3'),
example: I18n.t('markdown_heading3_syntax', { space: I18n.t('space') }),
iconKey: 'h3',
},
{
mark: I18n.t('markdown_bold'),
example: I18n.t('markdown_bold_syntax', {
space: I18n.t('space'),
text: I18n.t('text'),
}),
iconKey: 'bold',
},
{
mark: I18n.t('markdown_italic'),
example: I18n.t('markdown_italic_syntax', {
space: I18n.t('space'),
text: I18n.t('text'),
}),
iconKey: 'italic',
},
{
mark: I18n.t('markdown_strickthrough'),
example: I18n.t('markdown_strickthrough_syntax', {
space: I18n.t('space'),
text: I18n.t('text'),
}),
iconKey: 'strikethrough',
},
{
mark: I18n.t('markdown_quote'),
example: I18n.t('markdown_quote_syntax', { space: I18n.t('space') }),
iconKey: 'quote',
},
{
mark: I18n.t('markdown_code'),
example: I18n.t('markdown_code_syntax', {
space: I18n.t('space'),
code: I18n.t('code'),
}),
iconKey: 'code',
},
{
mark: I18n.t('markdown_codeblock'),
example: I18n.t('markdown_codeblock_syntax', {
space: I18n.t('space'),
}),
iconKey: 'codeBlock',
},
{
mark: I18n.t('markdown_numberedlist'),
example: I18n.t('markdown_numberedlist_syntax', { space: I18n.t('space') }),
iconKey: 'numberedList',
},
{
mark: I18n.t('markdown_bulletedlist'),
example: I18n.t('markdown_bulletedlist_syntax', { space: I18n.t('space') }),
iconKey: 'bulletedList',
},
];
export const MarkdownDescriptionPopover: React.FC<
PropsWithChildren<MarkdownDescriptionPopoverProps>
> = ({ children, visible, onVisibleChange }) => (
<Popover
trigger="custom"
visible={visible}
showArrow
position="rightTop"
content={
<div className={styles.content}>
<div className={styles.title}>
<div>Markdown</div>
<IconButton
size="small"
theme="borderless"
icon={<IconCloseNoCycle />}
onClick={() => onVisibleChange?.(false)}
/>
</div>
<div className={styles.description}>{I18n.t('markdown_intro')}</div>
<Table
className={styles.table}
showHeader={false}
columns={columns}
dataSource={getData()}
pagination={false}
size="small"
onRow={() => ({
className: styles.row,
})}
/>
</div>
}
>
{children}
</Popover>
);

View File

@@ -0,0 +1,43 @@
/*
* 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 SuggestQuestionMessage } from '@coze-studio/bot-detail-store';
import { type BotEditorOnboardingSuggestion } from '@coze-agent-ide/bot-editor-context-store';
import { OnboardingSuggestion } from '../../../onboarding-suggestion';
export interface OnboardingSuggestionContentProps {
onSuggestionChange: (param: SuggestQuestionMessage) => void;
onDeleteSuggestion: (id: string) => void;
onboardingSuggestions: BotEditorOnboardingSuggestion[];
}
export const OnboardingSuggestionContent: React.FC<
OnboardingSuggestionContentProps
> = ({ onDeleteSuggestion, onSuggestionChange, onboardingSuggestions }) => (
<>
{onboardingSuggestions.map(({ id, content, highlight }) => (
<OnboardingSuggestion
key={id}
id={id}
value={content}
onChange={(changedId, value) => {
onSuggestionChange({ id: changedId, content: value, highlight });
}}
onDelete={onDeleteSuggestion}
/>
))}
</>
);

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 const ONBOARDING_PREVIEW_DELAY = 500;

View File

@@ -0,0 +1,88 @@
.onboarding-markdown {
display: flex;
width: 100%;
height: 100%;
.edit-content {
overflow-y: auto;
flex-shrink: 0;
padding: 16px 24px;
}
.preview-content {
overflow-y: auto;
display: flex;
flex: 1;
align-items: center;
padding: 24px;
background-color: #FFF;
border-left: 1px solid rgb(6 7 9 / 10%);
}
.preview-content-scroll {
align-items: flex-start;
}
}
.opening-text {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 24px;
margin-bottom: 8px;
}
.opening-question {
display: flex;
column-gap: 8px;
align-items: center;
margin-bottom: 8px;
}
.strong-text {
font-size: 14px;
font-weight: 600;
line-height: 20px;
color: rgb(6 7 9 / 96%);
}
.markdown-editor {
box-sizing: border-box;
width: 590px;
height: 50%;
min-height: 200px;
margin-bottom: 24px;
}
.modal {
:global(.semi-modal-content) {
padding: 0;
:global(.semi-modal-header) {
margin: 0;
padding: 24px;
border-bottom: 1px solid rgb(6 7 9 / 10%);
}
:global(.semi-modal-body) {
padding: 0;
}
}
}
.markdown-tag {
cursor: pointer;
height: 24px;
color: #4E40E5;
background: rgba(128, 138, 255, 20%);
}
.modal-icon {
width: 16px;
height: 16px;
}

View File

@@ -0,0 +1,174 @@
/*
* 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 { useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import { useDebounce } from 'ahooks';
import { I18n } from '@coze-arch/i18n';
import { Tag, Tooltip, UIModal, type UIModalProps } from '@coze-arch/bot-semi';
import { IconInfo } from '@coze-arch/bot-icons';
import { type BotEditorOnboardingSuggestion } from '@coze-agent-ide/bot-editor-context-store';
import {
OnboardingPreview,
type OnboardingPreviewProps,
} from '../onboarding-preview';
import { MarkdownEditor, type MarkdownEditorProps } from '../markdown-editor';
import { ReactComponent as IconMinimizeOutlined } from '../../assets/icon_minimize_outlined.svg';
import { ONBOARDING_PREVIEW_DELAY } from './constant';
import {
OnboardingSuggestionContent,
type OnboardingSuggestionContentProps,
} from './components/onboarding-suggestion-content';
import { MarkdownDescriptionPopover } from './components/markdown-description-popover';
import styles from './index.module.less';
export interface OnboardingMarkdownModalProps
extends UIModalProps,
OnboardingSuggestionContentProps,
Pick<
MarkdownEditorProps,
'getValueLength' | 'maxLength' | 'getSlicedTextOnExceed'
> {
getBotInfo: OnboardingPreviewProps['getBotInfo'];
getUserInfo: () => {
userName: string;
userId: string;
};
prologue: string;
onPrologueChange: (prologue: string) => void;
/**
* 要预览的和能编辑的可能不一样
*/
previewSuggestions?: BotEditorOnboardingSuggestion[];
}
export const OnboardingMarkdownModal: React.FC<
OnboardingMarkdownModalProps
> = ({
getBotInfo,
getUserInfo,
onDeleteSuggestion,
onPrologueChange,
onSuggestionChange,
onboardingSuggestions,
previewSuggestions = onboardingSuggestions,
prologue,
getValueLength,
maxLength,
getSlicedTextOnExceed,
...modalProps
}) => {
const [visible, setVisible] = useState(false);
const [previewScrollable, setScrollable] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const onShowPopover = () => setVisible(true);
const debouncedPrologue = useDebounce(prologue, {
wait: ONBOARDING_PREVIEW_DELAY,
});
useEffect(() => {
if (!ref.current) {
return;
}
const { scrollHeight, clientHeight } = ref.current;
setScrollable(scrollHeight > clientHeight);
}, [debouncedPrologue, onboardingSuggestions, modalProps.visible]);
return (
<UIModal
{...modalProps}
title={I18n.t('bot_preview_opening_remarks')}
centered
footer={null}
type="base-composition"
className={styles.modal}
closeIcon={
<Tooltip content={I18n.t('collapse')}>
<span className={styles['modal-icon']}>
<IconMinimizeOutlined className={styles['modal-icon']} />
</span>
</Tooltip>
}
>
<div className={styles['onboarding-markdown']}>
<div className={styles['edit-content']}>
<div className={styles['opening-text']}>
<div className={styles['strong-text']}>
{I18n.t('bot_edit_opening_text_title')}
</div>
<MarkdownDescriptionPopover
visible={visible}
onVisibleChange={setVisible}
>
<Tag
onClick={onShowPopover}
className={styles['markdown-tag']}
color="indigo"
>
<IconInfo style={{ marginRight: 4 }} />
{I18n.t('markdown_is_supported')}
</Tag>
</MarkdownDescriptionPopover>
</div>
<MarkdownEditor
className={styles['markdown-editor']}
getUserId={() => getUserInfo().userId}
value={prologue}
onChange={onPrologueChange}
getValueLength={getValueLength}
maxLength={maxLength}
getSlicedTextOnExceed={getSlicedTextOnExceed}
/>
<div
className={classNames(
styles['opening-question'],
styles['strong-text'],
)}
>
{I18n.t('bot_edit_opening_question_title')}
<Tooltip content={I18n.t('bot_edit_opening_questions_tooltip')}>
<IconInfo />
</Tooltip>
</div>
<OnboardingSuggestionContent
onDeleteSuggestion={onDeleteSuggestion}
onSuggestionChange={onSuggestionChange}
onboardingSuggestions={onboardingSuggestions}
/>
</div>
<div
className={classNames(
styles['preview-content'],
previewScrollable && styles['preview-content-scroll'],
)}
ref={ref}
>
<OnboardingPreview
content={debouncedPrologue}
suggestions={previewSuggestions}
getBotInfo={getBotInfo}
getUserName={() => getUserInfo().userName}
/>
</div>
</div>
</UIModal>
);
};