feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
.related-entities-option {
|
||||
cursor: pointer;
|
||||
|
||||
margin-bottom: 2px;
|
||||
padding: 0;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
background-color: var(--coz-mg-secondary-hovered, rgba(87, 104, 161, 8%));
|
||||
}
|
||||
}
|
||||
|
||||
.related-entities-option-disabled {
|
||||
cursor: not-allowed;
|
||||
color: var(--Fg-COZ-fg-dim, rgba(55, 67, 106, 38%));
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.related-entities-option-selected {
|
||||
background-color: var(--coz-mg-primary);
|
||||
}
|
||||
|
||||
.bot-foot-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
color: #4D53E8;
|
||||
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
.empty-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.text {
|
||||
margin-top: 10px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgba(52, 60, 87, 72%);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.bot-panel-option {
|
||||
margin-bottom: 2px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.variable-panel-option {
|
||||
margin-bottom: 2px;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:global(.semi-select-option-selected) {
|
||||
background-color: var(--coz-mg-primary);
|
||||
}
|
||||
|
||||
&:global(.semi-select-option):hover {
|
||||
background-color: var(--coz-mg-secondary-hovered, rgba(87, 104, 161, 8%));
|
||||
}
|
||||
}
|
||||
|
||||
.variable-option-checked {
|
||||
font-weight: 500;
|
||||
color: rgba(81, 71, 255, 100%);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { EmptyVariableContent } from './variable-panel/variable-panel';
|
||||
import type { BotProjectVariableSelectProps } from './types';
|
||||
import BotProjectVariableSelect from './select';
|
||||
|
||||
export {
|
||||
BotProjectVariableSelect,
|
||||
EmptyVariableContent,
|
||||
BotProjectVariableSelectProps,
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozEmpty } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import s from '../index.module.less';
|
||||
|
||||
export default function EmptyContent() {
|
||||
return (
|
||||
<div className={s['empty-block']}>
|
||||
<IconCozEmpty
|
||||
style={{ fontSize: '32px', color: 'rgba(52, 60, 87, 0.72)' }}
|
||||
/>
|
||||
<span className={s.text}>
|
||||
{I18n.t(
|
||||
'variable_binding_there_are_no_variables_in_this_project',
|
||||
{},
|
||||
'该智能体下暂时没有定义变量',
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 cls from 'classnames';
|
||||
import { IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconCozArrowRight,
|
||||
IconCozCheckMarkFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Avatar, Tag, Typography } from '@coze-arch/coze-design';
|
||||
import { useHover } from '@coze-arch/hooks';
|
||||
|
||||
import type { IBotSelectOption } from '@/components/bot-project-select/types';
|
||||
|
||||
interface OptionItemProps extends IBotSelectOption {
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function OptionItem({
|
||||
disabled,
|
||||
checked,
|
||||
avatar,
|
||||
name,
|
||||
type,
|
||||
}: OptionItemProps) {
|
||||
const [ref, isHover] = useHover<HTMLDivElement>();
|
||||
|
||||
const renderOperate = () => {
|
||||
if (isHover && !disabled) {
|
||||
return (
|
||||
<div className={'flex items-center coz-fg-secondary flex-shrink-0'}>
|
||||
<span className={'text-[12px]'}>
|
||||
{I18n.t('variable_binding_continue', {}, '继续')}
|
||||
</span>
|
||||
<IconCozArrowRight className="text-[12px] ml-2px" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return type === IntelligenceType.Project ? (
|
||||
<Tag size="mini" color="primary" className={'flex-shrink-0'}>
|
||||
{I18n.t('wf_chatflow_106')}
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag size="mini" color="primary" className={'flex-shrink-0'}>
|
||||
{I18n.t('wf_chatflow_107')}
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cls('flex w-full items-center pl-8px pr-8px pt-2px pb-2px')}
|
||||
>
|
||||
{checked ? (
|
||||
<IconCozCheckMarkFill className="text-[16px] mr-8px coz-fg-hglt flex-shrink-0" />
|
||||
) : (
|
||||
<div className={'w-16px h-16px mr-8px flex-shrink-0'} />
|
||||
)}
|
||||
|
||||
<Avatar
|
||||
style={{ flexShrink: 0, marginRight: 8, width: 16, height: 16 }}
|
||||
shape="square"
|
||||
src={avatar}
|
||||
/>
|
||||
<div
|
||||
className="flex"
|
||||
style={{ flexGrow: 1, flexShrink: 1, overflow: 'hidden' }}
|
||||
>
|
||||
<Typography.Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: '#1D1C23',
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
{renderOperate()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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 cls from 'classnames';
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { GlobalVariableService } from '@coze-workflow/variable';
|
||||
import { IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { useRelatedBotService } from '@/hooks';
|
||||
|
||||
import { type RelatedEntitiesProps } from '../types';
|
||||
import s from '../index.module.less';
|
||||
import { isItemDisabled } from '../../utils';
|
||||
import type { IBotSelectOption, DisableExtraOptions } from '../../types';
|
||||
import { RenderFootLoading } from '../../bots';
|
||||
import OptionItem from './option-item';
|
||||
import EmptyContent from './empty-content';
|
||||
|
||||
interface Options extends DisableExtraOptions {
|
||||
onClick: () => void;
|
||||
checkedValue?: string;
|
||||
}
|
||||
|
||||
const renderCustomOption = (
|
||||
item: IBotSelectOption | undefined,
|
||||
extraOptions: Options,
|
||||
) => {
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
disableBot,
|
||||
disableProject,
|
||||
disableBotTooltip,
|
||||
disableProjectTooltip,
|
||||
onClick,
|
||||
checkedValue,
|
||||
} = extraOptions;
|
||||
|
||||
const isBot = item.type === IntelligenceType.Bot;
|
||||
const disabled = isItemDisabled({ disableBot, disableProject }, item.type);
|
||||
|
||||
const disabledTooltip =
|
||||
isBot && disableBot ? disableBotTooltip : disableProjectTooltip;
|
||||
|
||||
const handleClick = () => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
onClick?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className={cls(s['related-entities-option'], {
|
||||
[s['related-entities-option-disabled']]: disabled,
|
||||
[s['related-entities-option-selected']]: checkedValue === item.value,
|
||||
})}
|
||||
>
|
||||
{disabled ? (
|
||||
<Tooltip
|
||||
keepDOM={true}
|
||||
content={disabledTooltip}
|
||||
position="left"
|
||||
className={'w-full'}
|
||||
>
|
||||
<div className={'w-full'}>
|
||||
<OptionItem
|
||||
{...item}
|
||||
disabled={disabled}
|
||||
checked={checkedValue === item.value}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<OptionItem
|
||||
{...item}
|
||||
disabled={disabled}
|
||||
checked={checkedValue === item.value}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Panel({
|
||||
relatedEntities = [],
|
||||
relatedEntityValue,
|
||||
disableProjectTooltip,
|
||||
disableProject,
|
||||
disableBotTooltip,
|
||||
disableBot,
|
||||
isLoadMore,
|
||||
onLoadMore,
|
||||
onRelatedSelect,
|
||||
relatedBotPanelStyle,
|
||||
}: RelatedEntitiesProps) {
|
||||
const relatedBotService = useRelatedBotService();
|
||||
|
||||
const globalVariableService = useService<GlobalVariableService>(
|
||||
GlobalVariableService,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{relatedEntities && relatedEntities?.length > 0 ? (
|
||||
<div
|
||||
className={
|
||||
'coz-fg-secondary mt-8px mb-4px pl-28px text-[12px] font-medium leading-16px'
|
||||
}
|
||||
>
|
||||
{I18n.t(
|
||||
'variable_binding_please_bind_an_agent_or_app_first',
|
||||
{},
|
||||
'请先绑定智能体或应用',
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={'h-[292px] overflow-y-auto'} style={relatedBotPanelStyle}>
|
||||
{relatedEntities?.map(item =>
|
||||
renderCustomOption(item, {
|
||||
disableProjectTooltip,
|
||||
disableProject,
|
||||
disableBotTooltip,
|
||||
disableBot,
|
||||
onClick: () => {
|
||||
relatedBotService.updateRelatedBot({
|
||||
id: item.value,
|
||||
type: item.type === IntelligenceType.Bot ? 'bot' : 'project',
|
||||
});
|
||||
|
||||
globalVariableService.loadGlobalVariables(
|
||||
item.type === 1 ? 'bot' : 'project',
|
||||
item.value,
|
||||
);
|
||||
|
||||
onRelatedSelect?.(item);
|
||||
},
|
||||
checkedValue: relatedEntityValue?.id,
|
||||
}),
|
||||
)}
|
||||
|
||||
{isLoadMore ? (
|
||||
<div className={s['bot-foot-loading']}>
|
||||
<RenderFootLoading onObserver={onLoadMore} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!relatedEntities || relatedEntities?.length <= 0 ? (
|
||||
<EmptyContent />
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
IntelligenceStatus,
|
||||
IntelligenceType,
|
||||
} from '@coze-arch/idl/intelligence_api';
|
||||
import { intelligenceApi } from '@coze-arch/bot-api';
|
||||
|
||||
interface QueryProps {
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export default function useQueryBotList({ spaceId }: QueryProps) {
|
||||
const { data } = useQuery({
|
||||
queryKey: ['related-bot-panel', 'GetDraftIntelligenceList', spaceId],
|
||||
queryFn: async () => {
|
||||
const res = await intelligenceApi.GetDraftIntelligenceList({
|
||||
space_id: spaceId,
|
||||
name: '',
|
||||
types: [IntelligenceType.Bot, IntelligenceType.Project],
|
||||
size: 30,
|
||||
order_by: 0,
|
||||
cursor_id: undefined,
|
||||
status: [
|
||||
IntelligenceStatus.Using,
|
||||
IntelligenceStatus.Banned,
|
||||
IntelligenceStatus.MoveFailed,
|
||||
],
|
||||
});
|
||||
|
||||
return res?.data ?? {};
|
||||
},
|
||||
retry: false,
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
import { PUBLIC_SPACE_ID } from '@coze-workflow/base';
|
||||
import {
|
||||
type DraftIntelligenceListData,
|
||||
IntelligenceStatus,
|
||||
IntelligenceType,
|
||||
} from '@coze-arch/idl/intelligence_api';
|
||||
import { intelligenceApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { useGlobalState } from '@/hooks';
|
||||
|
||||
import { type RelatedEntitiesHookProps } from '../types';
|
||||
import { useExtraBotOption } from '../../use-extra-bot-option';
|
||||
import { type IBotSelectOption, type IBotSelectOptions } from '../../types';
|
||||
import useQueryBotList from './use-query-bot-list';
|
||||
|
||||
export default function useRelated({
|
||||
relatedEntityValue,
|
||||
}: RelatedEntitiesHookProps) {
|
||||
const [baseRelatedEntities, setBaseRelatedEntities] = useState<
|
||||
IBotSelectOption[]
|
||||
>([]);
|
||||
const [nextCursorId, setNextCursorId] = useState<string | undefined>();
|
||||
const [search, setSearch] = useState<string>('');
|
||||
const [isLoadMore, setIsLoadMore] = useState<boolean>(false);
|
||||
const isLoadMoreDate = useRef(false);
|
||||
|
||||
const { spaceId, personalSpaceId } = useGlobalState();
|
||||
|
||||
const querySpaceId = spaceId === PUBLIC_SPACE_ID ? personalSpaceId : spaceId;
|
||||
|
||||
const defaultBotData = useQueryBotList({
|
||||
spaceId: querySpaceId,
|
||||
});
|
||||
|
||||
const fetchCallback = (
|
||||
fetchBotData?: DraftIntelligenceListData,
|
||||
isReset = false,
|
||||
) => {
|
||||
const { intelligences, total = 0, next_cursor_id } = fetchBotData ?? {};
|
||||
|
||||
const list: IBotSelectOptions = (intelligences ?? []).map(it => ({
|
||||
name: it.basic_info?.name ?? '',
|
||||
value: it.basic_info?.id ?? '',
|
||||
avatar: it.basic_info?.icon_url ?? '',
|
||||
type: it.type || IntelligenceType.Bot,
|
||||
}));
|
||||
|
||||
const totalList = isReset ? list : [...baseRelatedEntities, ...list];
|
||||
|
||||
setNextCursorId(next_cursor_id);
|
||||
setBaseRelatedEntities(totalList);
|
||||
|
||||
setIsLoadMore(totalList.length < total);
|
||||
};
|
||||
|
||||
const fetchBotList = async (query?: string, isReset = false) => {
|
||||
const res = await intelligenceApi.GetDraftIntelligenceList({
|
||||
space_id: querySpaceId,
|
||||
name: query ?? search,
|
||||
types: [IntelligenceType.Bot, IntelligenceType.Project],
|
||||
size: 30,
|
||||
order_by: 0,
|
||||
cursor_id: nextCursorId,
|
||||
status: [
|
||||
IntelligenceStatus.Using,
|
||||
IntelligenceStatus.Banned,
|
||||
IntelligenceStatus.MoveFailed,
|
||||
],
|
||||
});
|
||||
|
||||
fetchCallback(res?.data, isReset);
|
||||
};
|
||||
|
||||
const onRelatedEntitiesSearch = useCallback((query: string) => {
|
||||
setSearch(query);
|
||||
setNextCursorId(undefined);
|
||||
fetchBotList(query, true);
|
||||
}, []);
|
||||
|
||||
const loadMore = async () => {
|
||||
if (isLoadMoreDate.current) {
|
||||
return;
|
||||
}
|
||||
isLoadMoreDate.current = true;
|
||||
await fetchBotList();
|
||||
isLoadMoreDate.current = false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchCallback(defaultBotData, true);
|
||||
}, [defaultBotData]);
|
||||
|
||||
const isBot = relatedEntityValue?.type === IntelligenceType.Bot;
|
||||
|
||||
// 由于分页限制 选中的 botId 可能找不到对应的 option 需要额外添加
|
||||
const extraBotOption = useExtraBotOption(
|
||||
baseRelatedEntities,
|
||||
relatedEntityValue?.id,
|
||||
isBot,
|
||||
);
|
||||
|
||||
const relatedEntities = useMemo<IBotSelectOption[]>(
|
||||
() => [extraBotOption, ...baseRelatedEntities].filter(e => !!e),
|
||||
[extraBotOption, baseRelatedEntities],
|
||||
);
|
||||
|
||||
return {
|
||||
relatedEntities,
|
||||
onRelatedEntitiesSearch: debounce(onRelatedEntitiesSearch, 300),
|
||||
loadMoreRelatedEntities: loadMore,
|
||||
isLoadMore,
|
||||
};
|
||||
}
|
||||
@@ -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 React, { useCallback, useRef, useState } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozMagnifier } from '@coze-arch/coze-design/icons';
|
||||
import { Avatar, Select } from '@coze-arch/coze-design';
|
||||
import { type Select as SemiSelect } from '@coze-arch/bot-semi';
|
||||
|
||||
import { useRelatedBotService } from '@/hooks';
|
||||
|
||||
import VariablePanel from './variable-panel/variable-panel';
|
||||
import { type BotProjectVariableSelectProps, type RelatedValue } from './types';
|
||||
import useRelated from './related-bot-panel/use-related';
|
||||
import RelatedBotPanel from './related-bot-panel/panel';
|
||||
|
||||
const formatRelatedBotValue = (value?: {
|
||||
id: string;
|
||||
type: 'bot' | 'project';
|
||||
}): RelatedValue | undefined => {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
id: value.id,
|
||||
type:
|
||||
value.type === 'bot' ? IntelligenceType.Bot : IntelligenceType.Project,
|
||||
};
|
||||
};
|
||||
|
||||
export default function BotProjectVariableSelect({
|
||||
className,
|
||||
variablesFormatter,
|
||||
relatedEntityValue: relatedEntityValueFromProps,
|
||||
disableBot,
|
||||
disableProject,
|
||||
disableBotTooltip,
|
||||
disableProjectTooltip,
|
||||
onVariableSelect,
|
||||
variableValue,
|
||||
variablePanelStyle,
|
||||
relatedBotPanelStyle,
|
||||
customVariablePanel,
|
||||
}: BotProjectVariableSelectProps) {
|
||||
const selectRef = useRef<SemiSelect | null>(null);
|
||||
const relatedBotService = useRelatedBotService();
|
||||
const getRelatedEntityValue = () =>
|
||||
relatedEntityValueFromProps ||
|
||||
formatRelatedBotValue(relatedBotService.getRelatedBotValue());
|
||||
const relatedEntityValue = getRelatedEntityValue();
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const [showBotPanel, setShowBotPanel] = useState(!relatedEntityValue?.id);
|
||||
|
||||
const {
|
||||
relatedEntities,
|
||||
onRelatedEntitiesSearch,
|
||||
isLoadMore,
|
||||
loadMoreRelatedEntities,
|
||||
} = useRelated({
|
||||
relatedEntityValue,
|
||||
});
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
setTimeout(() => {
|
||||
setShowBotPanel(true);
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
selectRef.current?.clearInput?.();
|
||||
selectRef.current?.close?.();
|
||||
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setShowBotPanel(!getRelatedEntityValue()?.id);
|
||||
}, 300);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={cls(className, 'w-full flex flex-col p-4px')}>
|
||||
<Select
|
||||
autoClearSearchValue={true}
|
||||
ref={selectRef}
|
||||
filter
|
||||
inputProps={{
|
||||
onBlur: handleBlur,
|
||||
onFocusCapture: handleFocus,
|
||||
}}
|
||||
value={relatedEntityValue?.id}
|
||||
className={'w-full'}
|
||||
size={'small'}
|
||||
placeholder={I18n.t(
|
||||
'variable_binding_search_project',
|
||||
{},
|
||||
'搜索智能体/应用',
|
||||
)}
|
||||
optionList={[]}
|
||||
onSearch={onRelatedEntitiesSearch}
|
||||
emptyContent={null}
|
||||
prefix={
|
||||
<IconCozMagnifier className={'text-[16px] ml-8px coz-fg-secondary'} />
|
||||
}
|
||||
showArrow={false}
|
||||
renderSelectedItem={() => {
|
||||
const item = relatedEntities.find(
|
||||
e => e.value === relatedEntityValue?.id,
|
||||
);
|
||||
if (item) {
|
||||
return (
|
||||
<div className={'flex items-center'}>
|
||||
<Avatar
|
||||
className={'w-16px h-16px mr-8px'}
|
||||
shape="square"
|
||||
src={item.avatar}
|
||||
/>
|
||||
<span className={'text-[12px]'}>{item.name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className={'w-full coz-fg-primary'}>
|
||||
{showBotPanel ? (
|
||||
<RelatedBotPanel
|
||||
relatedBotPanelStyle={relatedBotPanelStyle}
|
||||
relatedEntityValue={relatedEntityValue}
|
||||
relatedEntities={relatedEntities}
|
||||
onLoadMore={loadMoreRelatedEntities}
|
||||
isLoadMore={isLoadMore}
|
||||
disableBot={disableBot}
|
||||
disableProject={disableProject}
|
||||
disableBotTooltip={disableBotTooltip}
|
||||
disableProjectTooltip={disableProjectTooltip}
|
||||
onRelatedSelect={() => {
|
||||
setShowBotPanel(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
customVariablePanel || (
|
||||
<VariablePanel
|
||||
variablePanelStyle={variablePanelStyle}
|
||||
variableValue={variableValue}
|
||||
onVariableSelect={onVariableSelect}
|
||||
variablesFormatter={variablesFormatter}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 CSSProperties, type ReactNode } from 'react';
|
||||
|
||||
import { type IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
|
||||
import { type VariableMetaWithNode } from '@/form-extensions/typings';
|
||||
import type {
|
||||
DisableExtraOptions,
|
||||
IBotSelectOption,
|
||||
} from '@/components/bot-project-select/types';
|
||||
|
||||
export interface Variable extends VariableMetaWithNode {
|
||||
disabled?: boolean;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface RelatedVariablesHookProps {
|
||||
variablesFormatter?: (variables: Variable[]) => Variable[];
|
||||
}
|
||||
|
||||
export interface RelatedEntitiesHookProps {
|
||||
relatedEntityValue?: RelatedValue;
|
||||
}
|
||||
|
||||
export interface RelatedValue {
|
||||
id: string;
|
||||
type: IntelligenceType;
|
||||
}
|
||||
|
||||
export interface RelatedEntitiesProps extends DisableExtraOptions {
|
||||
onLoadMore: () => Promise<void>;
|
||||
isLoadMore: boolean;
|
||||
relatedEntities?: IBotSelectOption[];
|
||||
relatedEntityValue?: RelatedValue;
|
||||
onRelatedSelect?: (item: IBotSelectOption) => void;
|
||||
relatedBotPanelStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
export interface VariablesPanelProps {
|
||||
onVariableSelect?: (value?: string) => void;
|
||||
variableValue?: string;
|
||||
variablePanelStyle?: CSSProperties;
|
||||
variablesFormatter?: (variables: Variable[]) => Variable[];
|
||||
}
|
||||
|
||||
export interface BotProjectVariableSelectProps extends DisableExtraOptions {
|
||||
className?: string;
|
||||
onVariableSelect?: (value?: string) => void;
|
||||
variablesFormatter?: (variables: Variable[]) => Variable[];
|
||||
relatedEntityValue?: RelatedValue;
|
||||
variableValue?: string;
|
||||
variablePanelStyle?: CSSProperties;
|
||||
relatedBotPanelStyle?: CSSProperties;
|
||||
customVariablePanel?: ReactNode;
|
||||
}
|
||||
@@ -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 { useMemo } from 'react';
|
||||
|
||||
import { isGlobalVariableKey } from '@coze-workflow/variable';
|
||||
|
||||
import { useNodeAvailableVariablesWithNode } from '@/form-extensions/hooks';
|
||||
|
||||
import { type RelatedVariablesHookProps } from '../types';
|
||||
|
||||
export default function useRelatedVariable({
|
||||
variablesFormatter = v => v,
|
||||
}: RelatedVariablesHookProps) {
|
||||
const availableVariables = useNodeAvailableVariablesWithNode();
|
||||
|
||||
const globalVariables = useMemo(
|
||||
() =>
|
||||
variablesFormatter(
|
||||
availableVariables.filter(
|
||||
item => item.nodeId && isGlobalVariableKey(item.nodeId),
|
||||
),
|
||||
),
|
||||
[availableVariables, variablesFormatter],
|
||||
);
|
||||
|
||||
return {
|
||||
globalVariables,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { useGlobalVariableServiceState } from '@coze-workflow/variable';
|
||||
import { VARIABLE_TYPE_ALIAS_MAP } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozEmpty } from '@coze-arch/coze-design/icons';
|
||||
import { Select, Tag, Typography } from '@coze-arch/coze-design';
|
||||
|
||||
import { type Variable, type VariablesPanelProps } from '../types';
|
||||
import styles from '../index.module.less';
|
||||
import useRelatedVariable from './use-related-variable';
|
||||
|
||||
interface OptionProps extends Variable {
|
||||
checked?: boolean;
|
||||
onSelect?: (v?: string) => void;
|
||||
}
|
||||
|
||||
const OptionItem = (option: OptionProps) => (
|
||||
<Select.Option
|
||||
selected={option.checked}
|
||||
key={`${option.value}-${option.disabled}`}
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
className={styles['variable-panel-option']}
|
||||
onSelect={v => option.onSelect?.(v?.value)}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
}}
|
||||
className={
|
||||
'flex items-center justify-between pl-32px pr-8px pt-2px pb-2px'
|
||||
}
|
||||
>
|
||||
<Typography.Text
|
||||
className={cls('flex-1 leading-20px', {
|
||||
[styles['variable-option-checked']]: option.checked,
|
||||
})}
|
||||
disabled={option.disabled}
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{
|
||||
fontSize: 12,
|
||||
marginRight: 4,
|
||||
}}
|
||||
>
|
||||
{option.name}
|
||||
</Typography.Text>
|
||||
|
||||
<Tag disabled={option.disabled} size="mini" color="primary">
|
||||
{VARIABLE_TYPE_ALIAS_MAP[option.type]}
|
||||
</Tag>
|
||||
</div>
|
||||
</Select.Option>
|
||||
);
|
||||
|
||||
interface EmptyProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const EmptyVariableContent = ({ className }: EmptyProps) => {
|
||||
const { type } = useGlobalVariableServiceState();
|
||||
const isSelectedBotOrProject = Boolean(type);
|
||||
|
||||
return useMemo(() => {
|
||||
const getEmptyMsg = () => {
|
||||
if (isSelectedBotOrProject) {
|
||||
return I18n.t(
|
||||
'variable_binding_there_are_no_variables_in_this_project',
|
||||
);
|
||||
}
|
||||
|
||||
return I18n.t('variable_select_empty_library_tips');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cls(className, styles['empty-block'])}>
|
||||
<IconCozEmpty
|
||||
style={{ fontSize: '32px', color: 'rgba(52, 60, 87, 0.72)' }}
|
||||
/>
|
||||
<span className={styles.text}>{getEmptyMsg()}</span>
|
||||
</div>
|
||||
);
|
||||
}, [isSelectedBotOrProject]);
|
||||
};
|
||||
|
||||
export default function VariablePanel({
|
||||
onVariableSelect,
|
||||
variableValue,
|
||||
variablePanelStyle,
|
||||
variablesFormatter,
|
||||
}: VariablesPanelProps) {
|
||||
const { globalVariables } = useRelatedVariable({ variablesFormatter });
|
||||
|
||||
return (
|
||||
<>
|
||||
{globalVariables.length > 0 && (
|
||||
<div
|
||||
className={
|
||||
'coz-fg-secondary mt-8px mb-4px pl-28px text-[12px] font-medium leading-16px'
|
||||
}
|
||||
>
|
||||
{I18n.t(
|
||||
'variable_binding_please_select_a_variable',
|
||||
{},
|
||||
'请先选择变量',
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={'h-[292px] overflow-y-auto'} style={variablePanelStyle}>
|
||||
{globalVariables.map(e => (
|
||||
<OptionItem
|
||||
{...e}
|
||||
onSelect={onVariableSelect}
|
||||
checked={variableValue === e.value}
|
||||
/>
|
||||
))}
|
||||
|
||||
{globalVariables.length <= 0 && <EmptyVariableContent />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
.select-wrapper {
|
||||
position: relative;
|
||||
|
||||
:global {
|
||||
|
||||
// 修复bot-select在画布中因为缩放导致尺寸不正常
|
||||
.semi-portal-inner {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
width: 100%;
|
||||
min-height: 50px;
|
||||
|
||||
:global {
|
||||
.semi-select-option-list {
|
||||
&::-webkit-scrollbar {
|
||||
height: 0;
|
||||
min-height: 42px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.semi-select-loading-wrapper {
|
||||
min-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.semi-select-option-selected {
|
||||
.semi-icon-tick {
|
||||
color: var(--light-usage-primary-color-primary, #4d53e8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-foot-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
color: #4D53E8;
|
||||
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.bot-option {
|
||||
display: flex;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.bot-option-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.loading-tag {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* 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, useState, useRef, useMemo } from 'react';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { IconSearch } from '@douyinfe/semi-icons';
|
||||
import { PUBLIC_SPACE_ID } from '@coze-workflow/base/constants';
|
||||
import { concatTestId } from '@coze-workflow/base';
|
||||
import {
|
||||
IntelligenceStatus,
|
||||
IntelligenceType,
|
||||
} from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Typography, Spin, Avatar, Select } from '@coze-arch/bot-semi';
|
||||
import { intelligenceApi } from '@coze-arch/bot-api';
|
||||
import { Tag, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { ChatflowService } from '@/services';
|
||||
|
||||
import { useGlobalState, useRelatedBotService } from '../../hooks';
|
||||
import { isItemDisabled } from './utils';
|
||||
import { useExtraBotOption } from './use-extra-bot-option';
|
||||
import type {
|
||||
IBotSelectOption,
|
||||
IBotSelectOptions,
|
||||
ValueType,
|
||||
DisableExtraOptions,
|
||||
} from './types';
|
||||
|
||||
import styles from './bots.module.less';
|
||||
|
||||
const RenderCustomOption = (
|
||||
item: IBotSelectOption | undefined,
|
||||
extraOptions: DisableExtraOptions,
|
||||
) => {
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
disableBot,
|
||||
disableProject,
|
||||
disableBotTooltip,
|
||||
disableProjectTooltip,
|
||||
} = extraOptions;
|
||||
|
||||
const isBot = item.type === IntelligenceType.Bot;
|
||||
const disabled = isItemDisabled({ disableBot, disableProject }, item.type);
|
||||
|
||||
const disabledTooltip =
|
||||
isBot && disableBot ? disableBotTooltip : disableProjectTooltip;
|
||||
|
||||
const renderOptionItem = optionItem => (
|
||||
<div className="flex" style={{ width: '100%', alignItems: 'center' }}>
|
||||
<Avatar
|
||||
size="extra-extra-small"
|
||||
style={{ flexShrink: 0, marginRight: 8 }}
|
||||
shape="square"
|
||||
src={optionItem.avatar}
|
||||
/>
|
||||
<div className="flex" style={{ flexGrow: 1, flexShrink: 1, width: 0 }}>
|
||||
<Typography.Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: '#1D1C23',
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
{optionItem.name}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
{optionItem.type === IntelligenceType.Project ? (
|
||||
<Tag size="mini" color="primary">
|
||||
{I18n.t('wf_chatflow_106')}
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag size="mini" color="primary">
|
||||
{I18n.t('wf_chatflow_107')}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Select.Option
|
||||
data-testid={concatTestId(
|
||||
'workflow',
|
||||
'playground',
|
||||
'testrun',
|
||||
'bot-select',
|
||||
'option',
|
||||
item.value,
|
||||
)}
|
||||
value={item.value}
|
||||
showTick={true}
|
||||
key={item.value}
|
||||
disabled={disabled}
|
||||
className={classNames(
|
||||
styles['bot-option'],
|
||||
disabled ? styles['bot-option-disabled'] : '',
|
||||
)}
|
||||
>
|
||||
{disabled ? (
|
||||
<Tooltip content={disabledTooltip} position="left">
|
||||
{renderOptionItem(item)}
|
||||
</Tooltip>
|
||||
) : (
|
||||
renderOptionItem(item)
|
||||
)}
|
||||
</Select.Option>
|
||||
);
|
||||
};
|
||||
|
||||
export const RenderFootLoading = ({
|
||||
onObserver,
|
||||
}: {
|
||||
onObserver: () => Promise<void>;
|
||||
}) => {
|
||||
const indicatorRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const callback = entries => {
|
||||
if (entries[0].isIntersecting) {
|
||||
onObserver?.();
|
||||
}
|
||||
};
|
||||
const loadingObserver = new IntersectionObserver(callback);
|
||||
indicatorRef.current && loadingObserver.observe(indicatorRef.current);
|
||||
return () => loadingObserver.disconnect();
|
||||
}, []);
|
||||
return (
|
||||
<div className={styles['loading-tag']} ref={indicatorRef}>
|
||||
<Spin style={{ marginRight: 10 }} size="small" />
|
||||
<span>{I18n.t('workflow_add_common_loading')}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface BotsProps {
|
||||
isBot: boolean;
|
||||
value?: ValueType;
|
||||
onChange?: (value?: ValueType) => void;
|
||||
}
|
||||
|
||||
export const Bots: React.FC<BotsProps & DisableExtraOptions> = ({
|
||||
isBot,
|
||||
value,
|
||||
onChange,
|
||||
disableBot = false,
|
||||
disableProject = false,
|
||||
disableBotTooltip = '',
|
||||
disableProjectTooltip = '',
|
||||
...props
|
||||
}) => {
|
||||
const idValue = value?.id;
|
||||
const globalState = useGlobalState();
|
||||
const DebounceTime = 500;
|
||||
const chatflowService = useService<ChatflowService>(ChatflowService);
|
||||
const relatedBotService = useRelatedBotService();
|
||||
|
||||
const isLoadMoreDate = useRef(false);
|
||||
const [selectList = [], setSelectList] = useState<IBotSelectOptions>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isShowFoot, setIsShowFoot] = useState<boolean>(false);
|
||||
const [search, setSearch] = useState<string>('');
|
||||
const [searchTotal, setTotal] = useState(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const nextCursorRef = useRef<string | undefined>();
|
||||
|
||||
const useNewGlobalVariableCache = !globalState.isInIDE;
|
||||
|
||||
const handleChange = (botInfo?: IBotSelectOption, _value?: ValueType) => {
|
||||
chatflowService.setSelectItem(botInfo);
|
||||
onChange?.(_value);
|
||||
};
|
||||
|
||||
// 由于分页限制 选中的 botId 可能找不到对应的 option 需要额外添加
|
||||
const extraBotOption = useExtraBotOption(
|
||||
selectList,
|
||||
idValue,
|
||||
isBot,
|
||||
handleChange,
|
||||
);
|
||||
|
||||
// 接口得到的总数并非真实的总数,前端可能会拼接 options
|
||||
const listMaxHeight = useMemo(() => {
|
||||
const realTotal = extraBotOption ? searchTotal + 1 : searchTotal;
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
return realTotal < 7 ? realTotal * 32 : 208;
|
||||
}, [searchTotal, extraBotOption]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchBotList();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const findItem = selectList?.find(item => item.value === value?.id);
|
||||
const disabled = isItemDisabled(
|
||||
{ disableBot, disableProject },
|
||||
findItem?.type,
|
||||
);
|
||||
// 禁用状态下清空选中值
|
||||
if (value && findItem?.type && disabled) {
|
||||
onChange?.(undefined);
|
||||
}
|
||||
if (value?.id !== chatflowService.selectItem?.value) {
|
||||
if (!value) {
|
||||
chatflowService.setSelectItem(undefined);
|
||||
return;
|
||||
}
|
||||
if (findItem) {
|
||||
chatflowService.setSelectItem(findItem);
|
||||
}
|
||||
}
|
||||
}, [value, selectList]);
|
||||
|
||||
const fetchBotList = async (query?: string, isReset = false) => {
|
||||
if (query) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
if (isReset) {
|
||||
// 如果是重新搜索,需要清空上一次的游标
|
||||
nextCursorRef.current = undefined;
|
||||
}
|
||||
|
||||
// project 内部使用新接口查询列表
|
||||
const res = await intelligenceApi.GetDraftIntelligenceList({
|
||||
space_id:
|
||||
globalState.spaceId === PUBLIC_SPACE_ID
|
||||
? globalState.personalSpaceId
|
||||
: globalState.spaceId,
|
||||
name: query ?? search,
|
||||
types: [IntelligenceType.Bot, IntelligenceType.Project],
|
||||
size: 30,
|
||||
order_by: 0,
|
||||
cursor_id: nextCursorRef.current,
|
||||
status: [
|
||||
IntelligenceStatus.Using,
|
||||
IntelligenceStatus.Banned,
|
||||
IntelligenceStatus.MoveFailed,
|
||||
],
|
||||
});
|
||||
const { intelligences, total = 0, next_cursor_id } = res?.data ?? {};
|
||||
|
||||
const list: IBotSelectOptions = (intelligences ?? []).map(it => ({
|
||||
name: it.basic_info?.name ?? '',
|
||||
value: it.basic_info?.id ?? '',
|
||||
avatar: it.basic_info?.icon_url ?? '',
|
||||
type: it.type || IntelligenceType.Bot,
|
||||
}));
|
||||
const totalList = isReset ? list : [...selectList, ...list];
|
||||
|
||||
setTotal(total);
|
||||
nextCursorRef.current = next_cursor_id;
|
||||
setSelectList(totalList);
|
||||
setIsShowFoot(totalList.length < total);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const loadMoreData = async () => {
|
||||
if (isLoadMoreDate.current) {
|
||||
return;
|
||||
}
|
||||
isLoadMoreDate.current = true;
|
||||
await fetchBotList();
|
||||
isLoadMoreDate.current = false;
|
||||
};
|
||||
|
||||
const handleSearch = query => {
|
||||
setSearch(query);
|
||||
fetchBotList(query, true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles['select-wrapper']} ref={containerRef}>
|
||||
<Select
|
||||
value={idValue}
|
||||
data-testid={concatTestId(
|
||||
'workflow',
|
||||
'playground',
|
||||
'testerun',
|
||||
'bot-select',
|
||||
)}
|
||||
dropdownClassName={styles.dropdown}
|
||||
filter
|
||||
remote
|
||||
placeholder={I18n.t('wf_chatflow_73')}
|
||||
emptyContent={I18n.t('agentflow_addbot_select_empty_no_bot')}
|
||||
onSearch={debounce(handleSearch, DebounceTime)}
|
||||
prefix={<IconSearch />}
|
||||
loading={isLoading}
|
||||
style={{ width: '100%' }}
|
||||
virtualize={{
|
||||
height: listMaxHeight,
|
||||
width: '100%',
|
||||
itemSize: 32,
|
||||
}}
|
||||
onChange={newValue => {
|
||||
// 设置选中项类型(bot / project)
|
||||
const findItem = selectList.find(item => item.value === newValue);
|
||||
|
||||
if (useNewGlobalVariableCache && findItem?.type) {
|
||||
relatedBotService.updateRelatedBot({
|
||||
id: newValue as string,
|
||||
type: findItem?.type === IntelligenceType.Bot ? 'bot' : 'project',
|
||||
});
|
||||
}
|
||||
|
||||
handleChange(
|
||||
findItem,
|
||||
findItem
|
||||
? {
|
||||
id: newValue as string,
|
||||
type: findItem.type,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{[extraBotOption, ...selectList]
|
||||
.filter(item => item)
|
||||
.map(item =>
|
||||
RenderCustomOption(item, {
|
||||
disableBot,
|
||||
disableProject,
|
||||
disableBotTooltip,
|
||||
disableProjectTooltip,
|
||||
}),
|
||||
)}
|
||||
|
||||
{isShowFoot ? (
|
||||
<Select.Option
|
||||
value={new Date().getTime()}
|
||||
key={new Date().getTime()}
|
||||
className={styles['bot-foot-loading']}
|
||||
disabled
|
||||
>
|
||||
<RenderFootLoading onObserver={loadMoreData} />
|
||||
</Select.Option>
|
||||
) : null}
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
.json-viewer {
|
||||
max-height: unset;
|
||||
padding: 0 6px;
|
||||
background-color: unset;
|
||||
border: unset;
|
||||
|
||||
:global {
|
||||
// .flow-test-run-line {
|
||||
// width: 38px;
|
||||
|
||||
// // &::before {}
|
||||
// &::after {
|
||||
// width: 30px;
|
||||
// border-bottom-left-radius: 2px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.field-icon {
|
||||
color: var(--Light-usage-text---color-text-1, rgba(29, 28, 35, 80%));
|
||||
}
|
||||
|
||||
.field-key {
|
||||
color: var(--Light-usage-text---color-text-1, rgba(28, 31, 35, 80%));
|
||||
}
|
||||
|
||||
.field-value {
|
||||
color: var(--Light-usage-text---color-text-1, rgba(28, 31, 35, 80%));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 FC } from 'react';
|
||||
|
||||
import { JsonViewer } from '@coze-common/json-viewer';
|
||||
|
||||
import styles from './chat-history.module.less';
|
||||
|
||||
interface Props {
|
||||
data: object | null;
|
||||
}
|
||||
|
||||
export const ChatHistory: FC<Props> = ({ data }) => (
|
||||
<JsonViewer data={data} className={styles['json-viewer']} />
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
.container {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: var(--Light-color-grey---grey-1, #F0F0F5);
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* 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 complexity */
|
||||
import React from 'react';
|
||||
|
||||
import { useMount } from 'ahooks';
|
||||
import { IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { withField } from '@coze-arch/bot-semi';
|
||||
|
||||
import { Variables } from './variables';
|
||||
import { useVariables } from './use-variables';
|
||||
import { useTableInfo } from './use-table-info';
|
||||
import { useProjectVariables } from './use-project-variables';
|
||||
import { useLTMInfo } from './use-ltm-info';
|
||||
import { useChatHistory } from './use-chat-history';
|
||||
import { type ValueType } from './types';
|
||||
import { TableInfo } from './table-info';
|
||||
import { Item } from './item';
|
||||
import { ChatHistory } from './chat-history';
|
||||
import { Bots } from './bots';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface BotSelectProps {
|
||||
value?: ValueType;
|
||||
onChange?: (value?: ValueType) => void;
|
||||
hideLabel?: boolean;
|
||||
hideVariblesForce?: boolean;
|
||||
hasVariableNode?: boolean;
|
||||
hasVariableAssignNode?: boolean;
|
||||
hasDatabaseNode?: boolean;
|
||||
hasLTMNode?: boolean;
|
||||
hasChatHistoryEnabledLLM?: boolean;
|
||||
|
||||
disableBot?: boolean;
|
||||
disableBotTooltip?: string;
|
||||
disableProject?: boolean;
|
||||
disableProjectTooltip?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* copy from bot-select
|
||||
*/
|
||||
export const BotProjectSelect: React.FC<BotSelectProps> = ({
|
||||
value: originValue,
|
||||
onChange,
|
||||
hideLabel = false,
|
||||
hideVariblesForce = false,
|
||||
hasVariableNode = false,
|
||||
hasVariableAssignNode = false,
|
||||
hasDatabaseNode = false,
|
||||
hasChatHistoryEnabledLLM = false,
|
||||
hasLTMNode = false,
|
||||
...props
|
||||
}) => {
|
||||
let value = originValue;
|
||||
// 兼容历史数据
|
||||
if (typeof originValue === 'string') {
|
||||
value = {
|
||||
id: originValue,
|
||||
type: IntelligenceType.Bot,
|
||||
};
|
||||
}
|
||||
const valueId = value?.id;
|
||||
const isBot = value?.type === IntelligenceType.Bot;
|
||||
const { tableInfo, isLoading: isTableInfoLoading } = useTableInfo(
|
||||
isBot ? valueId : undefined,
|
||||
);
|
||||
const { variables, isLoading: isVariablesLoading } = useVariables(
|
||||
isBot ? valueId : undefined,
|
||||
);
|
||||
const { variables: projectVariables, isLoading: isProjectVariablesLoading } =
|
||||
useProjectVariables(!isBot ? valueId : undefined);
|
||||
const {
|
||||
chatHistory,
|
||||
// conversationId,
|
||||
// sectionId,
|
||||
} = useChatHistory(isBot ? valueId : undefined);
|
||||
const { ltmEnabled, isLoading: isLTMInfoLoading } = useLTMInfo(
|
||||
isBot ? valueId : undefined,
|
||||
);
|
||||
|
||||
const botSelected = !!valueId;
|
||||
|
||||
const hasVariables = variables && variables.length > 0;
|
||||
const hasTableInfo = tableInfo && tableInfo.length > 0;
|
||||
const hasProjectVariables = projectVariables && projectVariables?.length > 0;
|
||||
const hasChatHistory = !!chatHistory;
|
||||
|
||||
const showTableInfo = botSelected && !isTableInfoLoading && hasDatabaseNode;
|
||||
const showVariables =
|
||||
botSelected &&
|
||||
!isVariablesLoading &&
|
||||
(hasVariableNode || hasVariableAssignNode) &&
|
||||
!hideVariblesForce;
|
||||
|
||||
// 试运行都是临时搞了一个会话,这里把 bot 聊天历史展示可能会误导,先隐藏
|
||||
const showChatHistory = false;
|
||||
// const showChatHistory =
|
||||
// botSelected && !isChatHistoryLoading && hasChatHistoryEnabledLLM;
|
||||
const showLTMInfo = botSelected && hasLTMNode && !isLTMInfoLoading;
|
||||
const showProjectVariables =
|
||||
botSelected &&
|
||||
(hasVariableNode || hasVariableAssignNode) &&
|
||||
!isProjectVariablesLoading &&
|
||||
!isBot;
|
||||
|
||||
useMount(() => {
|
||||
const sourceBotId = new URLSearchParams(window.location.search).get(
|
||||
'bot_id',
|
||||
);
|
||||
if (!valueId && sourceBotId) {
|
||||
onChange?.({
|
||||
id: sourceBotId,
|
||||
type: IntelligenceType.Bot,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Item hideLabel={hideLabel} label={I18n.t('workflow_240218_02')}>
|
||||
<Bots isBot={isBot} value={value} onChange={onChange} {...props} />
|
||||
</Item>
|
||||
|
||||
{showProjectVariables ? (
|
||||
<div className={styles.container}>
|
||||
<Item
|
||||
label={I18n.t('wf_chatflow_126')}
|
||||
defaultText={I18n.t('wf_chatflow_127')}
|
||||
>
|
||||
{hasProjectVariables ? (
|
||||
<Variables variables={projectVariables} />
|
||||
) : null}
|
||||
</Item>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isBot ? (
|
||||
<div
|
||||
className={styles.container}
|
||||
style={{
|
||||
display:
|
||||
showVariables || showTableInfo || showChatHistory || showLTMInfo
|
||||
? 'block'
|
||||
: 'none',
|
||||
}}
|
||||
>
|
||||
{showVariables ? (
|
||||
<Item
|
||||
label={I18n.t('workflow_detail_testrun_variable_node_field')}
|
||||
defaultText={I18n.t(
|
||||
'workflow_detail_testrun_variable_node_nofield',
|
||||
)}
|
||||
>
|
||||
{hasVariables ? <Variables variables={variables} /> : null}
|
||||
</Item>
|
||||
) : null}
|
||||
|
||||
{showTableInfo ? (
|
||||
<Item
|
||||
label={I18n.t('workflow_240218_05')}
|
||||
defaultText={I18n.t('workflow_240218_04')}
|
||||
>
|
||||
{hasTableInfo ? <TableInfo data={tableInfo} /> : null}
|
||||
</Item>
|
||||
) : null}
|
||||
|
||||
{showChatHistory ? (
|
||||
<Item
|
||||
label={I18n.t('workflow_chathistory_testrun_title')}
|
||||
defaultText={I18n.t('workflow_chathistory_testrun_nocontent')}
|
||||
>
|
||||
{hasChatHistory ? <ChatHistory data={chatHistory} /> : null}
|
||||
</Item>
|
||||
) : null}
|
||||
|
||||
{showLTMInfo ? (
|
||||
<Item label={I18n.t('ltm_240617_02')}>
|
||||
<div className="text-[12px]">
|
||||
{I18n.t('timecapsule_1228_001')}:{' '}
|
||||
{ltmEnabled
|
||||
? I18n.t('timecapsule_0124_001')
|
||||
: I18n.t('timecapsule_0124_002')}
|
||||
</div>
|
||||
</Item>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const BotProjectSelectWithField = withField(BotProjectSelect, {
|
||||
valueKey: 'value',
|
||||
onKeyChangeFnName: 'onChange',
|
||||
});
|
||||
|
||||
BotProjectSelectWithField.defaultProps = {
|
||||
fieldStyle: { overflow: 'visible' },
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
.container {
|
||||
margin-top: 24px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label,
|
||||
.default-text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 8px;
|
||||
color: var(--Light-color-grey---grey-3, #A7A7B0);
|
||||
}
|
||||
|
||||
.default-text {
|
||||
color: var(--Light-color-grey---grey-3, #A7A7B0);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 PropsWithChildren } from 'react';
|
||||
|
||||
import styles from './item.module.less';
|
||||
|
||||
interface ItemProps {
|
||||
label: string;
|
||||
defaultText?: string;
|
||||
hideLabel?: boolean;
|
||||
}
|
||||
|
||||
export const Item: React.FC<PropsWithChildren<ItemProps>> = ({
|
||||
children,
|
||||
label,
|
||||
defaultText,
|
||||
hideLabel = false,
|
||||
}) => {
|
||||
const haveChildren = !!children;
|
||||
const showDefaultText = !haveChildren && !!defaultText;
|
||||
const showLabel = !hideLabel && !showDefaultText;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{showLabel ? <div className={styles.label}>{label}</div> : null}
|
||||
{showDefaultText ? (
|
||||
<div className={styles['default-text']}>{defaultText}</div>
|
||||
) : null}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
// 修改后禁用 stylelint
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.table {
|
||||
:global {
|
||||
table {
|
||||
border: 1px solid rgba(46, 46, 56, 8%);
|
||||
border-radius: 8px;
|
||||
|
||||
|
||||
thead {
|
||||
th {
|
||||
font-weight: 600 !important;
|
||||
color: var(--Light-usage-text---color-text-1, rgba(29, 28, 35, 80%)) !important;
|
||||
border-bottom: 1px solid rgba(46, 47, 56, 9%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
th,
|
||||
td {
|
||||
overflow: hidden;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--Light-usage-text---color-text-1, rgba(29, 28, 35, 80%));
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: 0 !important;
|
||||
|
||||
&:first-child {
|
||||
width: 114px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.semi-table-tbody>.semi-table-row>.semi-table-row-cell,
|
||||
.semi-table-thead>.semi-table-row>.semi-table-row-head {
|
||||
padding: 8px 0;
|
||||
|
||||
&:first-child {
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { Table } from '@coze-arch/bot-semi';
|
||||
import { type BotTable } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { TagList } from '../tag-list';
|
||||
|
||||
import styles from './table-info.module.less';
|
||||
|
||||
interface TableInfoProps {
|
||||
data?: BotTable[];
|
||||
}
|
||||
|
||||
export const TableInfo: React.FC<TableInfoProps> = ({
|
||||
data = [{ table_name: 'none' }],
|
||||
}) => {
|
||||
const columns = [
|
||||
{
|
||||
title: I18n.t('db_add_table_name'),
|
||||
dataIndex: 'table_name',
|
||||
render: (text: string) => <span>{text}</span>,
|
||||
},
|
||||
{
|
||||
title: I18n.t('db_add_table_field_name'),
|
||||
dataIndex: 'field_list',
|
||||
render: (fieldList: BotTable['field_list']) => {
|
||||
if (fieldList && fieldList.length > 0) {
|
||||
return (
|
||||
<TagList
|
||||
tags={(fieldList ?? []).map(({ name }) => name || '')}
|
||||
max={3}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return 'none';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={styles.table}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
pagination={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
//后端无定义 根据 bot_info 中的 workflow_info.profile_memory 推导而来
|
||||
import { type IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
|
||||
export interface Variable {
|
||||
key: string;
|
||||
description?: string;
|
||||
default_value?: string;
|
||||
}
|
||||
|
||||
export interface IBotSelectOption {
|
||||
name: string;
|
||||
avatar: string;
|
||||
value: string;
|
||||
type: IntelligenceType;
|
||||
}
|
||||
|
||||
export interface ValueType {
|
||||
id?: string;
|
||||
type?: IntelligenceType;
|
||||
}
|
||||
|
||||
export type IBotSelectOptions = IBotSelectOption[];
|
||||
|
||||
export interface DisableExtraOptions {
|
||||
disableBot?: boolean;
|
||||
disableProject?: boolean;
|
||||
disableBotTooltip?: string;
|
||||
disableProjectTooltip?: string;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import { IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
import {
|
||||
type GetDraftBotInfoAgwData,
|
||||
type ModelInfo,
|
||||
} from '@coze-arch/bot-api/playground_api';
|
||||
import { type BotTable } from '@coze-arch/bot-api/memory';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type IBotSelectOption } from './types';
|
||||
|
||||
export const useBotInfo = (botId?: string) => {
|
||||
const { isLoading, data: botInfo } = useQuery({
|
||||
queryKey: ['bot_info', botId || ''],
|
||||
queryFn: async () => {
|
||||
if (!botId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { data } = await PlaygroundApi.GetDraftBotInfoAgw({
|
||||
bot_id: botId,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { isLoading, botInfo };
|
||||
};
|
||||
|
||||
// 为 wf 使用 bot 信息做数据转换
|
||||
export const transformBotInfo = {
|
||||
// 模型数据
|
||||
model: (data?: GetDraftBotInfoAgwData): ModelInfo =>
|
||||
data?.bot_info?.model_info ?? {},
|
||||
// 基本信息数据
|
||||
basicInfo: (
|
||||
botInfo?: GetDraftBotInfoAgwData,
|
||||
): IBotSelectOption | undefined => {
|
||||
if (!botInfo) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
name: botInfo?.bot_info?.name ?? '',
|
||||
avatar: botInfo?.bot_info?.icon_url ?? '',
|
||||
value: botInfo?.bot_info?.bot_id ?? '',
|
||||
type: IntelligenceType.Bot,
|
||||
};
|
||||
},
|
||||
// 数据库信息
|
||||
database: (botInfo?: GetDraftBotInfoAgwData): BotTable[] | undefined =>
|
||||
botInfo?.bot_info?.database_list,
|
||||
// 变量信息
|
||||
variable: (botInfo?: GetDraftBotInfoAgwData) =>
|
||||
botInfo?.bot_info?.variable_list,
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import { Scene } from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { transformBotInfo, useBotInfo } from './use-bot-info';
|
||||
|
||||
const MAX_ROUND_COUNT = 20;
|
||||
const getMessageList = async ({
|
||||
botId,
|
||||
botCount,
|
||||
}: {
|
||||
botId: string;
|
||||
botCount: number;
|
||||
}) => {
|
||||
const totalRound = Math.ceil(botCount / MAX_ROUND_COUNT);
|
||||
|
||||
let cursor = '0';
|
||||
let round = 0;
|
||||
let conversationId = '';
|
||||
let sectionId = '';
|
||||
|
||||
let messageList: { role: string; content: string }[] = [];
|
||||
while (round < totalRound) {
|
||||
const res = await DeveloperApi.GetMessageList({
|
||||
bot_id: botId,
|
||||
draft_mode: true,
|
||||
scene: Scene.Playground,
|
||||
|
||||
cursor,
|
||||
count: Math.min(botCount - round * MAX_ROUND_COUNT, MAX_ROUND_COUNT),
|
||||
});
|
||||
|
||||
messageList = [
|
||||
...messageList,
|
||||
...res.message_list
|
||||
.filter(item => item.type === 'question' || item.type === 'answer')
|
||||
.map(item => ({
|
||||
role: item?.role as string,
|
||||
content: item?.content as string,
|
||||
})),
|
||||
];
|
||||
|
||||
conversationId = res?.connector_conversation_id || '';
|
||||
sectionId = res?.last_section_id || '';
|
||||
cursor = res?.cursor;
|
||||
round += 1;
|
||||
|
||||
if (!res.hasmore) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { messageList, conversationId, sectionId };
|
||||
};
|
||||
|
||||
export const useChatHistory = (botId?: string) => {
|
||||
const { botInfo, isLoading: isBotInfoLoading } = useBotInfo(botId);
|
||||
|
||||
const botCount =
|
||||
transformBotInfo.model(botInfo)?.short_memory_policy?.history_round ?? 0;
|
||||
|
||||
const { isLoading: isMessageLoading, data } = useQuery({
|
||||
queryKey: ['bot_info', botId, botCount],
|
||||
queryFn: () =>
|
||||
getMessageList({
|
||||
botId: botId as string,
|
||||
botCount,
|
||||
}),
|
||||
enabled: botCount !== undefined,
|
||||
});
|
||||
|
||||
const { messageList, conversationId, sectionId } = data || {};
|
||||
|
||||
return {
|
||||
chatHistory: messageList?.length
|
||||
? {
|
||||
chatHistory: messageList,
|
||||
}
|
||||
: null,
|
||||
isLoading: isBotInfoLoading || isMessageLoading,
|
||||
conversationId,
|
||||
sectionId,
|
||||
};
|
||||
};
|
||||
@@ -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 { useMemo } from 'react';
|
||||
|
||||
import { IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
|
||||
import { useProjectItemInfo } from './use-project-info';
|
||||
import { transformBotInfo, useBotInfo } from './use-bot-info';
|
||||
import type { IBotSelectOption, ValueType } from './types';
|
||||
|
||||
export const useExtraBotOption = (
|
||||
botOptionList: IBotSelectOption[],
|
||||
currentBotValue?: string,
|
||||
isBot?: boolean,
|
||||
handleChange?: (botInfo?: IBotSelectOption, _value?: ValueType) => void,
|
||||
// eslint-disable-next-line max-params
|
||||
): IBotSelectOption | undefined => {
|
||||
const { botInfo } = useBotInfo(isBot ? currentBotValue : undefined);
|
||||
const { projectItemInfo } = useProjectItemInfo(
|
||||
!isBot ? currentBotValue : undefined,
|
||||
);
|
||||
|
||||
const botValue = useMemo(() => {
|
||||
const botFinded = botOptionList.find(
|
||||
({ value }) => value === currentBotValue,
|
||||
);
|
||||
|
||||
if (!botFinded) {
|
||||
const botItem = transformBotInfo.basicInfo(botInfo);
|
||||
if (botItem) {
|
||||
handleChange?.(botItem, {
|
||||
id: botItem.value,
|
||||
type: botItem.type,
|
||||
});
|
||||
}
|
||||
return botItem;
|
||||
}
|
||||
return undefined;
|
||||
}, [botOptionList, botInfo, currentBotValue]);
|
||||
|
||||
const projectValue = useMemo(() => {
|
||||
const projectFinded = botOptionList.find(
|
||||
({ value }) => value === currentBotValue,
|
||||
);
|
||||
if (projectFinded) {
|
||||
return undefined;
|
||||
}
|
||||
let projectItem;
|
||||
if (projectItemInfo) {
|
||||
projectItem = {
|
||||
name: projectItemInfo?.basic_info?.name || '',
|
||||
value: projectItemInfo?.basic_info?.id || '',
|
||||
avatar: projectItemInfo?.basic_info?.icon_url || '',
|
||||
type: IntelligenceType.Project,
|
||||
};
|
||||
handleChange?.(projectItem, {
|
||||
id: projectItem.value,
|
||||
type: projectItem.type,
|
||||
});
|
||||
}
|
||||
return projectItem;
|
||||
}, [projectItemInfo, botOptionList, currentBotValue]);
|
||||
|
||||
return isBot ? botValue : projectValue;
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import get from 'lodash-es/get';
|
||||
import { TimeCapsuleMode } from '@coze-arch/idl/playground_api';
|
||||
|
||||
import { useBotInfo } from './use-bot-info';
|
||||
|
||||
export const useLTMInfo = (botId?: string) => {
|
||||
const { isLoading, botInfo } = useBotInfo(botId);
|
||||
const timeCapsuleMode = get(
|
||||
botInfo,
|
||||
['bot_info', 'bot_tag_info', 'time_capsule_info', 'time_capsule_mode'],
|
||||
TimeCapsuleMode.Off,
|
||||
);
|
||||
|
||||
return {
|
||||
// 是否开启长期记忆
|
||||
ltmEnabled: timeCapsuleMode === TimeCapsuleMode.On,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import { IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
import { intelligenceApi, MemoryApi } from '@coze-arch/bot-api';
|
||||
|
||||
export const useProjectInfo = (projectId?: string) => {
|
||||
const { isLoading, data: variableList } = useQuery({
|
||||
queryKey: ['project_info', projectId || ''],
|
||||
queryFn: async () => {
|
||||
if (!projectId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { VariableList } = await MemoryApi.GetProjectVariableList({
|
||||
ProjectID: projectId,
|
||||
});
|
||||
|
||||
return (
|
||||
VariableList?.filter?.(v => v.Enable)?.map(variable => ({
|
||||
key: variable.Keyword,
|
||||
})) || []
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return { isLoading, variableList };
|
||||
};
|
||||
|
||||
export const useProjectItemInfo = (projectId?: string) => {
|
||||
const { isLoading, data: projectItemInfo } = useQuery({
|
||||
queryKey: ['project_item_info', projectId || ''],
|
||||
queryFn: async () => {
|
||||
if (!projectId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { data } = await intelligenceApi.GetDraftIntelligenceInfo({
|
||||
intelligence_id: projectId,
|
||||
intelligence_type: IntelligenceType.Project,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { isLoading, projectItemInfo };
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { useProjectInfo } from './use-project-info';
|
||||
|
||||
export const useProjectVariables = (projectID?: string) => {
|
||||
const { variableList, isLoading } = useProjectInfo(projectID);
|
||||
|
||||
return {
|
||||
variables: variableList,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { type BotTable, BotTableRWMode } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { transformBotInfo, useBotInfo } from './use-bot-info';
|
||||
|
||||
// 多人模式下产品希望前端展示uuid & id 目前这两个字段会被后端过滤 先由前端补充这两个字段 后端充分评估过再移除过滤逻辑
|
||||
function addUidAndIdToBotFieldsIfIsUnlimitedReadWriteMode(
|
||||
tableInfo: BotTable[],
|
||||
): BotTable[] {
|
||||
tableInfo.forEach(bot => {
|
||||
if (
|
||||
bot.rw_mode === BotTableRWMode.UnlimitedReadWrite &&
|
||||
(bot?.field_list?.length as number) > 0
|
||||
) {
|
||||
['id', 'uuid'].forEach(name => {
|
||||
const fieldExisted = !!bot.field_list?.find(
|
||||
field => field.name === name,
|
||||
);
|
||||
if (!fieldExisted) {
|
||||
bot.field_list?.unshift({ name });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return tableInfo;
|
||||
}
|
||||
|
||||
export const useTableInfo = (botID?: string) => {
|
||||
const { isLoading, botInfo } = useBotInfo(botID);
|
||||
let tableInfo: BotTable[] | undefined;
|
||||
tableInfo = transformBotInfo.database(botInfo);
|
||||
if (tableInfo) {
|
||||
tableInfo = addUidAndIdToBotFieldsIfIsUnlimitedReadWriteMode(tableInfo);
|
||||
}
|
||||
|
||||
return { tableInfo, isLoading };
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 Variable } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import { transformBotInfo, useBotInfo } from './use-bot-info';
|
||||
|
||||
export const useVariables = (botID?: string) => {
|
||||
const { botInfo, isLoading } = useBotInfo(botID);
|
||||
const variables: Variable[] | undefined = transformBotInfo.variable(botInfo);
|
||||
|
||||
return {
|
||||
variables,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
@@ -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 { IntelligenceType } from '@coze-arch/idl/intelligence_api';
|
||||
|
||||
export const isItemDisabled = (
|
||||
{ disableBot, disableProject },
|
||||
type?: IntelligenceType,
|
||||
) => {
|
||||
const isBot = type === IntelligenceType.Bot;
|
||||
const isProject = type === IntelligenceType.Project;
|
||||
|
||||
const disabled = (isBot && disableBot) || (isProject && disableProject);
|
||||
return disabled || !type;
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 Variable } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import { TagList } from '../tag-list';
|
||||
|
||||
interface VariablesProps {
|
||||
variables: Variable[];
|
||||
}
|
||||
|
||||
export const Variables: React.FC<VariablesProps> = ({ variables }) => {
|
||||
const tagList = variables.map(({ key }) => key ?? '');
|
||||
|
||||
return <TagList tags={tagList} max={5} />;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
.select-wrapper {
|
||||
position: relative;
|
||||
|
||||
:global {
|
||||
|
||||
// 修复bot-select在画布中因为缩放导致尺寸不正常
|
||||
.semi-portal-inner {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
min-height: 50px;
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.semi-select-option-list {
|
||||
&::-webkit-scrollbar {
|
||||
height: 0;
|
||||
background: transparent;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.semi-select-loading-wrapper {
|
||||
text-align: center;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.semi-select-option-selected {
|
||||
.semi-icon-tick {
|
||||
color: var(--light-usage-primary-color-primary, #4d53e8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-foot-loading {
|
||||
color: #4D53E8;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.bot-option {
|
||||
display: flex;
|
||||
padding-left: 24px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.loading-tag {
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* 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, useState, useRef, useMemo } from 'react';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
import { PUBLIC_SPACE_ID } from '@coze-workflow/base/constants';
|
||||
import { concatTestId } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Typography, Spin, Avatar, Select } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
ListBotDraftType,
|
||||
PublishStatus,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
import { IconSearch } from '@douyinfe/semi-icons';
|
||||
|
||||
import { useGlobalState } from '../../hooks';
|
||||
import { useExtraBotOption } from './use-extra-bot-option';
|
||||
import type { IBotSelectOption } from './types';
|
||||
|
||||
import styles from './bots.module.less';
|
||||
|
||||
type IBotSelectOptions = IBotSelectOption[];
|
||||
|
||||
const RenderCustomOption = item => {
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Select.Option
|
||||
data-testid={concatTestId(
|
||||
'workflow',
|
||||
'playground',
|
||||
'testrun',
|
||||
'bot-select',
|
||||
'option',
|
||||
item.value,
|
||||
)}
|
||||
value={item.value}
|
||||
showTick={true}
|
||||
key={item.value}
|
||||
className={styles['bot-option']}
|
||||
>
|
||||
<div className="flex" style={{ width: '100%' }}>
|
||||
<Avatar
|
||||
size="extra-extra-small"
|
||||
style={{ flexShrink: 0, marginRight: 8 }}
|
||||
shape="square"
|
||||
src={item.avatar}
|
||||
/>
|
||||
<div className="flex" style={{ flexGrow: 1, flexShrink: 1, width: 0 }}>
|
||||
<Typography.Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: '#1D1C23',
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderFootLoading = ({
|
||||
onObserver,
|
||||
}: {
|
||||
onObserver: () => Promise<void>;
|
||||
}) => {
|
||||
const indicatorRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const callback = entries => {
|
||||
if (entries[0].isIntersecting) {
|
||||
onObserver?.();
|
||||
}
|
||||
};
|
||||
const loadingObserver = new IntersectionObserver(callback);
|
||||
indicatorRef.current && loadingObserver.observe(indicatorRef.current);
|
||||
return () => loadingObserver.disconnect();
|
||||
}, []);
|
||||
return (
|
||||
<div className={styles['loading-tag']} ref={indicatorRef}>
|
||||
<Spin style={{ marginRight: 10 }} size="small" />
|
||||
<span>{I18n.t('workflow_add_common_loading')}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface BotsProps {
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export const Bots: React.FC<BotsProps> = ({ value, onChange, ...props }) => {
|
||||
const globalState = useGlobalState();
|
||||
const DebounceTime = 500;
|
||||
|
||||
const isLoadMoreDate = useRef(false);
|
||||
const [selectList = [], setSelectList] = useState<IBotSelectOptions>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isShowFoot, setIsShowFoot] = useState<boolean>(false);
|
||||
const [pageIndex, setPageIndex] = useState<number>(1);
|
||||
const [search, setSearch] = useState<string>('');
|
||||
const [searchTotal, setTotal] = useState(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 由于分页限制 选中的botId可能找不到对应的option 需要额外添加
|
||||
const extraBotOption = useExtraBotOption(selectList, value);
|
||||
|
||||
// 接口得到的总数并非真实的总数,前端可能会拼接 options
|
||||
const listMaxHeight = useMemo(() => {
|
||||
const realTotal = extraBotOption ? searchTotal + 1 : searchTotal;
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
return realTotal < 7 ? realTotal * 32 : 208;
|
||||
}, [searchTotal, extraBotOption]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchBotList();
|
||||
}, []);
|
||||
|
||||
const fetchBotList = async (
|
||||
index?: number,
|
||||
query?: string,
|
||||
isReset = false,
|
||||
) => {
|
||||
if (query) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
const res = await DeveloperApi.GetDraftBotList({
|
||||
space_id:
|
||||
globalState.spaceId === PUBLIC_SPACE_ID
|
||||
? globalState.personalSpaceId
|
||||
: globalState.spaceId,
|
||||
bot_name: query ?? search,
|
||||
order_by: 0,
|
||||
team_bot_type: ListBotDraftType.TeamBots,
|
||||
page_index: index ?? pageIndex,
|
||||
page_size: 30,
|
||||
is_publish: PublishStatus.All,
|
||||
});
|
||||
const { bot_draft_list, total = 0 } = res?.data ?? {};
|
||||
const list: IBotSelectOptions = (bot_draft_list ?? []).map(it => ({
|
||||
name: it.name ?? '',
|
||||
value: it.id ?? '',
|
||||
avatar: it.icon_url ?? '',
|
||||
}));
|
||||
const totalList = isReset ? list : [...selectList, ...list];
|
||||
|
||||
setTotal(total);
|
||||
setSelectList(totalList);
|
||||
setIsShowFoot(totalList.length < total);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const loadMoreData = async () => {
|
||||
if (isLoadMoreDate.current) {
|
||||
return;
|
||||
}
|
||||
isLoadMoreDate.current = true;
|
||||
const newPageIndex = pageIndex + 1;
|
||||
setPageIndex(newPageIndex);
|
||||
await fetchBotList(newPageIndex);
|
||||
isLoadMoreDate.current = false;
|
||||
};
|
||||
|
||||
const handleSearch = query => {
|
||||
setSearch(query);
|
||||
setPageIndex(1);
|
||||
fetchBotList(1, query, true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles['select-wrapper']} ref={containerRef}>
|
||||
<Select
|
||||
value={value}
|
||||
data-testid={concatTestId(
|
||||
'workflow',
|
||||
'playground',
|
||||
'testerun',
|
||||
'bot-select',
|
||||
)}
|
||||
dropdownClassName={styles.dropdown}
|
||||
showClear
|
||||
filter
|
||||
remote
|
||||
placeholder={I18n.t('workflow_detail_testrun_variable_node_select')}
|
||||
emptyContent={I18n.t('agentflow_addbot_select_empty_no_bot')}
|
||||
onSearch={debounce(handleSearch, DebounceTime)}
|
||||
prefix={<IconSearch />}
|
||||
loading={isLoading}
|
||||
style={{ width: '100%' }}
|
||||
virtualize={{
|
||||
height: listMaxHeight,
|
||||
width: '100%',
|
||||
itemSize: 32,
|
||||
}}
|
||||
onChange={newValue => onChange?.(newValue as string)}
|
||||
{...props}
|
||||
>
|
||||
{[extraBotOption, ...selectList]
|
||||
.filter(item => item)
|
||||
.map(item => RenderCustomOption(item))}
|
||||
|
||||
{isShowFoot ? (
|
||||
<Select.Option
|
||||
value={new Date().getTime()}
|
||||
key={new Date().getTime()}
|
||||
className={styles['bot-foot-loading']}
|
||||
disabled
|
||||
>
|
||||
<RenderFootLoading onObserver={loadMoreData} />
|
||||
</Select.Option>
|
||||
) : null}
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
.json-viewer {
|
||||
max-height: unset;
|
||||
background-color: unset;
|
||||
border: unset;
|
||||
padding: 0 6px;
|
||||
|
||||
:global {
|
||||
// .flow-test-run-line {
|
||||
// width: 38px;
|
||||
|
||||
// // &::before {}
|
||||
// &::after {
|
||||
// width: 30px;
|
||||
// border-bottom-left-radius: 2px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.field-icon {
|
||||
color: var(--Light-usage-text---color-text-1, rgba(29, 28, 35, 0.80));
|
||||
}
|
||||
|
||||
.field-key {
|
||||
color: var(--Light-usage-text---color-text-1, rgba(28, 31, 35, 0.80));
|
||||
}
|
||||
|
||||
.field-value {
|
||||
color: var(--Light-usage-text---color-text-1, rgba(28, 31, 35, 0.80));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 FC } from 'react';
|
||||
|
||||
import { JsonViewer } from '@coze-common/json-viewer';
|
||||
|
||||
import styles from './chat-history.module.less';
|
||||
|
||||
interface Props {
|
||||
data: object | null;
|
||||
}
|
||||
|
||||
export const ChatHistory: FC<Props> = ({ data }) => (
|
||||
<JsonViewer data={data} className={styles['json-viewer']} />
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
.container {
|
||||
border-radius: 8px;
|
||||
background: var(--Light-color-grey---grey-1, #F0F0F5);
|
||||
padding: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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 complexity */
|
||||
import React from 'react';
|
||||
|
||||
import { useMount } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { withField } from '@coze-arch/bot-semi';
|
||||
|
||||
import { Variables } from './variables';
|
||||
import { useVariables } from './use-variables';
|
||||
import { useTableInfo } from './use-table-info';
|
||||
import { useLTMInfo } from './use-ltm-info';
|
||||
import { useChatHistory } from './use-chat-history';
|
||||
import { TableInfo } from './table-info';
|
||||
import { Item } from './item';
|
||||
import { ChatHistory } from './chat-history';
|
||||
import { Bots } from './bots';
|
||||
|
||||
import styles from './index.module.less';
|
||||
interface BotSelectProps {
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
hideLabel?: boolean;
|
||||
hideVariblesForce?: boolean;
|
||||
hasVariableNode?: boolean;
|
||||
hasVariableAssignNode?: boolean;
|
||||
/** @deprecated 这个字段目前没有用了,后续可以清理掉 */
|
||||
hasDatabaseNode?: boolean;
|
||||
hasLTMNode?: boolean;
|
||||
hasChatHistoryEnabledLLM?: boolean;
|
||||
}
|
||||
|
||||
export const BotSelect: React.FC<BotSelectProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
hideLabel = false,
|
||||
hideVariblesForce = false,
|
||||
hasVariableNode = false,
|
||||
hasVariableAssignNode = false,
|
||||
hasDatabaseNode = false,
|
||||
hasChatHistoryEnabledLLM = false,
|
||||
hasLTMNode = false,
|
||||
...props
|
||||
}) => {
|
||||
const { tableInfo, isLoading: isTableInfoLoading } = useTableInfo(value);
|
||||
const { variables, isLoading: isVariablesLoading } = useVariables(value);
|
||||
const { chatHistory } = useChatHistory(value);
|
||||
const { ltmEnabled, isLoading: isLTMInfoLoading } = useLTMInfo(value);
|
||||
|
||||
const botSelected = !!value;
|
||||
|
||||
const hasVariables = variables && variables.length > 0;
|
||||
const hasTableInfo = tableInfo && tableInfo.length > 0;
|
||||
const hasChatHistory = !!chatHistory;
|
||||
|
||||
const showTableInfo = botSelected && !isTableInfoLoading && hasDatabaseNode;
|
||||
const showVariables =
|
||||
botSelected &&
|
||||
!isVariablesLoading &&
|
||||
(hasVariableNode || hasVariableAssignNode) &&
|
||||
!hideVariblesForce;
|
||||
// 试运行都是临时搞了一个会话,这里把 bot 聊天历史展示可能会误导,先隐藏
|
||||
const showChatHistory = false;
|
||||
// const showChatHistory =
|
||||
// botSelected && !isChatHistoryLoading && hasChatHistoryEnabledLLM;
|
||||
const showLTMInfo = botSelected && hasLTMNode && !isLTMInfoLoading;
|
||||
|
||||
useMount(() => {
|
||||
const sourceBotId = new URLSearchParams(window.location.search).get(
|
||||
'bot_id',
|
||||
);
|
||||
if (!value && sourceBotId) {
|
||||
onChange?.(sourceBotId);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Item hideLabel={hideLabel} label={I18n.t('workflow_240218_02')}>
|
||||
<Bots value={value} onChange={onChange} {...props} />
|
||||
</Item>
|
||||
|
||||
<div
|
||||
className={styles.container}
|
||||
style={{
|
||||
display:
|
||||
showVariables || showTableInfo || showChatHistory || showLTMInfo
|
||||
? 'block'
|
||||
: 'none',
|
||||
}}
|
||||
>
|
||||
{showVariables ? (
|
||||
<Item
|
||||
label={I18n.t('workflow_detail_testrun_variable_node_field')}
|
||||
defaultText={I18n.t(
|
||||
'workflow_detail_testrun_variable_node_nofield',
|
||||
)}
|
||||
>
|
||||
{hasVariables ? <Variables variables={variables} /> : null}
|
||||
</Item>
|
||||
) : null}
|
||||
|
||||
{showTableInfo ? (
|
||||
<Item
|
||||
label={I18n.t('workflow_240218_05')}
|
||||
defaultText={I18n.t('workflow_240218_04')}
|
||||
>
|
||||
{hasTableInfo ? <TableInfo data={tableInfo} /> : null}
|
||||
</Item>
|
||||
) : null}
|
||||
|
||||
{showChatHistory ? (
|
||||
<Item
|
||||
label={I18n.t('workflow_chathistory_testrun_title')}
|
||||
defaultText={I18n.t('workflow_chathistory_testrun_nocontent')}
|
||||
>
|
||||
{hasChatHistory ? <ChatHistory data={chatHistory} /> : null}
|
||||
</Item>
|
||||
) : null}
|
||||
|
||||
{showLTMInfo ? (
|
||||
<Item label={I18n.t('ltm_240617_02')}>
|
||||
<div>
|
||||
{I18n.t('timecapsule_1228_001')}:{' '}
|
||||
{ltmEnabled
|
||||
? I18n.t('timecapsule_0124_001')
|
||||
: I18n.t('timecapsule_0124_002')}
|
||||
</div>
|
||||
</Item>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const BotSelectWithField = withField(BotSelect, {
|
||||
valueKey: 'value',
|
||||
onKeyChangeFnName: 'onChange',
|
||||
});
|
||||
|
||||
BotSelectWithField.defaultProps = {
|
||||
fieldStyle: { overflow: 'visible' },
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
.container {
|
||||
margin-top: 24px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label,
|
||||
.default-text {
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--Light-color-grey---grey-3, #A7A7B0);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.default-text {
|
||||
color: var(--Light-color-grey---grey-3, #A7A7B0);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 PropsWithChildren } from 'react';
|
||||
|
||||
import styles from './item.module.less';
|
||||
|
||||
interface ItemProps {
|
||||
label: string;
|
||||
defaultText?: string;
|
||||
hideLabel?: boolean;
|
||||
}
|
||||
|
||||
export const Item: React.FC<PropsWithChildren<ItemProps>> = ({
|
||||
children,
|
||||
label,
|
||||
defaultText,
|
||||
hideLabel = false,
|
||||
}) => {
|
||||
const haveChildren = !!children;
|
||||
const showDefaultText = !haveChildren && !!defaultText;
|
||||
const showLabel = !hideLabel && !showDefaultText;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{showLabel && <div className={styles.label}>{label}</div>}
|
||||
{showDefaultText && (
|
||||
<div className={styles['default-text']}>{defaultText}</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
.table {
|
||||
:global {
|
||||
table {
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(46, 46, 56, 0.08);
|
||||
|
||||
|
||||
thead {
|
||||
th {
|
||||
border-bottom: 1px solid rgba(46, 47, 56, 0.09) !important;
|
||||
color: var(--Light-usage-text---color-text-1, rgba(29, 28, 35, 0.80)) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
th,
|
||||
td {
|
||||
overflow: hidden;
|
||||
color: var(--Light-usage-text---color-text-1, rgba(29, 28, 35, 0.80));
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: 0 !important;
|
||||
|
||||
&:first-child {
|
||||
width: 114px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.semi-table-tbody>.semi-table-row>.semi-table-row-cell,
|
||||
.semi-table-thead>.semi-table-row>.semi-table-row-head {
|
||||
padding: 8px 0;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 { Table } from '@coze-arch/bot-semi';
|
||||
import { type BotTable } from '@coze-arch/bot-api/memory';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { TagList } from '../tag-list';
|
||||
|
||||
import styles from './table-info.module.less';
|
||||
|
||||
interface TableInfoProps {
|
||||
data?: BotTable[];
|
||||
}
|
||||
|
||||
export const TableInfo: React.FC<TableInfoProps> = ({
|
||||
data = [{ table_name: 'none' }],
|
||||
}) => {
|
||||
const columns = [
|
||||
{
|
||||
title: I18n.t('db_add_table_name'),
|
||||
dataIndex: 'table_name',
|
||||
render: (text: string) => <span>{text}</span>,
|
||||
},
|
||||
{
|
||||
title: I18n.t('db_add_table_field_name'),
|
||||
dataIndex: 'field_list',
|
||||
render: (fieldList: BotTable['field_list']) => {
|
||||
if (fieldList && fieldList.length > 0) {
|
||||
return (
|
||||
<TagList
|
||||
tags={(fieldList ?? []).map(({ name }) => name || '')}
|
||||
max={3}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return 'none';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={styles.table}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
pagination={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
//后端无定义 根据bot_info中的workflow_info.profile_memory推导而来
|
||||
export interface Variable {
|
||||
key: string;
|
||||
description?: string;
|
||||
default_value?: string;
|
||||
}
|
||||
|
||||
export interface IBotSelectOption {
|
||||
name: string;
|
||||
avatar: string;
|
||||
value: string;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
type GetDraftBotInfoAgwData,
|
||||
type ModelInfo,
|
||||
} from '@coze-arch/bot-api/playground_api';
|
||||
import { type BotTable } from '@coze-arch/bot-api/memory';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type IBotSelectOption } from './types';
|
||||
|
||||
export const useBotInfo = (botId?: string) => {
|
||||
const { isLoading, data: botInfo } = useQuery({
|
||||
queryKey: ['bot_info', botId || ''],
|
||||
queryFn: async () => {
|
||||
if (!botId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { data } = await PlaygroundApi.GetDraftBotInfoAgw({
|
||||
bot_id: botId,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { isLoading, botInfo };
|
||||
};
|
||||
|
||||
// 为wf使用bot信息做数据转换
|
||||
export const transformBotInfo = {
|
||||
// 模型数据
|
||||
model: (data?: GetDraftBotInfoAgwData): ModelInfo =>
|
||||
data?.bot_info?.model_info ?? {},
|
||||
// 基本信息数据
|
||||
basicInfo: (
|
||||
botInfo?: GetDraftBotInfoAgwData,
|
||||
): IBotSelectOption | undefined => {
|
||||
if (!botInfo) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
name: botInfo?.bot_info?.name ?? '',
|
||||
avatar: botInfo?.bot_info?.icon_url ?? '',
|
||||
value: botInfo?.bot_info?.bot_id ?? '',
|
||||
};
|
||||
},
|
||||
// 数据库信息
|
||||
database: (botInfo?: GetDraftBotInfoAgwData): BotTable[] | undefined =>
|
||||
botInfo?.bot_info?.database_list,
|
||||
// 变量信息
|
||||
variable: (botInfo?: GetDraftBotInfoAgwData) =>
|
||||
botInfo?.bot_info?.variable_list,
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import { Scene } from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { transformBotInfo, useBotInfo } from './use-bot-info';
|
||||
|
||||
const MAX_ROUND_COUNT = 20;
|
||||
const getMessageList = async ({
|
||||
botId,
|
||||
botCount,
|
||||
}: {
|
||||
botId: string;
|
||||
botCount: number;
|
||||
}) => {
|
||||
const totalRound = Math.ceil(botCount / MAX_ROUND_COUNT);
|
||||
|
||||
let cursor = '0';
|
||||
let round = 0;
|
||||
|
||||
let messageList: { role: string; content: string }[] = [];
|
||||
while (round < totalRound) {
|
||||
const res = await DeveloperApi.GetMessageList({
|
||||
bot_id: botId,
|
||||
draft_mode: true,
|
||||
scene: Scene.Playground,
|
||||
|
||||
cursor,
|
||||
count: Math.min(botCount - round * MAX_ROUND_COUNT, MAX_ROUND_COUNT),
|
||||
});
|
||||
|
||||
messageList = [
|
||||
...messageList,
|
||||
...res.message_list
|
||||
.filter(item => item.type === 'question' || item.type === 'answer')
|
||||
.map(item => ({
|
||||
role: item?.role as string,
|
||||
content: item?.content as string,
|
||||
})),
|
||||
];
|
||||
|
||||
cursor = res.cursor;
|
||||
round += 1;
|
||||
|
||||
if (!res.hasmore) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return messageList;
|
||||
};
|
||||
|
||||
export const useChatHistory = (botId?: string) => {
|
||||
const { botInfo, isLoading: isBotInfoLoading } = useBotInfo(botId);
|
||||
|
||||
const botCount =
|
||||
transformBotInfo.model(botInfo)?.short_memory_policy?.history_round ?? 0;
|
||||
|
||||
const { isLoading: isMessageLoading, data: messageList } = useQuery({
|
||||
queryKey: ['bot_info', botId, botCount],
|
||||
queryFn: () =>
|
||||
getMessageList({
|
||||
botId: botId as string,
|
||||
botCount,
|
||||
}),
|
||||
enabled: botCount !== undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
chatHistory: messageList?.length
|
||||
? {
|
||||
chatHistory: messageList,
|
||||
}
|
||||
: null,
|
||||
isLoading: isBotInfoLoading || isMessageLoading,
|
||||
};
|
||||
};
|
||||
@@ -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 { useMemo } from 'react';
|
||||
|
||||
import { transformBotInfo, useBotInfo } from './use-bot-info';
|
||||
import type { IBotSelectOption } from './types';
|
||||
|
||||
export const useExtraBotOption = (
|
||||
botOptionList: IBotSelectOption[],
|
||||
currentBotValue?: string,
|
||||
): IBotSelectOption | undefined => {
|
||||
const { botInfo } = useBotInfo(currentBotValue);
|
||||
return useMemo(() => {
|
||||
const botFinded = botOptionList.find(
|
||||
({ value }) => value === currentBotValue,
|
||||
);
|
||||
|
||||
if (!botFinded) {
|
||||
return transformBotInfo.basicInfo(botInfo);
|
||||
}
|
||||
return undefined;
|
||||
}, [botOptionList, botInfo]);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import get from 'lodash-es/get';
|
||||
import { TimeCapsuleMode } from '@coze-arch/idl/playground_api';
|
||||
|
||||
import { useBotInfo } from './use-bot-info';
|
||||
|
||||
export const useLTMInfo = (botId?: string) => {
|
||||
const { isLoading, botInfo } = useBotInfo(botId);
|
||||
const timeCapsuleMode = get(
|
||||
botInfo,
|
||||
['bot_info', 'bot_tag_info', 'time_capsule_info', 'time_capsule_mode'],
|
||||
TimeCapsuleMode.Off,
|
||||
);
|
||||
|
||||
return {
|
||||
// 是否开启长期记忆
|
||||
ltmEnabled: timeCapsuleMode === TimeCapsuleMode.On,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { type BotTable, BotTableRWMode } from '@coze-arch/bot-api/memory';
|
||||
|
||||
import { transformBotInfo, useBotInfo } from './use-bot-info';
|
||||
|
||||
// 多人模式下产品希望前端展示uuid & id 目前这两个字段会被后端过滤 先由前端补充这两个字段 后端充分评估过再移除过滤逻辑
|
||||
function addUidAndIdToBotFieldsIfIsUnlimitedReadWriteMode(
|
||||
tableInfo: BotTable[],
|
||||
): BotTable[] {
|
||||
tableInfo.forEach(bot => {
|
||||
if (
|
||||
bot.rw_mode === BotTableRWMode.UnlimitedReadWrite &&
|
||||
(bot?.field_list?.length as number) > 0
|
||||
) {
|
||||
['id', 'uuid'].forEach(name => {
|
||||
const fieldExisted = !!bot.field_list?.find(
|
||||
field => field.name === name,
|
||||
);
|
||||
if (!fieldExisted) {
|
||||
bot.field_list?.unshift({ name });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return tableInfo;
|
||||
}
|
||||
|
||||
export const useTableInfo = (botID?: string) => {
|
||||
const { isLoading, botInfo } = useBotInfo(botID);
|
||||
let tableInfo: BotTable[] | undefined;
|
||||
tableInfo = transformBotInfo.database(botInfo);
|
||||
if (tableInfo) {
|
||||
tableInfo = addUidAndIdToBotFieldsIfIsUnlimitedReadWriteMode(tableInfo);
|
||||
}
|
||||
|
||||
return { tableInfo, isLoading };
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 Variable } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import { transformBotInfo, useBotInfo } from './use-bot-info';
|
||||
|
||||
export const useVariables = (botID?: string) => {
|
||||
const { botInfo, isLoading } = useBotInfo(botID);
|
||||
const variables: Variable[] | undefined = transformBotInfo.variable(botInfo);
|
||||
|
||||
return {
|
||||
variables,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 Variable } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import { TagList } from '../tag-list';
|
||||
|
||||
interface VariablesProps {
|
||||
variables: Variable[];
|
||||
}
|
||||
|
||||
export const Variables: React.FC<VariablesProps> = ({ variables }) => {
|
||||
const tagList = variables.map(({ key }) => key ?? '');
|
||||
|
||||
return <TagList tags={tagList} max={5} />;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip, CozInputNumber } from '@coze-arch/coze-design';
|
||||
|
||||
export interface ChatHistoryRoundProps {
|
||||
value?: number;
|
||||
onChange?: (value: number) => void;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
const MIN_ROUND = 1;
|
||||
const MAX_ROUND = 30;
|
||||
|
||||
export const ChatHistoryRound = ({
|
||||
value,
|
||||
onChange,
|
||||
readonly,
|
||||
}: ChatHistoryRoundProps) => (
|
||||
<div className="absolute right-[0] top-[9px] flex items-center gap-[4px]">
|
||||
<span className="text-xs">{I18n.t('wf_history_rounds')}</span>
|
||||
<Tooltip content={I18n.t('model_config_history_round_explain')}>
|
||||
<IconCozInfoCircle className="coz-fg-dim text-xs" />
|
||||
</Tooltip>
|
||||
|
||||
<CozInputNumber
|
||||
className="w-[60px]"
|
||||
size="small"
|
||||
min={MIN_ROUND}
|
||||
max={MAX_ROUND}
|
||||
disabled={readonly}
|
||||
value={value}
|
||||
onChange={w => {
|
||||
if (isNaN(w as number)) {
|
||||
return;
|
||||
}
|
||||
onChange?.(w as number);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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, useEffect } from 'react';
|
||||
|
||||
// import { useMutation } from '@tanstack/react-query';
|
||||
import { EventType, workflowApi } from '@coze-workflow/base/api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { SideSheet } from '@coze-arch/coze-design';
|
||||
import { typeSafeJSONParse } from '@coze-arch/bot-utils';
|
||||
import { UIIconButton } from '@coze-arch/bot-semi';
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { IconClose } from '@douyinfe/semi-icons';
|
||||
|
||||
import { useCancelTestRun } from '../test-run/hooks/use-cancel-test-run';
|
||||
import { WorkflowRunService } from '../../services';
|
||||
import { useExecStateEntity, useGlobalState } from '../../hooks';
|
||||
// import { useGenerateMessageFormInitValue } from './use-generate-message-form-init-value';
|
||||
import { type MessageFormValue } from './types';
|
||||
import { Title } from './title';
|
||||
import { MessageForm } from './message-form';
|
||||
|
||||
export const ChatTestRunPauseSideSheet = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const { workflowId, spaceId } = useGlobalState();
|
||||
|
||||
const { cancelTestRun } = useCancelTestRun();
|
||||
|
||||
const exeState = useExecStateEntity();
|
||||
|
||||
const testrunService = useService<WorkflowRunService>(WorkflowRunService);
|
||||
|
||||
const sceneChatNodeEvent = exeState.getNodeEvent(EventType.SceneChat);
|
||||
|
||||
// const generateMessageFormInitValue = useGenerateMessageFormInitValue();
|
||||
|
||||
// const { mutate } = useMutation({
|
||||
// mutationFn: workflowApi.WorkFlowTestResume,
|
||||
// onSuccess: () => {
|
||||
// debugger;
|
||||
// testrunService.continueTestRun();
|
||||
// setVisible(false);
|
||||
// },
|
||||
// });
|
||||
|
||||
useEffect(() => {
|
||||
if (sceneChatNodeEvent) {
|
||||
testrunService.pauseTestRun();
|
||||
setVisible(true);
|
||||
} else {
|
||||
setVisible(false);
|
||||
}
|
||||
}, [sceneChatNodeEvent]);
|
||||
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
cancelTestRun();
|
||||
};
|
||||
|
||||
const handleMessageFormSubmit = async (values: MessageFormValue) => {
|
||||
await workflowApi.WorkFlowTestResume({
|
||||
workflow_id: workflowId,
|
||||
execute_id: exeState.config.executeId,
|
||||
event_id: sceneChatNodeEvent?.id ?? '',
|
||||
space_id: spaceId,
|
||||
data: JSON.stringify(values),
|
||||
});
|
||||
|
||||
testrunService.continueTestRun();
|
||||
setVisible(false);
|
||||
|
||||
// debugger;
|
||||
// mutate({
|
||||
// workflow_id: workflowId,
|
||||
// execute_id: exeState.config.executeId,
|
||||
// event_id: sceneChatNodeEvent?.id ?? '',
|
||||
// data: JSON.stringify(values),
|
||||
// });
|
||||
};
|
||||
|
||||
if (visible) {
|
||||
return (
|
||||
<SideSheet
|
||||
visible={visible}
|
||||
closable={false}
|
||||
width={600}
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<UIIconButton
|
||||
type="secondary"
|
||||
icon={<IconClose style={{ color: '#1C1D23' }} />}
|
||||
iconSize="large"
|
||||
onClick={handleClose}
|
||||
/>
|
||||
{I18n.t(
|
||||
'scene_workflow_chat_node_test_run_title',
|
||||
{},
|
||||
'Test Q&A nodes',
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
style={{
|
||||
background: '#F7F7FA',
|
||||
}}
|
||||
>
|
||||
<div className="h-full flex flex-col">
|
||||
<Title icon={sceneChatNodeEvent?.node_icon ?? ''} />
|
||||
<div className="flex-1">
|
||||
<MessageForm
|
||||
initValues={{
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
Messages: (typeSafeJSONParse(sceneChatNodeEvent?.data) as any)
|
||||
?.messages,
|
||||
|
||||
// messages:
|
||||
// generateMessageFormInitValue(
|
||||
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// (typeSafeJSONParse(sceneChatNodeEvent?.data) as any)
|
||||
// ?.messages,
|
||||
// ) ?? [],
|
||||
}}
|
||||
onSubmit={handleMessageFormSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SideSheet>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 FC, useRef } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tag } from '@coze-arch/coze-design';
|
||||
import { Form, ArrayField, UIButton, Typography } from '@coze-arch/bot-semi';
|
||||
import { IconPlay } from '@douyinfe/semi-icons';
|
||||
|
||||
import { type MessageFormValue } from './types';
|
||||
|
||||
const { Input } = Form;
|
||||
|
||||
interface MessageFormProps {
|
||||
initValues: MessageFormValue;
|
||||
onSubmit: (values: MessageFormValue) => void;
|
||||
}
|
||||
|
||||
export const MessageForm: FC<MessageFormProps> = props => {
|
||||
const { initValues, onSubmit } = props;
|
||||
const formRef = useRef<Form<MessageFormValue>>(null);
|
||||
|
||||
const renderNickNameLabel = (nickName, roleName) => (
|
||||
<>
|
||||
{`${nickName}`} <Tag>{roleName ? roleName : nickName}</Tag>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Form<MessageFormValue>
|
||||
ref={formRef}
|
||||
onSubmit={onSubmit}
|
||||
className="relative h-full flex flex-col"
|
||||
>
|
||||
<div className="p-6 rounded-lg coz-bg-max coz-stroke-primary flex-1 mb-6 border border-solid">
|
||||
<ArrayField field="Messages" initValue={initValues.Messages}>
|
||||
{({ arrayFields }) => (
|
||||
<>
|
||||
{arrayFields.map(({ field, key, remove }, index) => {
|
||||
if (initValues.Messages[index].content) {
|
||||
return (
|
||||
<div className="py-3">
|
||||
<Form.Label>
|
||||
{renderNickNameLabel(
|
||||
initValues.Messages[index].nickname,
|
||||
initValues.Messages[index].role,
|
||||
)}
|
||||
</Form.Label>
|
||||
|
||||
<div>
|
||||
<Typography.Text>
|
||||
{initValues.Messages[index].content}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Input
|
||||
label={renderNickNameLabel(
|
||||
initValues.Messages[index].nickname,
|
||||
initValues.Messages[index].role,
|
||||
)}
|
||||
field={`${field}[content]`}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Required',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</ArrayField>
|
||||
</div>
|
||||
<div className="mb-6 text-right">
|
||||
<UIButton
|
||||
icon={<IconPlay />}
|
||||
type="primary"
|
||||
theme="solid"
|
||||
htmlType="submit"
|
||||
className="btn-margin-right "
|
||||
>
|
||||
{I18n.t(
|
||||
'scene_workflow_chat_node_test_run_button',
|
||||
{},
|
||||
'Continue running',
|
||||
)}
|
||||
</UIButton>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -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 { type FC } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tag, Typography } from '@coze-arch/coze-design';
|
||||
|
||||
export const Title: FC<{
|
||||
icon: string;
|
||||
}> = props => {
|
||||
const { icon } = props;
|
||||
return (
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<img src={icon} width={16} height={16} />
|
||||
<Typography.Title heading={6}>
|
||||
{I18n.t('scene_workflow_chat_node_name', {}, 'Role scheduling')}
|
||||
</Typography.Title>
|
||||
<Tag color="cyan" loading>
|
||||
{I18n.t('scene_workflow_chat_node_test_run_running', {}, 'Running')}
|
||||
</Tag>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 interface MessageFormValue {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
Messages: Array<MessageValue>;
|
||||
}
|
||||
export interface MessageValue {
|
||||
role: string;
|
||||
content: string;
|
||||
nickname: string;
|
||||
}
|
||||
@@ -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 { useGetSceneFlowRoleList } from '../../hooks/use-get-scene-flow-params';
|
||||
import { type SpeakerMessageSetValue } from '../../form-extensions/setters/speaker-message-set-array/types';
|
||||
import { type MessageValue } from './types';
|
||||
|
||||
export const useGenerateMessageFormInitValue = () => {
|
||||
const { data: roleList } = useGetSceneFlowRoleList();
|
||||
|
||||
return (messages: Array<SpeakerMessageSetValue> | undefined) => {
|
||||
const result = messages?.reduce<Array<MessageValue>>((buf, message) => {
|
||||
const role = roleList?.find(
|
||||
_role => _role.biz_role_id === message.biz_role_id,
|
||||
);
|
||||
if (!role) {
|
||||
return buf;
|
||||
} else {
|
||||
buf.push({
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
nickname: message.nickname ?? '',
|
||||
});
|
||||
return buf;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return result;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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 { createRenderer, option } from '@coze-editor/editor/react';
|
||||
import preset, { languages, themes } from '@coze-editor/editor/preset-code';
|
||||
import { shell } from '@coze-editor/editor/language-shell';
|
||||
import { json } from '@coze-editor/editor/language-json';
|
||||
import { mixLanguages } from '@coze-editor/editor';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
|
||||
import { cozeLight } from './themes/coze-light';
|
||||
import { cozeDark } from './themes/coze-dark';
|
||||
|
||||
// 注册语言
|
||||
languages.register('json', {
|
||||
// mixLanguages 用于解决 「插值也使用了括号,导致无法正确高亮」的问题
|
||||
language: mixLanguages({
|
||||
outerLanguage: json.language,
|
||||
}),
|
||||
languageService: json.languageService,
|
||||
});
|
||||
|
||||
languages.register('shell', shell);
|
||||
|
||||
// 注册主题
|
||||
themes.register('coze-light', cozeLight);
|
||||
themes.register('coze-dark', cozeDark);
|
||||
|
||||
const minHeightOption = (value?: string | number) =>
|
||||
EditorView.theme({
|
||||
'.cm-content, .cm-gutter, .cm-right-gutter': {
|
||||
minHeight:
|
||||
typeof value === 'number'
|
||||
? `${value}px`
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: 'unset',
|
||||
},
|
||||
'&.cm-editor': {
|
||||
minHeight:
|
||||
typeof value === 'number'
|
||||
? `${value}px`
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: 'unset',
|
||||
},
|
||||
});
|
||||
|
||||
const maxHeightOption = (value?: string | number) =>
|
||||
EditorView.theme({
|
||||
'&.cm-editor': {
|
||||
maxHeight:
|
||||
typeof value === 'number'
|
||||
? `${value}px`
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: 'unset',
|
||||
},
|
||||
});
|
||||
|
||||
const heightOption = (value?: string | number) =>
|
||||
EditorView.theme({
|
||||
'&.cm-editor': {
|
||||
height:
|
||||
typeof value === 'number'
|
||||
? `${value}px`
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: 'unset',
|
||||
},
|
||||
});
|
||||
|
||||
const paddingOption = (value?: string | number) =>
|
||||
EditorView.theme({
|
||||
'&.cm-editor': {
|
||||
padding:
|
||||
typeof value === 'number'
|
||||
? `${value}px`
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: 'unset',
|
||||
},
|
||||
});
|
||||
|
||||
const borderRadiusOption = (value?: string | number) =>
|
||||
EditorView.theme({
|
||||
'&.cm-editor, .cm-gutters': {
|
||||
borderRadius:
|
||||
typeof value === 'number'
|
||||
? `${value}px`
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: 'unset',
|
||||
},
|
||||
});
|
||||
|
||||
const lineHeightOption = (value?: string | number) =>
|
||||
EditorView.theme({
|
||||
'.cm-content, .cm-gutter, .cm-right-gutter': {
|
||||
lineHeight:
|
||||
typeof value === 'number'
|
||||
? `${value}px`
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: 'unset',
|
||||
},
|
||||
});
|
||||
|
||||
function createStyleOptions() {
|
||||
return [
|
||||
option('minHeight', minHeightOption),
|
||||
option('maxHeight', maxHeightOption),
|
||||
option('editerHeight', heightOption),
|
||||
option('borderRadius', borderRadiusOption),
|
||||
option('padding', paddingOption),
|
||||
option('lineHeight', lineHeightOption),
|
||||
];
|
||||
}
|
||||
|
||||
const builtinExtensions = [
|
||||
EditorView.theme({
|
||||
'&.cm-focused': {
|
||||
outline: 'none',
|
||||
},
|
||||
'& *': {
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
},
|
||||
}),
|
||||
EditorView.theme({
|
||||
'&.cm-content': {
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
export const CodeEditor = createRenderer(
|
||||
[...preset, ...createStyleOptions()],
|
||||
builtinExtensions,
|
||||
);
|
||||
@@ -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 { EditorProvider } from '@coze-editor/editor/react';
|
||||
export { CodeEditor } from './code-editor';
|
||||
export { TextEditor } from './text-editor';
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 { createRenderer, option } from '@coze-editor/editor/react';
|
||||
import universal from '@coze-editor/editor/preset-universal';
|
||||
import { mixLanguages } from '@coze-editor/editor';
|
||||
import { keymap, EditorView } from '@codemirror/view';
|
||||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
||||
|
||||
const RawEditorTheme = EditorView.theme({
|
||||
'&.cm-editor': {
|
||||
outline: 'none',
|
||||
},
|
||||
'&.cm-content': {
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
});
|
||||
|
||||
const minHeightOption = (value?: string | number) =>
|
||||
EditorView.theme({
|
||||
'.cm-content, .cm-gutter, .cm-right-gutter': {
|
||||
minHeight:
|
||||
typeof value === 'number'
|
||||
? `${value}px`
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: 'unset',
|
||||
},
|
||||
});
|
||||
|
||||
const lineHeightOption = (value?: string | number) =>
|
||||
EditorView.theme({
|
||||
'.cm-content, .cm-gutter, .cm-right-gutter': {
|
||||
lineHeight:
|
||||
typeof value === 'number'
|
||||
? `${value}px`
|
||||
: typeof value === 'string'
|
||||
? value
|
||||
: 'unset',
|
||||
},
|
||||
});
|
||||
|
||||
const extensions = [
|
||||
mixLanguages({}),
|
||||
RawEditorTheme,
|
||||
// ...其他 extensions
|
||||
history(),
|
||||
keymap.of([...defaultKeymap, ...historyKeymap]),
|
||||
];
|
||||
|
||||
export const TextEditor = createRenderer(
|
||||
[
|
||||
...universal,
|
||||
option('minHeight', minHeightOption),
|
||||
option('lineHeight', lineHeightOption),
|
||||
],
|
||||
extensions,
|
||||
);
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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, tags as t } from '@coze-editor/editor/preset-code';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
|
||||
const colors = {
|
||||
background: '#151B27',
|
||||
// syntax
|
||||
comment: '#FFFFFF63',
|
||||
key: '#39E5D7',
|
||||
string: '#FF94D2',
|
||||
number: '#FF9933',
|
||||
boolean: '#78B0FF',
|
||||
null: '#78B0FF',
|
||||
separator: '#FFFFFFC9',
|
||||
};
|
||||
|
||||
export const cozeDark = [
|
||||
EditorView.theme({
|
||||
'.cm-completionIcon-property': {
|
||||
backgroundImage:
|
||||
'url("' +
|
||||
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMWVtIiBoZWlnaHQ9IjFlbSIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMi4zNTc2IDguMTAzNTVDMTIuMTYyMyA3LjkwODI5IDExLjg0NTcgNy45MDgyOSAxMS42NTA1IDguMTAzNTVMOC4xMDM1NSAxMS42NTA1QzcuOTA4MjkgMTEuODQ1NyA3LjkwODI5IDEyLjE2MjMgOC4xMDM1NSAxMi4zNTc2TDExLjY1MDUgMTUuOTA0NUMxMS44NDU3IDE2LjA5OTggMTIuMTYyMyAxNi4wOTk4IDEyLjM1NzYgMTUuOTA0NUwxNS45MDQ1IDEyLjM1NzZDMTYuMDk5OCAxMi4xNjIzIDE2LjA5OTggMTEuODQ1NyAxNS45MDQ1IDExLjY1MDVMMTIuMzU3NiA4LjEwMzU1WiIgZmlsbD0iI0ZGRkZGRkM5Ii8+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMS4wMDI2IDEuNDU1NDVDMTEuNjIxNCAxLjA5ODE4IDEyLjM4MzggMS4wOTgxOCAxMy4wMDI2IDEuNDU1NDVMMjAuNjM4IDUuODYzNzRDMjEuMjU2OCA2LjIyMSAyMS42MzggNi44ODEyNiAyMS42MzggNy41OTU3OVYxNi40MTI0QzIxLjYzOCAxNy4xMjY5IDIxLjI1NjggMTcuNzg3MiAyMC42MzggMTguMTQ0NEwxMy4wMDI2IDIyLjU1MjdDMTIuMzgzOCAyMi45MSAxMS42MjE0IDIyLjkxIDExLjAwMjYgMjIuNTUyN0wzLjM2NzE5IDE4LjE0NDRDMi43NDgzOSAxNy43ODcyIDIuMzY3MTkgMTcuMTI2OSAyLjM2NzE5IDE2LjQxMjRWNy41OTU3OUMyLjM2NzE5IDYuODgxMjYgMi43NDgzOSA2LjIyMTAxIDMuMzY3MTkgNS44NjM3NEwxMS4wMDI2IDEuNDU1NDVaTTEyLjAwMjYgMy4xODc1TDE5LjYzOCA3LjU5NTc5VjE2LjQxMjRMMTIuMDAyNiAyMC44MjA3TDQuMzY3MTkgMTYuNDEyNEw0LjM2NzE5IDcuNTk1NzlMMTIuMDAyNiAzLjE4NzVaIiBmaWxsPSIjRkZGRkZGQzkiLz48L3N2Zz4=' +
|
||||
'")',
|
||||
backgroundSize: '11px 11px',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
width: '11px',
|
||||
height: '11px',
|
||||
},
|
||||
'.cm-completionIcon-property::after': {
|
||||
content: '""',
|
||||
},
|
||||
}),
|
||||
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: [
|
||||
// json
|
||||
{
|
||||
tag: t.comment,
|
||||
color: colors.comment,
|
||||
},
|
||||
{
|
||||
tag: [t.propertyName],
|
||||
color: colors.key,
|
||||
},
|
||||
{
|
||||
tag: [t.string],
|
||||
color: colors.string,
|
||||
},
|
||||
{
|
||||
tag: [t.number],
|
||||
color: colors.number,
|
||||
},
|
||||
{
|
||||
tag: [t.bool],
|
||||
color: colors.boolean,
|
||||
},
|
||||
{
|
||||
tag: [t.null],
|
||||
color: colors.null,
|
||||
},
|
||||
{
|
||||
tag: [t.separator],
|
||||
color: colors.separator,
|
||||
},
|
||||
|
||||
// shell
|
||||
// curl
|
||||
{
|
||||
tag: [t.standard(t.variableName)],
|
||||
color: '#3BEB84',
|
||||
},
|
||||
// -X
|
||||
{
|
||||
tag: [t.attributeName],
|
||||
color: '#FF9933',
|
||||
},
|
||||
// url in string (includes quotes), e.g.
|
||||
{
|
||||
tag: [t.special(t.string)],
|
||||
color: '#78B0FF',
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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, tags as t } from '@coze-editor/editor/preset-code';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
|
||||
const colors = {
|
||||
background: '#F7F7FC',
|
||||
// syntax
|
||||
comment: '#000A298A',
|
||||
key: '#00818C',
|
||||
string: '#D1009D',
|
||||
number: '#C74200',
|
||||
boolean: '#2B57D9',
|
||||
null: '#2B57D9',
|
||||
separator: '#0F1529D1',
|
||||
};
|
||||
|
||||
export const cozeLight = [
|
||||
EditorView.theme({
|
||||
'.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: '""',
|
||||
},
|
||||
}),
|
||||
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: t.comment,
|
||||
color: colors.comment,
|
||||
},
|
||||
{
|
||||
tag: [t.propertyName],
|
||||
color: colors.key,
|
||||
},
|
||||
{
|
||||
tag: [t.string],
|
||||
color: colors.string,
|
||||
},
|
||||
{
|
||||
tag: [t.number],
|
||||
color: colors.number,
|
||||
},
|
||||
{
|
||||
tag: [t.bool],
|
||||
color: colors.boolean,
|
||||
},
|
||||
{
|
||||
tag: [t.null],
|
||||
color: colors.null,
|
||||
},
|
||||
{
|
||||
tag: [t.separator],
|
||||
color: colors.separator,
|
||||
},
|
||||
|
||||
// shell
|
||||
// curl
|
||||
{
|
||||
tag: [t.standard(t.variableName)],
|
||||
color: '#00804A',
|
||||
},
|
||||
// -X
|
||||
{
|
||||
tag: [t.attributeName],
|
||||
color: '#C74200',
|
||||
},
|
||||
// url in string (includes quotes), e.g.
|
||||
{
|
||||
tag: [t.special(t.string)],
|
||||
color: '#2B57D9',
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* 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 { CommentEditorBlock } from '../../type';
|
||||
import { getCozeCom, getCozeCn } from './util';
|
||||
|
||||
export const commentEditorMockBlocks = [
|
||||
{
|
||||
type: 'heading-one',
|
||||
children: [
|
||||
{
|
||||
text: 'Workflow Comment',
|
||||
bold: true,
|
||||
underline: true,
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
text: '[Format]',
|
||||
bold: true,
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
text: 'Bold',
|
||||
bold: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
text: 'Italic',
|
||||
italic: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
text: 'Underline',
|
||||
underline: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
text: 'Strikethrough',
|
||||
strikethrough: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
strikethrough: true,
|
||||
text: 'Mixed',
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
text: '[Quote]',
|
||||
bold: true,
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'block-quote',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'This line should be displayed as a quote.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'block-quote',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Line 2: content.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'block-quote',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Line 3: content.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
text: '[Bullet List]',
|
||||
bold: true,
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'bulleted-list',
|
||||
children: [
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
text: 'item order 1',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
text: 'item order 2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'bulleted-list',
|
||||
children: [
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
text: 'item order 2.1',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
text: 'item order 2.2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
text: 'item order 3',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
text: '[Numbered List]',
|
||||
bold: true,
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'numbered-list',
|
||||
children: [
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
text: 'item order 1',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
text: 'item order 2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list-item',
|
||||
children: [
|
||||
{
|
||||
text: 'item order 3',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
bold: true,
|
||||
text: '[Hyper Link]',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
text: 'Coze 👉🏻 ',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
link: getCozeCom(),
|
||||
text: 'coze.com',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
text: 'Coze for CN 👉🏻 ',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
text: 'coze.cn',
|
||||
link: getCozeCn(),
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
bold: true,
|
||||
text: '[Heading]',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'heading-one',
|
||||
children: [
|
||||
{
|
||||
text: 'Heading 1',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'heading-two',
|
||||
children: [
|
||||
{
|
||||
text: 'Heading 2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'heading-three',
|
||||
children: [
|
||||
{
|
||||
text: 'Heading 3',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'heading-three',
|
||||
children: [
|
||||
{
|
||||
text: 'Heading Formatted',
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as CommentEditorBlock[];
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { getCozeCom, getCozeCn } from './util';
|
||||
|
||||
export const commentEditorMockHTML = `<h1><u><strong>Workflow Comment</strong></u></h1><p><strong>[Format]</strong></p><p><strong>Bold</strong> <em>Italic</em> <u>Underline</u> <del>Strikethrough</del> <del><u><em><strong>Mixed</strong></em></u></del></p><p><strong>[Quote]</strong></p><blockquote><p>This line should be displayed as a quote.</p></blockquote><blockquote><p>Line 2: content.</p></blockquote><blockquote><p>Line 3: content.</p></blockquote><p><strong>[Bullet List]</strong></p><ul><li>item order 1</li><li>item order 2</li><li>item order 3</li></ul><p><strong>[Numbered List]</strong></p><ol><li>item order 1</li><li>item order 2</li><li>item order 3</li></ol><p><strong>[Hyper Link]</strong></p><p>Coze 👉🏻 <a href="${getCozeCom()}">coze.com</a></p><p>Coze for CN 👉🏻 <a href="${getCozeCn()}">coze.cn</a></p><p><strong>[Heading]</strong></p><h1>Heading 1</h1><h2>Heading 2</h2><h3>Heading 3</h3><h3><u><em><strong>Heading Formatted</strong></em></u></h3>`;
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 { commentEditorMockMarkdown } from './markdown';
|
||||
export { commentEditorMockText } from './text';
|
||||
export { commentEditorMockBlocks } from './blocks';
|
||||
export { commentEditorMockHTML } from './html';
|
||||
export { commentEditorMockJSON } from './json';
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { commentEditorMockBlocks } from './blocks';
|
||||
|
||||
export const commentEditorMockJSON = JSON.stringify(commentEditorMockBlocks);
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 { getCozeCom, getCozeCn } from './util';
|
||||
|
||||
export const commentEditorMockMarkdown = `# __**Workflow Comment**__
|
||||
|
||||
**[Format]**
|
||||
|
||||
**Bold** *Italic* __Underline__ ~~Strikethrough~~ ~~__***Mixed***__~~
|
||||
|
||||
**[Quote]**
|
||||
|
||||
> This line should be displayed as a quote.
|
||||
|
||||
> Line 2: content.
|
||||
|
||||
> Line 3: content.
|
||||
|
||||
**[Bullet List]**
|
||||
|
||||
- item order 1
|
||||
- item order 2
|
||||
- item order 3
|
||||
|
||||
**[Numbered List]**
|
||||
|
||||
1. item order 1
|
||||
2. item order 2
|
||||
3. item order 3
|
||||
|
||||
**[Hyper Link]**
|
||||
|
||||
Coze 👉🏻 [coze.com](${getCozeCom()})
|
||||
|
||||
Coze for CN 👉🏻 [coze.cn](${getCozeCn()})
|
||||
|
||||
**[Heading]**
|
||||
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
|
||||
### Heading 3
|
||||
|
||||
### __***Heading Formatted***__`;
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 commentEditorMockText = `Workflow Comment
|
||||
[Format]
|
||||
Bold Italic Underline Strikethrough Mixed
|
||||
[Quote]
|
||||
This line should be displayed as a quote.
|
||||
Line 2: content.
|
||||
Line 3: content.
|
||||
[Bullet List]
|
||||
item order 1
|
||||
item order 2
|
||||
item order 3
|
||||
[Numbered List]
|
||||
item order 1
|
||||
item order 2
|
||||
item order 3
|
||||
[Hyper Link]
|
||||
Coze 👉🏻 coze.com
|
||||
Coze for CN 👉🏻 coze.cn
|
||||
[Heading]
|
||||
Heading 1
|
||||
Heading 2
|
||||
Heading 3
|
||||
Heading Formatted`;
|
||||
@@ -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 const getCozeCom = () => ['https://', 'coze.com'].join('');
|
||||
|
||||
export const getCozeCn = () => ['https://', 'coze.cn'].join('');
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 { describe, it, expect } from 'vitest';
|
||||
|
||||
import { CommentEditorParser } from '../parsers';
|
||||
import {
|
||||
commentEditorMockBlocks,
|
||||
commentEditorMockText,
|
||||
commentEditorMockMarkdown,
|
||||
commentEditorMockHTML,
|
||||
commentEditorMockJSON,
|
||||
} from './mock';
|
||||
|
||||
describe('CommentEditorParser', () => {
|
||||
it('toText', () => {
|
||||
expect(CommentEditorParser.toText(commentEditorMockBlocks)).toBe(
|
||||
commentEditorMockText,
|
||||
);
|
||||
});
|
||||
|
||||
it('toMarkdown', () => {
|
||||
expect(CommentEditorParser.toMarkdown(commentEditorMockBlocks)).toBe(
|
||||
commentEditorMockMarkdown,
|
||||
);
|
||||
});
|
||||
|
||||
it('toHTML', () => {
|
||||
expect(CommentEditorParser.toHTML(commentEditorMockBlocks)).toBe(
|
||||
commentEditorMockHTML,
|
||||
);
|
||||
});
|
||||
|
||||
it('toJSON', () => {
|
||||
expect(CommentEditorParser.toJSON(commentEditorMockBlocks)).toBe(
|
||||
commentEditorMockJSON,
|
||||
);
|
||||
});
|
||||
|
||||
it('fromJSON', () => {
|
||||
expect(CommentEditorParser.fromJSON(commentEditorMockJSON)).toEqual(
|
||||
commentEditorMockBlocks,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 { Editor } from 'slate';
|
||||
|
||||
import type { CommentEditorCommand } from '../type';
|
||||
import type { CommentEditorModel } from '../model';
|
||||
import { CommentEditorBlockFormat } from '../constant';
|
||||
|
||||
// 定义块前缀模式和对应的格式
|
||||
const blockPrefixConfig: Array<[RegExp, CommentEditorBlockFormat]> = [
|
||||
[/^#$/, CommentEditorBlockFormat.HeadingOne],
|
||||
[/^##$/, CommentEditorBlockFormat.HeadingTwo],
|
||||
[/^###$/, CommentEditorBlockFormat.HeadingThree],
|
||||
[/^>$/, CommentEditorBlockFormat.Blockquote],
|
||||
[/^-$/, CommentEditorBlockFormat.BulletedList],
|
||||
[/^\*$/, CommentEditorBlockFormat.BulletedList],
|
||||
[/^1\.$/, CommentEditorBlockFormat.NumberedList],
|
||||
];
|
||||
|
||||
// 删除文本的函数
|
||||
const deleteText = (model: CommentEditorModel, text: string): void => {
|
||||
Array.from(text).forEach(() => {
|
||||
Editor.deleteBackward(model.editor, { unit: 'character' });
|
||||
});
|
||||
};
|
||||
|
||||
// 处理块前缀的函数
|
||||
const handleBlockPrefix = (
|
||||
model: CommentEditorModel,
|
||||
text: string,
|
||||
): boolean => {
|
||||
const matchedConfig = blockPrefixConfig.find(([pattern]) =>
|
||||
pattern.test(text),
|
||||
);
|
||||
|
||||
if (matchedConfig) {
|
||||
const [, format] = matchedConfig;
|
||||
deleteText(model, text);
|
||||
model.markBlock(format);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const blockPrefixCommand: CommentEditorCommand = {
|
||||
key: ' ',
|
||||
exec: ({ model, event }) => {
|
||||
// 检查是否正在输入拼音
|
||||
if (event.nativeEvent.isComposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { before: beforeText } = model.getBlockText();
|
||||
if (!beforeText) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handleBlockPrefix(model, beforeText)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { CommentEditorCommand } from '../type';
|
||||
import { CommentEditorLeafFormat } from '../constant';
|
||||
|
||||
export const boldCommand: CommentEditorCommand = {
|
||||
key: 'b',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
model.markLeaf(CommentEditorLeafFormat.Bold);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 { CommentEditorCommand } from '../type';
|
||||
import { CommentEditorBlockFormat } from '../constant';
|
||||
|
||||
export const clearFormatEnterCommand: CommentEditorCommand = {
|
||||
key: 'Enter',
|
||||
shift: false,
|
||||
exec: ({ model, event }) => {
|
||||
// 检查是否正在输入拼音
|
||||
if (event.nativeEvent.isComposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isEmptyBlock = !model.getBlockText().text;
|
||||
const hasBlockFormat = !model.isBlockMarked(
|
||||
CommentEditorBlockFormat.Paragraph,
|
||||
);
|
||||
|
||||
if (!isEmptyBlock || !hasBlockFormat) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
model.clearFormat();
|
||||
},
|
||||
};
|
||||
|
||||
export const clearFormatBackspaceCommand: CommentEditorCommand = {
|
||||
key: 'Backspace',
|
||||
shift: false,
|
||||
exec: ({ model, event }) => {
|
||||
const isAtBlockStart = !model.getBlockText().before;
|
||||
const hasBlockFormat = !model.isBlockMarked(
|
||||
CommentEditorBlockFormat.Paragraph,
|
||||
);
|
||||
|
||||
if (!isAtBlockStart || !hasBlockFormat) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
model.clearFormat();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 { CommentEditorCommand } from '../type';
|
||||
import { CommentEditorBlockFormat } from '../constant';
|
||||
|
||||
export const headingOneCommand: CommentEditorCommand = {
|
||||
key: '1',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
model.markBlock(CommentEditorBlockFormat.HeadingOne);
|
||||
},
|
||||
};
|
||||
|
||||
export const headingTwoCommand: CommentEditorCommand = {
|
||||
key: '2',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
model.markBlock(CommentEditorBlockFormat.HeadingTwo);
|
||||
},
|
||||
};
|
||||
|
||||
export const headingThreeCommand: CommentEditorCommand = {
|
||||
key: '3',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
model.markBlock(CommentEditorBlockFormat.HeadingThree);
|
||||
},
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export { selectAllCommand } from './select-all';
|
||||
export { boldCommand } from './bold';
|
||||
export { italicCommand } from './italic';
|
||||
export { underlineCommand } from './underline';
|
||||
export { strikethroughCommand } from './strikethrough';
|
||||
export { paragraphCommand } from './paragraph';
|
||||
export {
|
||||
headingOneCommand,
|
||||
headingTwoCommand,
|
||||
headingThreeCommand,
|
||||
} from './heading';
|
||||
export { quoteCommand } from './quote';
|
||||
export {
|
||||
clearFormatEnterCommand,
|
||||
clearFormatBackspaceCommand,
|
||||
} from './clear-format';
|
||||
export { blockPrefixCommand } from './block-prefix';
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { CommentEditorCommand } from '../type';
|
||||
import { CommentEditorLeafFormat } from '../constant';
|
||||
|
||||
export const italicCommand: CommentEditorCommand = {
|
||||
key: 'i',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
model.markLeaf(CommentEditorLeafFormat.Italic);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { CommentEditorCommand } from '../type';
|
||||
import { CommentEditorBlockFormat } from '../constant';
|
||||
|
||||
export const paragraphCommand: CommentEditorCommand = {
|
||||
key: 'o',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
model.markBlock(CommentEditorBlockFormat.Paragraph);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { CommentEditorCommand } from '../type';
|
||||
import { CommentEditorBlockFormat } from '../constant';
|
||||
|
||||
export const quoteCommand: CommentEditorCommand = {
|
||||
key: 'q',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
model.markBlock(CommentEditorBlockFormat.Blockquote);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 { Editor, Transforms } from 'slate';
|
||||
|
||||
import type { CommentEditorCommand } from '../type';
|
||||
|
||||
export const selectAllCommand: CommentEditorCommand = {
|
||||
key: 'a',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
Transforms.select(model.editor, {
|
||||
anchor: Editor.start(model.editor, []),
|
||||
focus: Editor.end(model.editor, []),
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { CommentEditorCommand } from '../type';
|
||||
import { CommentEditorLeafFormat } from '../constant';
|
||||
|
||||
export const strikethroughCommand: CommentEditorCommand = {
|
||||
key: 's',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
model.markLeaf(CommentEditorLeafFormat.Strikethrough);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { CommentEditorCommand } from '../type';
|
||||
import { CommentEditorLeafFormat } from '../constant';
|
||||
|
||||
export const underlineCommand: CommentEditorCommand = {
|
||||
key: 'u',
|
||||
modifier: true,
|
||||
exec: ({ model, event }) => {
|
||||
event.preventDefault();
|
||||
model.markLeaf(CommentEditorLeafFormat.Underline);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 { FC } from 'react';
|
||||
|
||||
import {
|
||||
useNodeRender,
|
||||
usePlayground,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import type { CommentEditorModel } from '../model';
|
||||
import { DragArea } from './drag-area';
|
||||
|
||||
interface IBlankArea {
|
||||
model: CommentEditorModel;
|
||||
}
|
||||
|
||||
export const BlankArea: FC<IBlankArea> = props => {
|
||||
const { model } = props;
|
||||
const playground = usePlayground();
|
||||
const { selectNode } = useNodeRender();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="workflow-comment-blank-area h-full w-full"
|
||||
onMouseDown={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
model.setFocus(false);
|
||||
selectNode(e);
|
||||
playground.node.focus(); // 防止节点无法被删除
|
||||
}}
|
||||
onClick={e => {
|
||||
model.setFocus(true);
|
||||
model.selectEnd();
|
||||
}}
|
||||
>
|
||||
<DragArea
|
||||
className="relative h-full w-full"
|
||||
model={model}
|
||||
stopEvent={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import type { CommentEditorModel } from '../model';
|
||||
import { ResizeArea } from './resize-area';
|
||||
import { DragArea } from './drag-area';
|
||||
|
||||
interface IBorderArea {
|
||||
model: CommentEditorModel;
|
||||
overflow: boolean;
|
||||
onResize?: () => {
|
||||
resizing: (delta: {
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
}) => void;
|
||||
resizeEnd: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const BorderArea: FC<IBorderArea> = props => {
|
||||
const { model, overflow, onResize } = props;
|
||||
|
||||
return (
|
||||
<div className="workflow-comment-border-area z-[999]">
|
||||
{/* 左边 */}
|
||||
<DragArea
|
||||
className="left-[-10px] top-[10px] w-[20px] h-[calc(100%-20px)]"
|
||||
model={model}
|
||||
/>
|
||||
{/* 右边 */}
|
||||
<DragArea
|
||||
className={classNames('right-[-10px] top-[10px] h-[calc(100%-20px)]', {
|
||||
'w-[10px]': overflow, // 防止遮挡滚动条
|
||||
'w-[20px]': !overflow,
|
||||
})}
|
||||
model={model}
|
||||
/>
|
||||
{/* 上边 */}
|
||||
<DragArea
|
||||
className="top-[-10px] left-[10px] w-[calc(100%-20px)] h-[20px]"
|
||||
model={model}
|
||||
/>
|
||||
{/* 下边 */}
|
||||
<DragArea
|
||||
className="bottom-[-10px] left-[10px] w-[calc(100%-20px)] h-[20px]"
|
||||
model={model}
|
||||
/>
|
||||
{/** 左上角 */}
|
||||
<ResizeArea
|
||||
className="left-0 top-0 cursor-nwse-resize"
|
||||
model={model}
|
||||
getDelta={({ x, y }) => ({ top: y, right: 0, bottom: 0, left: x })}
|
||||
onResize={onResize}
|
||||
/>
|
||||
{/** 右上角 */}
|
||||
<ResizeArea
|
||||
className="right-0 top-0 cursor-nesw-resize"
|
||||
model={model}
|
||||
getDelta={({ x, y }) => ({ top: y, right: x, bottom: 0, left: 0 })}
|
||||
onResize={onResize}
|
||||
/>
|
||||
{/** 右下角 */}
|
||||
<ResizeArea
|
||||
className="right-0 bottom-0 cursor-nwse-resize"
|
||||
model={model}
|
||||
getDelta={({ x, y }) => ({ top: 0, right: x, bottom: y, left: 0 })}
|
||||
onResize={onResize}
|
||||
/>
|
||||
{/** 左下角 */}
|
||||
<ResizeArea
|
||||
className="left-0 bottom-0 cursor-nesw-resize"
|
||||
model={model}
|
||||
getDelta={({ x, y }) => ({ top: 0, right: 0, bottom: y, left: x })}
|
||||
onResize={onResize}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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, type FC, type CSSProperties } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface ICommentContainer {
|
||||
focused: boolean;
|
||||
children?: ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const CommentContainer: FC<ICommentContainer> = props => {
|
||||
const { focused, children, style } = props;
|
||||
|
||||
const scrollbarStyle = {
|
||||
// 滚动条样式
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: 'rgb(159 159 158 / 65%) transparent',
|
||||
// 针对 WebKit 浏览器(如 Chrome、Safari)的样式
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '4px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
background: 'transparent',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: 'rgb(159 159 158 / 65%)',
|
||||
borderRadius: '20px',
|
||||
border: '2px solid transparent',
|
||||
},
|
||||
} as unknown as CSSProperties;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'workflow-comment-container flex flex-col items-start justify-start w-full h-full rounded-[8px] outline-solid py-[6px] px-[10px] overflow-y-auto overflow-x-hidden outline-[1px]',
|
||||
{
|
||||
'bg-[#FFF3EA] outline-[#FF811A]': focused,
|
||||
'bg-[#FFFBED] outline-[#F2B600]': !focused,
|
||||
},
|
||||
)}
|
||||
data-flow-editor-selectable="false"
|
||||
style={{
|
||||
// tailwind 不支持 outline 的样式,所以这里需要使用 style 来设置
|
||||
outline: focused ? '1px solid #FF811A' : '1px solid #F2B600',
|
||||
...scrollbarStyle,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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 FC, useState, useEffect, type WheelEventHandler } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useNodeRender,
|
||||
usePlayground,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import type { CommentEditorModel } from '../model';
|
||||
import { DragArea } from './drag-area';
|
||||
|
||||
interface IContentDragArea {
|
||||
model: CommentEditorModel;
|
||||
focused: boolean;
|
||||
overflow: boolean;
|
||||
}
|
||||
|
||||
export const ContentDragArea: FC<IContentDragArea> = props => {
|
||||
const { model, focused, overflow } = props;
|
||||
const playground = usePlayground();
|
||||
const { selectNode } = useNodeRender();
|
||||
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// 当编辑器失去焦点时,取消激活状态
|
||||
if (!focused) {
|
||||
setActive(false);
|
||||
}
|
||||
}, [focused]);
|
||||
|
||||
const handleWheel: WheelEventHandler<HTMLDivElement> = e => {
|
||||
const containerElement = model.element?.parentElement;
|
||||
if (active || !overflow || !containerElement) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
const maxScroll =
|
||||
containerElement.scrollHeight - containerElement.clientHeight;
|
||||
const newScrollTop = Math.min(
|
||||
Math.max(containerElement.scrollTop + e.deltaY, 0),
|
||||
maxScroll,
|
||||
);
|
||||
containerElement.scroll(0, newScrollTop);
|
||||
};
|
||||
|
||||
const handleMouseDown = (mouseDownEvent: React.MouseEvent) => {
|
||||
if (active) {
|
||||
return;
|
||||
}
|
||||
mouseDownEvent.preventDefault();
|
||||
mouseDownEvent.stopPropagation();
|
||||
model.setFocus(false);
|
||||
selectNode(mouseDownEvent);
|
||||
playground.node.focus(); // 防止节点无法被删除
|
||||
|
||||
const startX = mouseDownEvent.clientX;
|
||||
const startY = mouseDownEvent.clientY;
|
||||
|
||||
const handleMouseUp = (mouseMoveEvent: MouseEvent) => {
|
||||
const deltaX = mouseMoveEvent.clientX - startX;
|
||||
const deltaY = mouseMoveEvent.clientY - startY;
|
||||
// 判断是拖拽还是点击
|
||||
const delta = 5;
|
||||
if (Math.abs(deltaX) < delta && Math.abs(deltaY) < delta) {
|
||||
// 点击后隐藏
|
||||
setActive(true);
|
||||
}
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.removeEventListener('click', handleMouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
document.addEventListener('click', handleMouseUp);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'workflow-comment-content-drag-area absolute h-full w-[calc(100%-20px)]',
|
||||
{
|
||||
hidden: active,
|
||||
},
|
||||
)}
|
||||
onMouseDown={handleMouseDown}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<DragArea
|
||||
className="relative h-full w-full"
|
||||
model={model}
|
||||
stopEvent={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useNodeRender,
|
||||
usePlayground,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { type CommentEditorModel } from '../model';
|
||||
|
||||
interface IDragArea {
|
||||
className?: string;
|
||||
model: CommentEditorModel;
|
||||
stopEvent?: boolean;
|
||||
}
|
||||
|
||||
export const DragArea: FC<IDragArea> = props => {
|
||||
const { className = '', model, stopEvent = true } = props;
|
||||
|
||||
const playground = usePlayground();
|
||||
|
||||
const {
|
||||
startDrag: onStartDrag,
|
||||
onFocus,
|
||||
onBlur,
|
||||
selectNode,
|
||||
} = useNodeRender();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'workflow-comment-drag-area',
|
||||
'absolute flex items-center justify-center cursor-move',
|
||||
className,
|
||||
)}
|
||||
data-flow-editor-selectable="false"
|
||||
draggable={true}
|
||||
onMouseDown={e => {
|
||||
if (stopEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
model.setFocus(false);
|
||||
onStartDrag(e);
|
||||
selectNode(e);
|
||||
playground.node.focus(); // 防止节点无法被删除
|
||||
}}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 { CommentEditorBlockFormat } from '../../constant';
|
||||
|
||||
export const Block = ({ attributes, children, element }) => {
|
||||
const style = {
|
||||
textAlign: element.align,
|
||||
color: 'var(--coz-fg-primary, rgba(6, 7, 9, 0.80))',
|
||||
};
|
||||
// 根据元素类型选择对应的 HTML 标签
|
||||
switch (element.type) {
|
||||
case CommentEditorBlockFormat.Paragraph:
|
||||
// 渲染段落
|
||||
return (
|
||||
<p className="text-[12px] m-0 p-0" style={style} {...attributes}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
case CommentEditorBlockFormat.Blockquote:
|
||||
// 渲染引用块
|
||||
return (
|
||||
<blockquote
|
||||
className="border-l-[3px] border-t-0 border-b-0 border-r-0 border-solid border-[#ced0d4] m-0 p-0 pl-[8px] ml-[8px] text-[12px]"
|
||||
style={{
|
||||
...style,
|
||||
color: 'var(--coz-fg-secondary, rgba(32, 41, 69, 0.62))',
|
||||
}}
|
||||
{...attributes}
|
||||
>
|
||||
{children}
|
||||
</blockquote>
|
||||
);
|
||||
case CommentEditorBlockFormat.HeadingOne:
|
||||
// 渲染一级标题
|
||||
return (
|
||||
<h1
|
||||
className="text-[18px] mx-0 my-[6px] p-0 font-[600]"
|
||||
style={style}
|
||||
{...attributes}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
case CommentEditorBlockFormat.HeadingTwo:
|
||||
// 渲染二级标题
|
||||
return (
|
||||
<h2
|
||||
className="text-[16px] mx-0 my-[6px] p-0 font-[600]"
|
||||
style={style}
|
||||
{...attributes}
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
case CommentEditorBlockFormat.HeadingThree:
|
||||
// 渲染三级标题
|
||||
return (
|
||||
<h3
|
||||
className="text-[14px] mx-0 my-[6px] p-0 font-[600]"
|
||||
style={style}
|
||||
{...attributes}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
case CommentEditorBlockFormat.BulletedList:
|
||||
// 渲染无序列表
|
||||
return (
|
||||
<ul
|
||||
className="text-[12px] m-0 p-0 pl-[16px] font-[400]"
|
||||
style={style}
|
||||
{...attributes}
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
case CommentEditorBlockFormat.NumberedList:
|
||||
// 渲染有序列表
|
||||
return (
|
||||
<ol
|
||||
className="text-[12px] m-0 p-0 pl-[16px]"
|
||||
style={style}
|
||||
{...attributes}
|
||||
>
|
||||
{children}
|
||||
</ol>
|
||||
);
|
||||
case CommentEditorBlockFormat.ListItem:
|
||||
// 渲染列表项
|
||||
return (
|
||||
<li className="text-[12px] m-0 p-0" style={style} {...attributes}>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
default:
|
||||
// 默认渲染为段落
|
||||
return (
|
||||
<p className="text-[12px] m-0 p-0" style={style} {...attributes}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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 React, {
|
||||
type FC,
|
||||
useCallback,
|
||||
type CSSProperties,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
|
||||
import { Editable, Slate } from 'slate-react';
|
||||
import type { Descendant } from 'slate';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { usePlayground } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import type { CommentEditorModel } from '../../model';
|
||||
import { CommentEditorEvent } from '../../constant';
|
||||
import { Placeholder } from './placeholder';
|
||||
import { Leaf } from './leaf';
|
||||
import { Block } from './block';
|
||||
|
||||
interface ICommentEditor {
|
||||
model: CommentEditorModel;
|
||||
style?: CSSProperties;
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export const CommentEditor: FC<ICommentEditor> = props => {
|
||||
const { model, style, onChange } = props;
|
||||
const playground = usePlayground();
|
||||
const renderBlock = useCallback(blockProps => <Block {...blockProps} />, []);
|
||||
const renderLeaf = useCallback(leafProps => <Leaf {...leafProps} />, []);
|
||||
|
||||
// 同步编辑器内部值变化
|
||||
useEffect(() => {
|
||||
const dispose = model.on<CommentEditorEvent.Change>(
|
||||
CommentEditorEvent.Change,
|
||||
() => {
|
||||
onChange?.(model.value);
|
||||
},
|
||||
);
|
||||
return () => dispose();
|
||||
}, [model, onChange]);
|
||||
|
||||
return (
|
||||
<Slate
|
||||
editor={model.editor}
|
||||
initialValue={model.blocks as unknown as Descendant[]}
|
||||
onChange={() => model.fireChange()}
|
||||
>
|
||||
<Editable
|
||||
className="workflow-comment-editor w-full cursor-text"
|
||||
spellCheck
|
||||
readOnly={playground.config.readonly}
|
||||
renderElement={renderBlock}
|
||||
renderLeaf={renderLeaf}
|
||||
onKeyDown={e => model.keydown(e)}
|
||||
onPaste={e => model.paste(e)}
|
||||
style={style}
|
||||
placeholder={I18n.t('workflow_note_placeholder')}
|
||||
renderPlaceholder={p => <Placeholder {...p} />}
|
||||
/>
|
||||
</Slate>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 { CommentEditorLeafFormat, CommentDefaultLink } from '../../constant';
|
||||
|
||||
export const Leaf = ({ attributes, children, leaf }) => {
|
||||
if (leaf[CommentEditorLeafFormat.Bold]) {
|
||||
children = <strong>{children}</strong>;
|
||||
}
|
||||
|
||||
if (leaf[CommentEditorLeafFormat.Strikethrough]) {
|
||||
children = <del>{children}</del>;
|
||||
}
|
||||
|
||||
if (leaf[CommentEditorLeafFormat.Italic]) {
|
||||
children = <em>{children}</em>;
|
||||
}
|
||||
|
||||
if (leaf[CommentEditorLeafFormat.Underline]) {
|
||||
children = <u>{children}</u>;
|
||||
}
|
||||
|
||||
if (leaf[CommentEditorLeafFormat.Link]) {
|
||||
children = (
|
||||
<a
|
||||
className="text-[var(--semi-color-link)] cursor-pointer"
|
||||
href={leaf[CommentEditorLeafFormat.Link]}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const link: string = leaf[CommentEditorLeafFormat.Link];
|
||||
if (link === CommentDefaultLink) {
|
||||
// 如果链接为默认链接,直接打开
|
||||
return window.open(link, '_blank');
|
||||
} else if (/^([a-zA-Z][a-zA-Z\d+\-.]*):\/\//.test(link)) {
|
||||
// 如果已经包含合法的协议,直接打开
|
||||
return window.open(link, '_blank');
|
||||
} else {
|
||||
// 如果没有合法协议,添加 https 协议头
|
||||
// cp-disable-next-line
|
||||
return window.open(`https://${link}`, '_blank');
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return <span {...attributes}>{children}</span>;
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 FC } from 'react';
|
||||
|
||||
import { type RenderPlaceholderProps } from 'slate-react';
|
||||
|
||||
export const Placeholder: FC<RenderPlaceholderProps> = props => {
|
||||
const { children, attributes } = props;
|
||||
return (
|
||||
<div
|
||||
{...attributes}
|
||||
className="workflow-comment-editor-placeholder text-[12px] text-[var(--coz-fg-dim)] overflow-hidden absolute pointer-events-none w-full select-none decoration-clone"
|
||||
style={
|
||||
{
|
||||
// 覆盖 slate 内置样式
|
||||
}
|
||||
}
|
||||
>
|
||||
<p>{children}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 { CommentRender } from './render';
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 { ErrorBoundary } from 'react-error-boundary';
|
||||
import { type FC } from 'react';
|
||||
|
||||
import {
|
||||
Field,
|
||||
type FieldRenderProps,
|
||||
FlowNodeFormData,
|
||||
Form,
|
||||
type FormModelV2,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
useNodeRender,
|
||||
type WorkflowNodeEntity,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { useSize, useModel, useOverflow } from '../hooks';
|
||||
import { CommentEditorFormField } from '../constant';
|
||||
import { CommentToolbarContainer } from './toolbar-container';
|
||||
import { CommentEditor } from './editor';
|
||||
import { ContentDragArea } from './content-drag-area';
|
||||
import { CommentContainer } from './container';
|
||||
import { BorderArea } from './border-area';
|
||||
import { BlankArea } from './blank-area';
|
||||
|
||||
export const CommentRender: FC<{
|
||||
node: WorkflowNodeEntity;
|
||||
}> = props => {
|
||||
const { node } = props;
|
||||
const model = useModel();
|
||||
|
||||
const { selected: focused, selectNode, nodeRef } = useNodeRender();
|
||||
|
||||
const formModel = node.getData(FlowNodeFormData).getFormModel<FormModelV2>();
|
||||
const formControl = formModel?.formControl;
|
||||
|
||||
const { width, height, onResize } = useSize();
|
||||
const { overflow, updateOverflow } = useOverflow({ model, height });
|
||||
|
||||
return (
|
||||
<div
|
||||
className="workflow-comment w-auto h-auto min-w-[120px] min-h-[80px]"
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
}}
|
||||
ref={nodeRef}
|
||||
data-node-selected={String(focused)}
|
||||
onMouseEnter={updateOverflow}
|
||||
onMouseDown={e => {
|
||||
setTimeout(() => {
|
||||
// 防止 selectNode 拦截事件,导致 slate 编辑器无法聚焦
|
||||
selectNode(e);
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- delay
|
||||
}, 20);
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<p className="text-red-500">ERROR: Workflow Comment Form Error</p>
|
||||
}
|
||||
>
|
||||
<Form control={formControl}>
|
||||
<>
|
||||
{/* 背景 */}
|
||||
<CommentContainer focused={focused} style={{ height }}>
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<p className="text-red-500">
|
||||
ERROR: Workflow Comment Render Failed
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<Field name={CommentEditorFormField.Note}>
|
||||
{({ field }: FieldRenderProps<string>) => (
|
||||
<>
|
||||
{/** 编辑器 */}
|
||||
<CommentEditor
|
||||
model={model}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
{/* 空白区域 */}
|
||||
<BlankArea model={model} />
|
||||
{/* 内容拖拽区域(点击后隐藏) */}
|
||||
<ContentDragArea
|
||||
model={model}
|
||||
focused={focused}
|
||||
overflow={overflow}
|
||||
/>
|
||||
{/* 工具栏 */}
|
||||
<CommentToolbarContainer
|
||||
model={model}
|
||||
containerRef={nodeRef}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</ErrorBoundary>
|
||||
</CommentContainer>
|
||||
{/* 边框 */}
|
||||
<BorderArea model={model} overflow={overflow} onResize={onResize} />
|
||||
</>
|
||||
</Form>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useNodeRender,
|
||||
usePlayground,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import type { CommentEditorModel } from '../model';
|
||||
|
||||
interface IResizeArea {
|
||||
className?: string;
|
||||
model: CommentEditorModel;
|
||||
onResize?: () => {
|
||||
resizing: (delta: {
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
}) => void;
|
||||
resizeEnd: () => void;
|
||||
};
|
||||
getDelta?: (delta: { x: number; y: number }) => {
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const ResizeArea: FC<IResizeArea> = props => {
|
||||
const { className = '', model, onResize, getDelta } = props;
|
||||
|
||||
const playground = usePlayground();
|
||||
|
||||
const { selectNode } = useNodeRender();
|
||||
|
||||
const handleMouseDown = (mouseDownEvent: React.MouseEvent) => {
|
||||
mouseDownEvent.preventDefault();
|
||||
mouseDownEvent.stopPropagation();
|
||||
if (!onResize) {
|
||||
return;
|
||||
}
|
||||
const { resizing, resizeEnd } = onResize();
|
||||
model.setFocus(false);
|
||||
selectNode(mouseDownEvent);
|
||||
playground.node.focus(); // 防止节点无法被删除
|
||||
|
||||
const startX = mouseDownEvent.clientX;
|
||||
const startY = mouseDownEvent.clientY;
|
||||
|
||||
const handleMouseMove = (mouseMoveEvent: MouseEvent) => {
|
||||
const deltaX = mouseMoveEvent.clientX - startX;
|
||||
const deltaY = mouseMoveEvent.clientY - startY;
|
||||
const delta = getDelta?.({ x: deltaX, y: deltaY });
|
||||
if (!delta || !resizing) {
|
||||
return;
|
||||
}
|
||||
resizing(delta);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
resizeEnd();
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.removeEventListener('click', handleMouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
document.addEventListener('click', handleMouseUp);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
'workflow-comment-resize-area absolute w-[10px] h-[10px]',
|
||||
)}
|
||||
data-flow-editor-selectable="false"
|
||||
onMouseDown={handleMouseDown}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 FC, type RefObject, useRef } from 'react';
|
||||
|
||||
import { CommentToolbar } from '../toolbar';
|
||||
import type { CommentEditorModel } from '../../model';
|
||||
import { usePosition } from './use-position';
|
||||
import { useActivate } from './use-activate';
|
||||
import { Portal } from './portal';
|
||||
|
||||
interface ICommentToolbar {
|
||||
disabled?: boolean;
|
||||
model: CommentEditorModel;
|
||||
containerRef: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const CommentToolbarContainer: FC<ICommentToolbar> = props => {
|
||||
const { disabled = false, model, containerRef } = props;
|
||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||
const shouldRender = !!containerRef?.current;
|
||||
const activated = useActivate({ model, toolbarRef });
|
||||
const position = usePosition({ model, containerRef });
|
||||
const visible = activated && Boolean(position);
|
||||
|
||||
if (!shouldRender || disabled) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal container={containerRef.current}>
|
||||
<div
|
||||
className="workflow-comment-toolbar-container absolute z-[1000]"
|
||||
ref={toolbarRef}
|
||||
style={{
|
||||
display: visible ? 'flex' : 'none',
|
||||
top: position?.y,
|
||||
left: position?.x,
|
||||
}}
|
||||
>
|
||||
<CommentToolbar
|
||||
model={model}
|
||||
containerRef={containerRef}
|
||||
visible={visible}
|
||||
/>
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
@@ -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 ReactDOM from 'react-dom';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
interface IPortal {
|
||||
container: HTMLElement;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const Portal = ({ children, container }: IPortal) =>
|
||||
typeof document === 'object'
|
||||
? ReactDOM.createPortal(children, container)
|
||||
: null;
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 @typescript-eslint/naming-convention -- inner value */
|
||||
/* eslint-disable react-hooks/exhaustive-deps -- init */
|
||||
import { type RefObject, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import type { CommentEditorModel } from '../../model';
|
||||
import { CommentEditorEvent, CommentToolbarDisplayDelay } from '../../constant';
|
||||
|
||||
export const useActivate = (params: {
|
||||
model: CommentEditorModel;
|
||||
toolbarRef: RefObject<HTMLDivElement>;
|
||||
}) => {
|
||||
const { model, toolbarRef } = params;
|
||||
const [activated, _setActivated] = useState(false);
|
||||
|
||||
const setActivated = useCallback(
|
||||
debounce((active: boolean) => {
|
||||
_setActivated(active);
|
||||
}, CommentToolbarDisplayDelay),
|
||||
[],
|
||||
);
|
||||
|
||||
// 清理 debounce
|
||||
useEffect(() => () => setActivated.cancel(), [setActivated]);
|
||||
|
||||
// 监听处理 model 事件
|
||||
useEffect(() => {
|
||||
const eventHandlers = {
|
||||
[CommentEditorEvent.MultiSelect]: () => setActivated(true),
|
||||
[CommentEditorEvent.Select]: () => setActivated(false),
|
||||
[CommentEditorEvent.Change]: () => setActivated(false),
|
||||
[CommentEditorEvent.Blur]: () => setActivated(false),
|
||||
};
|
||||
|
||||
const disposers = Object.entries(eventHandlers).map(([event, handler]) =>
|
||||
model.on(event as CommentEditorEvent, handler),
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposers.forEach(dispose => dispose());
|
||||
};
|
||||
}, [model, setActivated]);
|
||||
|
||||
// 鼠标事件处理
|
||||
useEffect(() => {
|
||||
const mouseHandler = (e: MouseEvent) => {
|
||||
if (
|
||||
!toolbarRef.current ||
|
||||
toolbarRef.current.contains(e.target as Node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setActivated(false);
|
||||
};
|
||||
|
||||
window.addEventListener('mousedown', mouseHandler);
|
||||
return () => window.removeEventListener('mousedown', mouseHandler);
|
||||
}, [toolbarRef, setActivated]);
|
||||
|
||||
return activated;
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 RefObject, useState, useCallback, useEffect } from 'react';
|
||||
|
||||
import { ReactEditor } from 'slate-react';
|
||||
import { type PositionSchema, usePlayground } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import type { CommentEditorModel } from '../../model';
|
||||
import { CommentEditorEvent } from '../../constant';
|
||||
|
||||
export const usePosition = (params: {
|
||||
model: CommentEditorModel;
|
||||
containerRef: RefObject<HTMLDivElement>;
|
||||
}): PositionSchema | undefined => {
|
||||
const { model, containerRef } = params;
|
||||
const playground = usePlayground();
|
||||
|
||||
const [position, setPosition] = useState<PositionSchema | undefined>();
|
||||
|
||||
const calcPosition = useCallback(() => {
|
||||
const containerRect = containerRef?.current?.getBoundingClientRect();
|
||||
if (!model.editor.selection || !containerRect) {
|
||||
setPosition(undefined);
|
||||
return;
|
||||
}
|
||||
const selectionRect = ReactEditor.toDOMRange(
|
||||
model.editor,
|
||||
model.editor.selection,
|
||||
).getBoundingClientRect();
|
||||
|
||||
const { zoom } = playground.config;
|
||||
const lineHeight = 24;
|
||||
|
||||
const selectionWidth = selectionRect.width / zoom;
|
||||
const selectionX = selectionRect.left / zoom;
|
||||
const selectionY = selectionRect.top / zoom;
|
||||
const containerX = containerRect.left / zoom;
|
||||
const containerY = containerRect.top / zoom;
|
||||
|
||||
const positionX = selectionX - containerX + selectionWidth / 2;
|
||||
const positionY = selectionY - containerY - lineHeight;
|
||||
|
||||
const selectionPosition: PositionSchema = {
|
||||
x: positionX,
|
||||
y: positionY,
|
||||
};
|
||||
setPosition(selectionPosition);
|
||||
}, [containerRef, model.editor, playground.config]);
|
||||
|
||||
useEffect(() => {
|
||||
calcPosition();
|
||||
const multiSelectDispose = model.on<CommentEditorEvent.MultiSelect>(
|
||||
CommentEditorEvent.MultiSelect,
|
||||
() => {
|
||||
calcPosition();
|
||||
},
|
||||
);
|
||||
const changeDispose = model.on<CommentEditorEvent.Change>(
|
||||
CommentEditorEvent.Change,
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
calcPosition();
|
||||
}, 20);
|
||||
},
|
||||
);
|
||||
return () => {
|
||||
multiSelectDispose();
|
||||
changeDispose();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- init
|
||||
}, []);
|
||||
|
||||
return position;
|
||||
};
|
||||
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* 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 max-lines-per-function -- no need to split */
|
||||
import { type RefObject, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tooltip } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
IconCozArrowDown,
|
||||
IconCozBold,
|
||||
IconCozQuotation,
|
||||
IconCozH1,
|
||||
IconCozH2,
|
||||
IconCozH3,
|
||||
IconCozItalic,
|
||||
IconCozLink,
|
||||
IconCozListDisorder,
|
||||
IconCozListOrder,
|
||||
IconCozStrikethrough,
|
||||
IconCozTextStyle,
|
||||
IconCozUnderscore,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Dropdown, Input } from '@coze-arch/coze-design';
|
||||
|
||||
import { type CommentEditorModel } from '../model';
|
||||
import {
|
||||
CommentEditorBlockFormat,
|
||||
CommentEditorLeafFormat,
|
||||
CommentDefaultLink,
|
||||
} from '../constant';
|
||||
|
||||
interface ICommentToolbar {
|
||||
model: CommentEditorModel;
|
||||
containerRef: RefObject<HTMLDivElement>;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export const CommentToolbar: FC<ICommentToolbar> = props => {
|
||||
const { model, containerRef } = props;
|
||||
|
||||
const linkValue = model.getLeafValue(CommentEditorLeafFormat.Link) as string;
|
||||
const linkDisplay = linkValue === CommentDefaultLink ? '' : linkValue;
|
||||
|
||||
const tooltipDelay = 1000;
|
||||
const toolItemClass =
|
||||
'flex items-center gap-[2px] w-auto h-[24px] p-[4px] rounded-[6px] hover:bg-[var(--coz-mg-primary)] cursor-pointer select-none';
|
||||
const toolItemMarkedClass =
|
||||
'text-[var(--coz-fg-hglt)] bg-[var(--coz-mg-hglt)] hover:bg-[var(--coz-mg-hglt-hovered)]';
|
||||
const separatorClass = 'w-[1px] h-[16px] bg-[var(--coz-stroke-primary)]';
|
||||
const textItemClass =
|
||||
'w-full h-[32px] p-[8px] text-[14px] flex gap-[8px] items-center rounded-[5px] cursor-pointer select-none hover:bg-[var(--coz-mg-primary)]';
|
||||
const textItemMarkedClass =
|
||||
'text-[var(--coz-fg-hglt)] bg-[var(--coz-mg-hglt)] hover:bg-[var(--coz-mg-hglt-hovered)]';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'workflow-comment-toolbar absolute left-[-140px]',
|
||||
model.isLeafMarked(CommentEditorLeafFormat.Link)
|
||||
? 'top-[-48px]'
|
||||
: 'top-[-14px]',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="flex items-center gap-[3px] h-[32px] p-[4px] w-auto bg-[var(--coz-bg-max)] rounded-[8px] border-[1px] border-solid border-[var(--coz-stroke-primary)]"
|
||||
style={{
|
||||
boxShadow:
|
||||
'0px 4px 12px 0px rgba(0, 0, 0, 0.08), 0px 8px 24px 0px rgba(0, 0, 0, 0.04)',
|
||||
}}
|
||||
onMouseDown={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{/** 文本样式 */}
|
||||
<Dropdown
|
||||
position="bottom"
|
||||
trigger="hover"
|
||||
clickToHide
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- must exist
|
||||
getPopupContainer={() => containerRef.current!}
|
||||
render={
|
||||
<div
|
||||
className="flex flex-col gap-[2px] justify-start items-center w-[120px] p-[4px] rounded-[8px] border-[0.5px] border-solid border-[var(--coz-stroke-primary)] bg-[var(--coz-bg-max)]"
|
||||
style={{
|
||||
boxShadow:
|
||||
'0px 8px 24px 0px rgba(0, 0, 0, 0.16), 0px 16px 48px 0px rgba(0, 0, 0, 0.08)',
|
||||
}}
|
||||
>
|
||||
{/** 正文 */}
|
||||
<div
|
||||
className={classNames(textItemClass, {
|
||||
[textItemMarkedClass]: model.isBlockMarked(
|
||||
CommentEditorBlockFormat.Paragraph,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markBlock(CommentEditorBlockFormat.Paragraph);
|
||||
}}
|
||||
>
|
||||
<IconCozTextStyle className="text-xxl" />
|
||||
<p>{I18n.t('workflow_note_main_text')}</p>
|
||||
</div>
|
||||
{/** 一级标题 */}
|
||||
<div
|
||||
className={classNames(textItemClass, {
|
||||
[textItemMarkedClass]: model.isBlockMarked(
|
||||
CommentEditorBlockFormat.HeadingOne,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markBlock(CommentEditorBlockFormat.HeadingOne);
|
||||
}}
|
||||
>
|
||||
<IconCozH1 className="text-xxl" />
|
||||
<p>{I18n.t('workflow_note_heading')} 1</p>
|
||||
</div>
|
||||
{/** 二级标题 */}
|
||||
<div
|
||||
className={classNames(textItemClass, {
|
||||
[textItemMarkedClass]: model.isBlockMarked(
|
||||
CommentEditorBlockFormat.HeadingTwo,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markBlock(CommentEditorBlockFormat.HeadingTwo);
|
||||
}}
|
||||
>
|
||||
<IconCozH2 className="text-xxl" />
|
||||
<p>{I18n.t('workflow_note_heading')} 2</p>
|
||||
</div>
|
||||
{/** 三级标题 */}
|
||||
<div
|
||||
className={classNames(textItemClass, {
|
||||
[textItemMarkedClass]: model.isBlockMarked(
|
||||
CommentEditorBlockFormat.HeadingThree,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markBlock(CommentEditorBlockFormat.HeadingThree);
|
||||
}}
|
||||
>
|
||||
<IconCozH3 className="text-xxl" />
|
||||
<p>{I18n.t('workflow_note_heading')} 3</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span className={toolItemClass}>
|
||||
<IconCozTextStyle />
|
||||
<IconCozArrowDown />
|
||||
</span>
|
||||
</Dropdown>
|
||||
|
||||
<div className={separatorClass} />
|
||||
|
||||
{/** 粗体 */}
|
||||
<Tooltip
|
||||
content={I18n.t('workflow_note_bold')}
|
||||
mouseEnterDelay={tooltipDelay}
|
||||
>
|
||||
<span
|
||||
className={classNames(toolItemClass, {
|
||||
[toolItemMarkedClass]: model.isLeafMarked(
|
||||
CommentEditorLeafFormat.Bold,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markLeaf(CommentEditorLeafFormat.Bold);
|
||||
}}
|
||||
>
|
||||
<IconCozBold />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{/** 斜体 */}
|
||||
<Tooltip
|
||||
content={I18n.t('workflow_note_italic')}
|
||||
mouseEnterDelay={tooltipDelay}
|
||||
>
|
||||
<span
|
||||
className={classNames(toolItemClass, {
|
||||
[toolItemMarkedClass]: model.isLeafMarked(
|
||||
CommentEditorLeafFormat.Italic,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markLeaf(CommentEditorLeafFormat.Italic);
|
||||
}}
|
||||
>
|
||||
<IconCozItalic />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{/** 下划线 */}
|
||||
<Tooltip
|
||||
content={I18n.t('workflow_note_underline')}
|
||||
mouseEnterDelay={tooltipDelay}
|
||||
>
|
||||
<span
|
||||
className={classNames(toolItemClass, {
|
||||
[toolItemMarkedClass]: model.isLeafMarked(
|
||||
CommentEditorLeafFormat.Underline,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markLeaf(CommentEditorLeafFormat.Underline);
|
||||
}}
|
||||
>
|
||||
<IconCozUnderscore />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{/** 删除线 */}
|
||||
<Tooltip
|
||||
content={I18n.t('workflow_note_strikethrough')}
|
||||
mouseEnterDelay={tooltipDelay}
|
||||
>
|
||||
<span
|
||||
className={classNames(toolItemClass, {
|
||||
[toolItemMarkedClass]: model.isLeafMarked(
|
||||
CommentEditorLeafFormat.Strikethrough,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markLeaf(CommentEditorLeafFormat.Strikethrough);
|
||||
}}
|
||||
>
|
||||
<IconCozStrikethrough />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<div className={separatorClass} />
|
||||
|
||||
{/** 无序列表 */}
|
||||
<Tooltip
|
||||
content={I18n.t('workflow_note_bulleted_list')}
|
||||
mouseEnterDelay={tooltipDelay}
|
||||
>
|
||||
<span
|
||||
className={classNames(toolItemClass, {
|
||||
[toolItemMarkedClass]: model.isBlockMarked(
|
||||
CommentEditorBlockFormat.BulletedList,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markBlock(CommentEditorBlockFormat.BulletedList);
|
||||
}}
|
||||
>
|
||||
<IconCozListDisorder />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{/** 有序列表 */}
|
||||
<Tooltip
|
||||
content={I18n.t('workflow_note_numbered_list')}
|
||||
mouseEnterDelay={tooltipDelay}
|
||||
>
|
||||
<span
|
||||
className={classNames(toolItemClass, {
|
||||
[toolItemMarkedClass]: model.isBlockMarked(
|
||||
CommentEditorBlockFormat.NumberedList,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markBlock(CommentEditorBlockFormat.NumberedList);
|
||||
}}
|
||||
>
|
||||
<IconCozListOrder />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{/** 引用 */}
|
||||
<Tooltip
|
||||
content={I18n.t('workflow_note_quote')}
|
||||
mouseEnterDelay={tooltipDelay}
|
||||
>
|
||||
<span
|
||||
className={classNames(toolItemClass, {
|
||||
[toolItemMarkedClass]: model.isBlockMarked(
|
||||
CommentEditorBlockFormat.Blockquote,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
model.markBlock(CommentEditorBlockFormat.Blockquote);
|
||||
}}
|
||||
>
|
||||
<IconCozQuotation />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<div className={separatorClass} />
|
||||
|
||||
{/** 链接 */}
|
||||
<Tooltip
|
||||
content={I18n.t('workflow_note_hyperlink')}
|
||||
mouseEnterDelay={tooltipDelay}
|
||||
>
|
||||
<span
|
||||
className={classNames(toolItemClass, {
|
||||
[toolItemMarkedClass]: model.isLeafMarked(
|
||||
CommentEditorLeafFormat.Link,
|
||||
),
|
||||
})}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
model.markLeaf(CommentEditorLeafFormat.Link, CommentDefaultLink);
|
||||
}}
|
||||
>
|
||||
<IconCozLink />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
className="w-full overflow-hidden absolute top-[34px]"
|
||||
style={{
|
||||
display: model.isLeafMarked(CommentEditorLeafFormat.Link)
|
||||
? 'flex'
|
||||
: 'none',
|
||||
}}
|
||||
onMouseDown={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
// cp-disable-next-line
|
||||
placeholder="https://"
|
||||
style={{
|
||||
// 这里需要覆盖样式,tailwind class优先级不够高
|
||||
backgroundColor: '#fff',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
value={linkDisplay}
|
||||
onChange={value => {
|
||||
model.setLeafValue(
|
||||
CommentEditorLeafFormat.Link,
|
||||
value || CommentDefaultLink,
|
||||
);
|
||||
}}
|
||||
></Input>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 @typescript-eslint/naming-convention -- enum */
|
||||
|
||||
export enum CommentEditorFormField {
|
||||
Size = 'size',
|
||||
Note = 'note',
|
||||
}
|
||||
|
||||
/** 编辑器事件 */
|
||||
export enum CommentEditorEvent {
|
||||
/** 内容变更事件 */
|
||||
Change = 'change',
|
||||
/** 多选事件 */
|
||||
MultiSelect = 'multiSelect',
|
||||
/** 单选事件 */
|
||||
Select = 'select',
|
||||
/** 失焦事件 */
|
||||
Blur = 'blur',
|
||||
}
|
||||
|
||||
/** 编辑器块格式 */
|
||||
export enum CommentEditorBlockFormat {
|
||||
/** 段落 */
|
||||
Paragraph = 'paragraph',
|
||||
/** 标题一 */
|
||||
HeadingOne = 'heading-one',
|
||||
/** 标题二 */
|
||||
HeadingTwo = 'heading-two',
|
||||
/** 标题三 */
|
||||
HeadingThree = 'heading-three',
|
||||
/** 引用 */
|
||||
Blockquote = 'block-quote',
|
||||
/** 无序列表 */
|
||||
BulletedList = 'bulleted-list',
|
||||
/** 有序列表 */
|
||||
NumberedList = 'numbered-list',
|
||||
/** 列表项 */
|
||||
ListItem = 'list-item',
|
||||
}
|
||||
|
||||
export const CommentEditorListBlockFormat = [
|
||||
CommentEditorBlockFormat.BulletedList,
|
||||
CommentEditorBlockFormat.NumberedList,
|
||||
];
|
||||
|
||||
export const CommentEditorLeafType = 'text';
|
||||
|
||||
/** 编辑器叶子节点格式 */
|
||||
export enum CommentEditorLeafFormat {
|
||||
/** 粗体 */
|
||||
Bold = 'bold',
|
||||
/** 斜体 */
|
||||
Italic = 'italic',
|
||||
/** 下划线 */
|
||||
Underline = 'underline',
|
||||
/** 删除线 */
|
||||
Strikethrough = 'strikethrough',
|
||||
/** 链接 */
|
||||
Link = 'link',
|
||||
}
|
||||
|
||||
/** 编辑器默认块 */
|
||||
export const CommentEditorDefaultBlocks = [
|
||||
{
|
||||
type: CommentEditorBlockFormat.Paragraph,
|
||||
children: [{ text: '' }],
|
||||
},
|
||||
];
|
||||
|
||||
/** 编辑器默认值 */
|
||||
export const CommentEditorDefaultValue = JSON.stringify(
|
||||
CommentEditorDefaultBlocks,
|
||||
);
|
||||
|
||||
/** 工具栏显示延迟 */
|
||||
export const CommentToolbarDisplayDelay = 200;
|
||||
|
||||
/** 默认链接 */
|
||||
export const CommentDefaultLink = 'about:blank';
|
||||
@@ -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 { useModel } from './use-model';
|
||||
export { useSize } from './use-size';
|
||||
export { useOverflow } from './use-overflow';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user