feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user