feat: manually mirror opencoze's code from bytedance

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

View File

@@ -0,0 +1,239 @@
/*
* 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 copy from 'copy-to-clipboard';
import { BotE2e } from '@coze-data/e2e';
import { I18n, getUnReactiveLanguage } from '@coze-arch/i18n';
import { UITag, Toast, Tooltip, Image, Typography } from '@coze-arch/bot-semi';
import { IconCopy } from '@coze-arch/bot-icons';
import {
KnowledgeShowSourceMode,
KnowledgeNoRecallReplyMode,
} from '@coze-arch/bot-api/playground_api';
import ZhCustomizePromptPNG from '../assets/customize-prompt-zh.png';
import EnCustomizePromptPNG from '../assets/customize-prompt-en.png';
import { type RadioItem } from './radio-group-setting';
import styles from './index.module.less';
export const MAX_TOP_K_VALUE = 5;
export const FULL_TEXT_SEARCH_KEY = 20;
export const getSearchStrategyOptions = () => [
{
label: I18n.t('knowledge_hybird_search_title'),
value: 1,
tip: I18n.t('knowledge_hybird_search_tooltip'),
},
{
label: I18n.t('knowledge_semantic_search_title'),
value: 0,
tip: I18n.t('knowledge_semantic_search_tooltip'),
},
{
label: I18n.t('knowledge_full_text_search_title'),
value: 20,
tip: I18n.t('knowledge_full_text_search_tooltip'),
},
];
export const getAutomaticCallOptions = () => {
const onCopy = (text: string) => {
const res = copy(text);
if (!res) {
return;
}
Toast.success({
content: I18n.t('copy_success'),
showClose: false,
});
};
return [
{
e2e: BotE2e.BotKnowledgeSettingModalAutoRadio,
label: I18n.t('dataset_automatic_call'),
value: 1,
},
{
e2e: BotE2e.BotKnowledgeSettingModalManualRadio,
label: I18n.t('dataset_on_demand_call'),
value: 0,
desc: (
<>
{I18n.t('bot_edit_dataset_on_demand_prompt1')}
<Tooltip content={I18n.t('bot_edit_datasets_copyName')}>
<UITag
onClick={() => onCopy(I18n.t('dataset_recall_copy_value'))}
type="light"
className={styles['setting-item-copy']}
>
{I18n.t('dataset_recall_copy_label')}
<IconCopy className={styles['icon-copy']} />
</UITag>
</Tooltip>
{I18n.t('bot_edit_dataset_on_demand_prompt2')}
</>
),
},
];
};
export const getNoRecallReplyOptions = (): RadioItem[] => [
{
e2e: BotE2e.BotKnowledgeSettingNoRecallReplyModeDefaultRadio,
label: I18n.t('No_recall_003'),
value: KnowledgeNoRecallReplyMode.Default,
},
{
e2e: BotE2e.BotKnowledgeSettingNoRecallReplyModeCustomizePromptRadio,
label: I18n.t('No_recall_004'),
value: KnowledgeNoRecallReplyMode.CustomizePrompt,
tip: (
<>
<div
style={{
lineHeight: '20px',
color: 'rgba(29, 28, 35, 1)',
marginBottom: '8px',
}}
>
{I18n.t('No_recall_007')}
</div>
<Image
width={344}
preview={false}
src={
I18n.language === 'zh-CN'
? ZhCustomizePromptPNG
: EnCustomizePromptPNG
}
/>
</>
),
tipStyle: {
backgroundColor: '#fff',
padding: '16px',
minWidth: '376px',
maxWidth: '376px',
},
},
];
export const localeMapLink: Record<string, string> = {
'zh-CN': '/docs/guides/knowledge',
en: '/docs/guides/knowledge_overview?_lang=en',
};
export const getShowSourceModeOptions = (): RadioItem[] => {
const language = getUnReactiveLanguage();
const goToGuides = (module = 'knowledge') => {
window.open(
`${window.location.origin}${
localeMapLink[language] || `/docs/guides/${module}`
}`,
);
};
return [
{
e2e: BotE2e.BotKnowledgeSettingShowSourceModeCardRadio,
label: I18n.t('knowledge_source_card_0002'),
value: KnowledgeShowSourceMode.CardList,
tip: (
<div className={styles['show-source-mode-tip']}>
<div className={styles.title}>
{I18n.t('knowledge_source_card_0004')}
</div>
<div className={styles.space}>
{[
{
title: I18n.t('what_is_coze'),
content: I18n.t('landingpage_description'),
guideModule: 'welcome',
},
{
title: I18n.t('knowledge_source_display_tooltip_link'),
content: I18n.t('knowledge_source_display_tooltip_content'),
guideModule: 'knowledge',
},
].map(i => (
<div
className={styles.card}
onClick={e => {
goToGuides(i.guideModule);
}}
>
<div className={styles.title}>
<div>{i.title}</div>
</div>
<Typography.Text
className={styles.content}
ellipsis={{
rows: 3,
showTooltip: false,
}}
>
{i.content}
</Typography.Text>
</div>
))}
</div>
</div>
),
tipStyle: {
backgroundColor: '#fff',
maxWidth: '436px',
minWidth: '436px',
padding: '16px',
},
},
{
e2e: BotE2e.BotKnowledgeSettingShowSourceModeTextRadio,
label: I18n.t('knowledge_source_card_0001'),
value: KnowledgeShowSourceMode.ReplyBottom,
tip: (
<div className={styles['show-source-mode-tip']}>
<div className={styles.title}>
{I18n.t('knowledge_source_card_0003')}
</div>
<div className={styles.main}>
<Typography.Text className={styles.content}>
{I18n.t('knowledge_source_display_tooltip_content')}
</Typography.Text>
<div className={styles.link}>
<div
onClick={e => {
goToGuides();
}}
>
1. {I18n.t('knowledge_source_display_tooltip_link')}
</div>
</div>
</div>
</div>
),
tipStyle: {
backgroundColor: '#fff',
maxWidth: '436px',
minWidth: '436px',
padding: '16px',
},
},
];
};

View File

@@ -0,0 +1,411 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable no-descending-specificity */
/* stylelint-disable selector-class-pattern */
@common-box-shadow: 0px 2px 8px 0px rgba(31, 35, 41, 0.02),
0px 2px 4px 0px rgba(31, 35, 41, 0.02), 0px 2px 2px 0px rgba(31, 35, 41, 0.02);
.common-svg-icon(@size: 14px, @color: #4d53e8) {
>svg {
width: @size;
height: @size;
>path {
fill: @color;
fill-opacity: 1;
}
}
}
.text {
font-size: 12px;
font-weight: 400;
font-style: normal;
line-height: 16px;
}
.icon-copy {
cursor: pointer;
padding: 2px;
border-radius: 4px;
.common-svg-icon(14px, rgba(107, 109, 117, 1));
&:hover {
background-color: var(--semi-color-fill-0);
}
}
.data-set-info {
display: flex;
align-items: center;
}
.default-text {
.text;
padding: 4px 0;
font-size: 14px;
line-height: 16px;
color: var(--light-usage-text-color-text-2, rgb(28 29 35 / 60%));
}
.setting-trigger {
cursor: pointer;
display: flex;
column-gap: 4px;
align-items: center;
margin-left: 8px;
font-size: 12px;
font-weight: 600;
font-style: normal;
line-height: 16px;
color: var(--light-color-brand-brand-5, #4d53e8);
&-icon {
svg {
width: 10px;
height: 10px;
}
}
}
.setting-content-popover {
background: #f7f7fa;
border-radius: 12px;
}
.setting {
overflow-y: auto;
max-width: 611px;
height: 454px;
padding: 24px;
font-size: 14px;
line-height: 20px;
color: var(--light-usage-text-color-text-0, #1f2329);
background-color: #f7f7fa;
border-radius: 12px;
.setting-title {
padding-bottom: 24px;
font-size: 18px;
font-weight: 600;
line-height: 24px;
}
.recall_title {
margin-top: 16px;
font-size: 16px;
font-weight: 600;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1d1c24);
}
.setting-item-container {
display: flex;
align-items: self-start;
justify-content: space-between;
min-height: 32px;
margin-top: 16px;
.setting-item {
width: 346px;
}
.setting-item-copy {
cursor: pointer;
margin: 0 4px;
padding: 2px 4px 2px 8px;
font-size: 12px;
font-weight: 500;
line-height: 16px;
color: var(--light-color-brand-brand-5, #4d53e8);
border-radius: 6px;
.icon-copy {
.common-svg-icon(14px, var(--light-color-brand-brand-5, #4d53e8));
margin: 0 0 0 4px;
}
}
:global {
.semi-tag-grey-light {
background: var(--light-color-brand-brand-1, #d9dcfa) !important;
}
}
}
.setting-source-display {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--Light-usage-border---color-border, rgba(29, 28, 35, 8%));
:global {
.semi-switch-checked {
background-color: var(--light-color-brand-brand-5, #4d53e8);
}
.semi-switch-disabled.semi-switch-checked {
background-color: var(--semi-color-primary-disabled);
}
}
&-title {
font-size: 16px;
font-weight: 600;
line-height: 22px;
}
}
}
.title-area {
display: flex;
align-items: center;
width: 220px;
font-size: 14px;
line-height: 22px;
color: var(--light-usage-text-color-text-0, #1c1d23);
&-icon {
cursor: pointer;
flex-shrink: 0;
margin-left: 8px;
color: #a7a9b0;
}
}
.slider-area {
&:hover {
.slider-boundary {
opacity: 1;
}
:global {
.semi-slider-mark {
display: block !important;
}
}
}
.slider-wrapper {
display: flex;
align-items: center;
.slider {
width: 210px;
margin-right: 24px;
:global {
.semi-slider-mark {
display: none;
margin-top: 9px;
font-size: 12px;
line-height: 16px;
}
}
}
.input-number {
width: 108px;
}
}
.slider-boundary {
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
width: 200px;
margin-top: 0;
padding: 0 12px;
font-size: 12px;
line-height: 16px;
color: var(--light-color-black-black, #000);
opacity: 0;
}
}
.tip-area {
width: 540px;
margin-top: 16px;
border-radius: 8px;
.icon {
width: 20px;
height: 20px;
}
.desc {
font-size: 14px;
line-height: 20px;
color: var(--Light-usage-text---color-text-0, #1c1f23);
}
}
.radio-area {
width: 346px;
:global {
.semi-radioGroup-horizontal {
gap: 24px;
}
}
.radio-item {
display: flex;
align-items: center;
.radio-item-desc {
margin: 4px 0 0 24px;
font-size: 14px;
line-height: 22px;
color: var(--light-usage-text-color-text-2, rgb(28 29 35 / 60%));
}
:global {
.semi-radio-addon {
font-weight: 600;
}
}
}
.radio-item-icon {
cursor: pointer;
flex-shrink: 0;
margin-left: 4px;
color: #a7a9b0;
}
.radio-desc {
box-sizing: border-box;
width: 346px;
margin: 8px 0;
padding: 12px;
font-size: 12px;
background: var(--Light-color-brand---brand-0, #f1f2fd);
border: 1px solid var(--Light-color-brand---brand-1, #d9dcfa);
border-radius: 8px;
}
}
.display_tooltip {
.display_tooltip_content {
margin-top: 16px;
padding: 12px;
background: var(--Light-usage-fill---color-fill-0, rgba(46, 46, 57, 4%));
border-radius: var(--default, 8px);
.display_tooltip_content_link {
// text-decoration: underline;
cursor: pointer;
display: flex;
margin-top: 8px;
color: var(--Light-usage-primary---color-primary, #4c54f0);
.link_num {
margin-right: 10px;
color: var(--Light-usage-text---color-text-0, #1d1c24);
text-decoration: none !important;
}
.display_tooltip_link {
text-decoration: underline;
}
}
}
}
.show-source-mode-tip {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
.title {
font-size: 14px;
font-weight: 600;
line-height: 20px;
color: rgba(29, 28, 35, 100%);
}
// Footer展示
.main {
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px;
background: #f9f9f9;
border: 1px solid rgba(6, 7, 9, 10%);
border-radius: 16px;
.content {
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: rgba(6, 7, 9, 80%);
}
.link {
cursor: pointer;
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: #543ef7;
}
}
// Card展示
.space {
display: flex;
gap: 8px;
justify-content: center;
.card {
cursor: pointer;
display: flex;
flex-direction: column;
gap: 8px;
width: 177px;
padding: 16px;
background-color: rgba(244, 244, 246, 100%);
border-radius: 8px;
.content {
font-size: 12px;
font-weight: 400;
line-height: 16px;
color: rgba(6, 7, 9, 50%);
}
.title {
font-size: 12px;
font-weight: 500;
line-height: 16px;
color: rgba(6, 7, 9, 80%);
}
}
}
}

View File

@@ -0,0 +1,392 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useRef } from 'react';
import { debounce, isEmpty } from 'lodash-es';
import { produce } from 'immer';
import classNames from 'classnames';
import { BotE2e } from '@coze-data/e2e';
import { RerankTips, RewriteTips } from '@coze-common/biz-tooltip-ui';
import { I18n, getUnReactiveLanguage } from '@coze-arch/i18n';
import { Banner, Form, Switch } from '@coze-arch/bot-semi';
import { IconWarningInfo } from '@coze-arch/bot-icons';
import { getFlags } from '@coze-arch/bot-flags';
import {
KnowledgeShowSourceMode,
KnowledgeNoRecallReplyMode,
} from '@coze-arch/bot-api/playground_api';
import { recallStrategyUpdater } from './utils';
import { type RagModeConfigurationProps } from './type';
import { SliderSetting } from './slider-setting';
import { SettingItem } from './setting-item';
import { RadioGroupSetting } from './radio-group-setting';
import {
MAX_TOP_K_VALUE,
getAutomaticCallOptions,
getShowSourceModeOptions,
getNoRecallReplyOptions,
getSearchStrategyOptions,
localeMapLink,
} from './constant';
import styles from './index.module.less';
const DATASET_INFO_MIN_SCORE = 0.01;
/* eslint-disable @coze-arch/max-line-per-function*/
// eslint-disable-next-line complexity, max-lines-per-function
export function RagModeConfiguration({
dataSetInfo,
onDataSetInfoChange,
showTitle = true,
isReadonly = false,
showNL2SQLConfig,
showAuto = true,
showSourceDisplay = true,
}: RagModeConfigurationProps): JSX.Element {
const {
auto,
min_score: minScore,
top_k: topK,
search_strategy: searchStrategy,
show_source,
no_recall_reply_mode,
no_recall_reply_customize_prompt,
show_source_mode,
recall_strategy = {},
} = dataSetInfo;
// undefined 默认为 true
const {
use_nl2sql = true,
use_rerank = true,
use_rewrite = true,
} = recall_strategy;
const language = getUnReactiveLanguage();
const FLAGS = getFlags();
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const debounceOnNoRecallReplyCustomizePromptChange = debounce(v => {
onDataSetInfoChange({
...dataSetInfo,
no_recall_reply_customize_prompt: v,
});
}, 300);
return (
<div className={styles.setting}>
{showTitle ? (
<div
data-testid={BotE2e.BotKnowledgeSettingModalTitle}
className={classNames(
styles['setting-title'],
'dataset-setting-content-title',
)}
>
{I18n.t('dataset_settings_title')}
</div>
) : null}
<div className={styles.recall_title}>
{I18n.t('dataset-setting_recall_title')}
</div>
{showAuto ? (
<SettingItem
title={I18n.t('dataset_call_method')}
tip={I18n.t('knowledge_call_method_tooltip')}
>
<RadioGroupSetting
options={getAutomaticCallOptions()}
value={auto ? 1 : 0}
onChange={v => onDataSetInfoChange({ ...dataSetInfo, auto: !!v })}
disabled={isReadonly}
/>
</SettingItem>
) : null}
<SettingItem
title={I18n.t('knowledge_search_strategy_title')}
tip={I18n.t('knowledge_search_strategy_tooltip')}
>
<RadioGroupSetting
options={getSearchStrategyOptions()}
value={searchStrategy ?? 0}
onChange={v =>
onDataSetInfoChange({ ...dataSetInfo, search_strategy: v })
}
disabled={isReadonly}
/>
</SettingItem>
<SettingItem
title={I18n.t('dataset_max_recall')}
tip={I18n.t('bot_edit_datasetsSettings_MaxTip')}
>
<SliderSetting
min={1}
max={10}
step={1}
precision={0}
value={topK}
marks={{ 3: I18n.t('dataset_max_recall_default') }}
onChange={v => {
onDataSetInfoChange({
...dataSetInfo,
top_k: v,
});
}}
disabled={isReadonly}
/>
</SettingItem>
{topK > MAX_TOP_K_VALUE && (
<Banner
bordered
type="warning"
fullMode={false}
closeIcon={null}
className={classNames(
styles['tip-area'],
'dataset-setting-content-tip-area',
)}
icon={<IconWarningInfo className={styles.icon} />}
description={
<span className={styles.desc}>
{I18n.t('dataset_max_recall_desc')}
</span>
}
/>
)}
{recall_strategy.use_rerank ? (
<SettingItem
title={I18n.t('dataset_min_degree')}
tip={I18n.t('bot_edit_datasetsSettings_MinTip')}
>
<SliderSetting
min={DATASET_INFO_MIN_SCORE}
max={0.99}
step={0.01}
precision={2}
value={minScore}
marks={{ 0.5: I18n.t('dataset_min_degree_default') }}
disabled={isReadonly}
onChange={v => {
onDataSetInfoChange({
...dataSetInfo,
min_score: v,
});
}}
/>
</SettingItem>
) : null}
{showNL2SQLConfig ? (
<SettingItem
title={I18n.t('kl_write_022')}
tip={I18n.t('kl_write_023')}
>
<Switch
checked={use_nl2sql}
onChange={value => {
onDataSetInfoChange(
produce(dataSetInfo, draft =>
recallStrategyUpdater({
datasetInfo: draft,
field: 'use_nl2sql',
value,
}),
),
);
}}
/>
</SettingItem>
) : null}
<SettingItem title={I18n.t('kl_write_024')} tip={<RewriteTips />}>
<Switch
checked={use_rewrite}
onChange={value => {
onDataSetInfoChange(
produce(dataSetInfo, draft =>
recallStrategyUpdater({
datasetInfo: draft,
field: 'use_rewrite',
value,
}),
),
);
}}
/>
</SettingItem>
<SettingItem title={I18n.t('kl_write_026')} tip={<RerankTips />}>
<Switch
checked={use_rerank}
onChange={value => {
onDataSetInfoChange(
produce(dataSetInfo, draft => {
const nextState = {
datasetInfo: draft,
field: 'use_rerank',
value,
} as const;
if (!value) {
nextState.datasetInfo.min_score = 0;
} else if (!nextState.datasetInfo.min_score) {
nextState.datasetInfo.min_score = DATASET_INFO_MIN_SCORE;
}
return recallStrategyUpdater(nextState);
}),
);
}}
/>
</SettingItem>
{FLAGS['bot.data.no_recall_reply'] ? (
<div className={styles['setting-source-display']}>
<div className={styles['setting-source-display-title']}>
{I18n.t('No_recall_001')}
</div>
<SettingItem
title={I18n.t('No_recall_002')}
tip={
<div className={styles.display_tooltip}>
{I18n.t('No_recall_005')}
</div>
}
>
<RadioGroupSetting
options={getNoRecallReplyOptions()}
value={no_recall_reply_mode ?? KnowledgeNoRecallReplyMode.Default}
onChange={v =>
onDataSetInfoChange({
...dataSetInfo,
no_recall_reply_mode: v,
no_recall_reply_customize_prompt:
v === KnowledgeNoRecallReplyMode.CustomizePrompt &&
isEmpty(no_recall_reply_customize_prompt)
? I18n.t('No_recall_006')
: no_recall_reply_customize_prompt,
})
}
disabled={isReadonly}
/>
</SettingItem>
{no_recall_reply_mode ===
KnowledgeNoRecallReplyMode.CustomizePrompt ? (
<Form<Record<string, unknown>>
initValues={{
no_recall_reply_customize_prompt:
no_recall_reply_customize_prompt ?? I18n.t('No_recall_006'),
}}
>
<Form.TextArea
maxLength={500}
maxCount={500}
ref={textAreaRef}
onChange={debounceOnNoRecallReplyCustomizePromptChange}
rows={2}
disabled={isReadonly}
placeholder={I18n.t(
'card_builder_dataEditor_get_errormsg_please_enter',
)}
pure
field="no_recall_reply_customize_prompt"
/>
</Form>
) : null}
</div>
) : null}
{FLAGS['bot.data.source_display'] && showSourceDisplay ? (
<div className={styles['setting-source-display']}>
<div
className={styles['setting-source-display-title']}
data-testid={BotE2e.BotKnowledgeSettingShowSourceDisplayTitle}
>
{I18n.t('knowledge_source_display_title')}
</div>
<SettingItem
title={I18n.t('knowledge_source_display_status')}
tipStyle={{
backgroundColor: '#fff',
color: 'var(--Light-usage-text---color-text-0, #1D1C24)',
maxWidth: '453px',
minWidth: '453px',
}}
tip={
<div className={styles.display_tooltip}>
<div className={styles.display_tooltip_title}>
{I18n.t('knowledge_source_display_tooltip_title')}
</div>
<div className={styles.display_tooltip_content}>
<div>
{I18n.t('knowledge_source_display_tooltip_content')}
</div>
<div className={styles.display_tooltip_content_link}>
<div className={styles.link_num}>1.</div>
<div
className={styles.display_tooltip_link}
onClick={() =>
window.open(
`${window.location.origin}${
localeMapLink[language] || '/docs/guides/knowledge'
}`,
)
}
>
{I18n.t('knowledge_source_display_tooltip_link')}
</div>
</div>
</div>
</div>
}
>
<Switch
data-testid={BotE2e.BotKnowledgeSettingShowSourceDisplaySwitch}
className={styles.display_status}
checked={show_source}
disabled={isReadonly}
onChange={v => {
onDataSetInfoChange({
...dataSetInfo,
show_source: v,
// 展示方式没有值且打开展示来源,默认选中卡片
...(!show_source_mode && v
? {
show_source_mode: KnowledgeShowSourceMode.CardList,
}
: {}),
});
}}
/>
</SettingItem>
{show_source ? (
<SettingItem title={I18n.t('Display format')}>
<RadioGroupSetting
options={getShowSourceModeOptions()}
value={show_source_mode ?? KnowledgeShowSourceMode.ReplyBottom}
onChange={v =>
onDataSetInfoChange({ ...dataSetInfo, show_source_mode: v })
}
disabled={isReadonly}
/>
</SettingItem>
) : null}
</div>
) : null}
</div>
);
}

View File

@@ -0,0 +1,86 @@
/*
* 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 CSSProperties, type ReactNode } from 'react';
import classNames from 'classnames';
import { Radio, RadioGroup, Popover } from '@coze-arch/bot-semi';
import { IconInfo } from '@coze-arch/bot-icons';
import styles from './index.module.less';
export interface RadioItem {
label: string;
value: number;
e2e?: string;
tip?: ReactNode;
tipStyle?: CSSProperties;
desc?: string | ReactNode;
}
export interface RadioGroupSettingProps {
options: RadioItem[];
value: number;
disabled?: boolean;
onChange: (value: number) => void;
}
export function RadioGroupSetting({
options,
value,
disabled,
onChange,
}: RadioGroupSettingProps) {
const desc = options.find(v => v.value === value)?.desc;
return (
<div className={styles['radio-area']}>
<RadioGroup
onChange={e => onChange(e.target.value as number)}
value={value}
disabled={disabled}
>
{options.map(item => (
<div
data-testid={item.e2e}
key={item.value}
className={classNames(
styles['radio-item'],
value === item.value ? styles.active : styles.normal,
)}
>
<Radio value={item.value}>{item.label}</Radio>
{!!item.tip && (
<Popover
showArrow
position="top"
zIndex={1031}
style={{
backgroundColor: '#41464c',
color: '#fff',
maxWidth: '276px',
...(item.tipStyle || {}),
}}
content={item.tip}
>
<IconInfo className={styles['radio-item-icon']} />
</Popover>
)}
</div>
))}
</RadioGroup>
{desc ? <div className={styles['radio-desc']}>{desc}</div> : null}
</div>
);
}

View File

@@ -0,0 +1,55 @@
/*
* 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 classNames from 'classnames';
import { type I18nKeysNoOptionsType } from '@coze-arch/i18n';
import { I18n } from '@coze-arch/i18n';
import { TitleArea } from './title-area';
import styles from './index.module.less';
export interface SettingItemProps {
title: string;
tip?: string | React.ReactNode;
children: React.ReactNode;
className?: string;
tipStyle?: Record<string, string | number>;
}
export const SettingItem = ({
title,
tip,
children,
className,
tipStyle,
}: SettingItemProps) => (
<div className={classNames(styles['setting-item-container'], className)}>
<TitleArea
title={I18n.t(title as unknown as I18nKeysNoOptionsType)}
tip={tip || ''}
tipStyle={tipStyle}
/>
<div
className={classNames(
styles['setting-item'],
'dataset-setting-content-item',
)}
>
{children}
</div>
</div>
);

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { InputNumber, Slider } from '@coze-arch/bot-semi';
import styles from './index.module.less';
interface SliderSettingProps {
min: number;
max: number;
step: number;
precision: number;
value: number;
marks: Record<number, string>;
onChange: (value: number) => void;
disabled: boolean;
}
export const SliderSetting = ({
min = 0,
max = 100,
step = 1,
precision = 0,
value,
marks,
onChange,
disabled,
}: SliderSettingProps) => (
<div className={styles['slider-area']}>
<div className={styles['slider-wrapper']}>
<div className={styles.slider}>
<Slider
step={step}
min={min}
max={max}
value={value}
marks={marks}
disabled={disabled}
onChange={v => onChange(v as number)}
></Slider>
</div>
<InputNumber
className={styles['input-number']}
step={step}
precision={precision}
onChange={v => {
let inputValue = Number(v);
if (isNaN(inputValue)) {
inputValue = value;
} else {
inputValue = inputValue || value;
inputValue = Math.max(inputValue, 0);
}
if (inputValue > max) {
inputValue = max;
}
onChange(inputValue);
}}
value={value}
min={min}
max={max}
disabled={disabled}
/>
</div>
<div className={styles['slider-boundary']}>
<div className={styles.min}>{min}</div>
<div className={styles.max}>{max}</div>
</div>
</div>
);

View File

@@ -0,0 +1,50 @@
/*
* 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 { IconInfo } from '@coze-arch/bot-icons';
import { Popover } from '@coze-arch/coze-design';
import styles from './index.module.less';
interface TitleAreaProps {
title: string;
tipStyle?: Record<string, string | number>;
tip?: string | React.ReactNode;
}
export function TitleArea({ title, tip, tipStyle = {} }: TitleAreaProps) {
return (
<div className={styles['title-area']}>
{title}
{!!tip && (
<Popover
showArrow
position="top"
zIndex={1031}
style={{
maxWidth: '276px',
...tipStyle,
}}
content={tip}
>
<IconInfo className={styles['title-area-icon']} />
</Popover>
)}
</div>
);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type KnowledgeShowSourceMode,
type KnowledgeNoRecallReplyMode,
type RecallStrategy,
} from '@coze-arch/bot-api/playground_api';
export interface IDataSetInfo {
min_score: number;
top_k: number;
auto: boolean;
search_strategy?: number;
show_source?: boolean;
no_recall_reply_mode?: KnowledgeNoRecallReplyMode;
no_recall_reply_customize_prompt?: string;
show_source_mode?: KnowledgeShowSourceMode;
recall_strategy?: RecallStrategy;
}
export interface RagModeConfigurationProps {
dataSetInfo: IDataSetInfo;
onDataSetInfoChange: (v: IDataSetInfo) => void;
showTitle?: boolean;
isReadonly?: boolean;
showNL2SQLConfig?: boolean;
showAuto?: boolean;
showSourceDisplay?: boolean;
}

View File

@@ -0,0 +1,34 @@
/*
* 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 RecallStrategy } from '@coze-arch/bot-api/playground_api';
import { type IDataSetInfo } from './type';
export const recallStrategyUpdater: (params: {
datasetInfo: IDataSetInfo;
field: keyof RecallStrategy;
value: boolean;
}) => IDataSetInfo = ({ datasetInfo, field, value }) => {
if (!datasetInfo.recall_strategy) {
datasetInfo.recall_strategy = {
[field]: value,
};
} else {
datasetInfo.recall_strategy[field] = value;
}
return datasetInfo;
};