moderation

This commit is contained in:
JzoNg 2024-08-28 15:18:16 +08:00
parent a691700b48
commit 2f658de155
15 changed files with 190 additions and 544 deletions

View File

@ -3,7 +3,7 @@ import { useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import FormGeneration from '../toolbox/moderation/form-generation' import FormGeneration from '@/app/components/base/features/new-feature-panel/moderation/form-generation'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import EmojiPicker from '@/app/components/base/emoji-picker' import EmojiPicker from '@/app/components/base/emoji-picker'

View File

@ -1,81 +0,0 @@
import type { FC } from 'react'
import { memo } from 'react'
import { useContext } from 'use-context-selector'
import type { CodeBasedExtensionForm } from '@/models/common'
import I18n from '@/context/i18n'
import { PortalSelect } from '@/app/components/base/select'
import Textarea from '@/app/components/base/textarea'
import type { ModerationConfig } from '@/models/debug'
type FormGenerationProps = {
forms: CodeBasedExtensionForm[]
value: ModerationConfig['config']
onChange: (v: Record<string, string>) => void
}
const FormGeneration: FC<FormGenerationProps> = ({
forms,
value,
onChange,
}) => {
const { locale } = useContext(I18n)
const handleFormChange = (type: string, v: string) => {
onChange({ ...value, [type]: v })
}
return (
<>
{
forms.map((form, index) => (
<div
key={index}
className='py-2'
>
<div className='flex items-center h-9 text-sm font-medium text-gray-900'>
{locale === 'zh-Hans' ? form.label['zh-Hans'] : form.label['en-US']}
</div>
{
form.type === 'text-input' && (
<input
value={value?.[form.variable] || ''}
className='block px-3 w-full h-9 bg-gray-100 rounded-lg text-sm text-gray-900 outline-none appearance-none'
placeholder={form.placeholder}
onChange={e => handleFormChange(form.variable, e.target.value)}
/>
)
}
{
form.type === 'paragraph' && (
<div className='relative'>
<Textarea
value={value?.[form.variable] || ''}
className='resize-none'
placeholder={form.placeholder}
onChange={e => handleFormChange(form.variable, e.target.value)}
/>
</div>
)
}
{
form.type === 'select' && (
<PortalSelect
value={value?.[form.variable]}
items={form.options.map((option) => {
return {
name: option.label[locale === 'zh-Hans' ? 'zh-Hans' : 'en-US'],
value: option.value,
}
})}
onSelect={item => handleFormChange(form.variable, item.value as string)}
popupClassName='w-[576px] !z-[102]'
/>
)
}
</div>
))
}
</>
)
}
export default memo(FormGeneration)

View File

@ -1,73 +0,0 @@
import type { FC } from 'react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import Switch from '@/app/components/base/switch'
import type { ModerationContentConfig } from '@/models/debug'
type ModerationContentProps = {
title: string
info?: string
showPreset?: boolean
config: ModerationContentConfig
onConfigChange: (config: ModerationContentConfig) => void
}
const ModerationContent: FC<ModerationContentProps> = ({
title,
info,
showPreset = true,
config,
onConfigChange,
}) => {
const { t } = useTranslation()
const handleConfigChange = (field: string, value: boolean | string) => {
if (field === 'preset_response' && typeof value === 'string')
value = value.slice(0, 100)
onConfigChange({ ...config, [field]: value })
}
return (
<div className='py-2'>
<div className='rounded-lg bg-gray-50 border border-gray-200'>
<div className='flex items-center justify-between px-3 h-10 rounded-lg'>
<div className='shrink-0 text-sm font-medium text-gray-900'>{title}</div>
<div className='grow flex items-center justify-end'>
{
info && (
<div className='mr-2 text-xs text-gray-500 truncate' title={info}>{info}</div>
)
}
<Switch
size='l'
defaultValue={config.enabled}
onChange={v => handleConfigChange('enabled', v)}
/>
</div>
</div>
{
config.enabled && showPreset && (
<div className='px-3 pt-1 pb-3 bg-white rounded-lg'>
<div className='flex items-center justify-between h-8 text-[13px] font-medium text-gray-700'>
{t('appDebug.feature.moderation.modal.content.preset')}
<span className='text-xs font-normal text-gray-500'>{t('appDebug.feature.moderation.modal.content.supportMarkdown')}</span>
</div>
<div className='relative px-3 py-2 h-20 rounded-lg bg-gray-100'>
<textarea
value={config.preset_response || ''}
className='block w-full h-full bg-transparent text-sm outline-none appearance-none resize-none'
placeholder={t('appDebug.feature.moderation.modal.content.placeholder') || ''}
onChange={e => handleConfigChange('preset_response', e.target.value)}
/>
<div className='absolute bottom-2 right-2 flex items-center px-1 h-5 rounded-md bg-gray-50 text-xs font-medium text-gray-300'>
<span>{(config.preset_response || '').length}</span>/<span className='text-gray-500'>100</span>
</div>
</div>
</div>
)
}
</div>
</div>
)
}
export default memo(ModerationContent)

View File

@ -1,376 +0,0 @@
import type { ChangeEvent, FC } from 'react'
import {
memo,
useState,
} from 'react'
import useSWR from 'swr'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import ModerationContent from './moderation-content'
import FormGeneration from './form-generation'
import ApiBasedExtensionSelector from '@/app/components/header/account-setting/api-based-extension-page/selector'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
import type { ModerationConfig, ModerationContentConfig } from '@/models/debug'
import { useToastContext } from '@/app/components/base/toast'
import {
fetchCodeBasedExtensionList,
fetchModelProviders,
} from '@/service/common'
import type { CodeBasedExtensionItem } from '@/models/common'
import I18n from '@/context/i18n'
import { LanguagesSupported } from '@/i18n/language'
import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
import { useModalContext } from '@/context/modal-context'
import { CustomConfigurationStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
const systemTypes = ['openai_moderation', 'keywords', 'api']
type Provider = {
key: string
name: string
form_schema?: CodeBasedExtensionItem['form_schema']
}
type ModerationSettingModalProps = {
data: ModerationConfig
onCancel: () => void
onSave: (moderationConfig: ModerationConfig) => void
}
const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
data,
onCancel,
onSave,
}) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const { locale } = useContext(I18n)
const { data: modelProviders, isLoading, mutate } = useSWR('/workspaces/current/model-providers', fetchModelProviders)
const [localeData, setLocaleData] = useState<ModerationConfig>(data)
const { setShowAccountSettingModal } = useModalContext()
const handleOpenSettingsModal = () => {
setShowAccountSettingModal({
payload: 'provider',
onCancelCallback: () => {
mutate()
},
})
}
const { data: codeBasedExtensionList } = useSWR(
'/code-based-extension?module=moderation',
fetchCodeBasedExtensionList,
)
const openaiProvider = modelProviders?.data.find(item => item.provider === 'openai')
const systemOpenaiProviderEnabled = openaiProvider?.system_configuration.enabled
const systemOpenaiProviderQuota = systemOpenaiProviderEnabled ? openaiProvider?.system_configuration.quota_configurations.find(item => item.quota_type === openaiProvider.system_configuration.current_quota_type) : undefined
const systemOpenaiProviderCanUse = systemOpenaiProviderQuota?.is_valid
const customOpenaiProvidersCanUse = openaiProvider?.custom_configuration.status === CustomConfigurationStatusEnum.active
const openaiProviderConfiged = customOpenaiProvidersCanUse || systemOpenaiProviderCanUse
const providers: Provider[] = [
{
key: 'openai_moderation',
name: t('appDebug.feature.moderation.modal.provider.openai'),
},
{
key: 'keywords',
name: t('appDebug.feature.moderation.modal.provider.keywords'),
},
{
key: 'api',
name: t('common.apiBasedExtension.selector.title'),
},
...(
codeBasedExtensionList
? codeBasedExtensionList.data.map((item) => {
return {
key: item.name,
name: locale === 'zh-Hans' ? item.label['zh-Hans'] : item.label['en-US'],
form_schema: item.form_schema,
}
})
: []
),
]
const currentProvider = providers.find(provider => provider.key === localeData.type)
const handleDataTypeChange = (type: string) => {
let config: undefined | Record<string, any>
const currProvider = providers.find(provider => provider.key === type)
if (systemTypes.findIndex(t => t === type) < 0 && currProvider?.form_schema) {
config = currProvider?.form_schema.reduce((prev, next) => {
prev[next.variable] = next.default
return prev
}, {} as Record<string, any>)
}
setLocaleData({
...localeData,
type,
config,
})
}
const handleDataKeywordsChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value
const arr = value.split('\n').reduce((prev: string[], next: string) => {
if (next !== '')
prev.push(next.slice(0, 100))
if (next === '' && prev[prev.length - 1] !== '')
prev.push(next)
return prev
}, [])
setLocaleData({
...localeData,
config: {
...localeData.config,
keywords: arr.slice(0, 100).join('\n'),
},
})
}
const handleDataContentChange = (contentType: string, contentConfig: ModerationContentConfig) => {
setLocaleData({
...localeData,
config: {
...localeData.config,
[contentType]: contentConfig,
},
})
}
const handleDataApiBasedChange = (apiBasedExtensionId: string) => {
setLocaleData({
...localeData,
config: {
...localeData.config,
api_based_extension_id: apiBasedExtensionId,
},
})
}
const handleDataExtraChange = (extraValue: Record<string, string>) => {
setLocaleData({
...localeData,
config: {
...localeData.config,
...extraValue,
},
})
}
const formatData = (originData: ModerationConfig) => {
const { enabled, type, config } = originData
const { inputs_config, outputs_config } = config!
const params: Record<string, string | undefined> = {}
if (type === 'keywords')
params.keywords = config?.keywords
if (type === 'api')
params.api_based_extension_id = config?.api_based_extension_id
if (systemTypes.findIndex(t => t === type) < 0 && currentProvider?.form_schema) {
currentProvider.form_schema.forEach((form) => {
params[form.variable] = config?.[form.variable]
})
}
return {
type,
enabled,
config: {
inputs_config: inputs_config || { enabled: false },
outputs_config: outputs_config || { enabled: false },
...params,
},
}
}
const handleSave = () => {
if (localeData.type === 'openai_moderation' && !openaiProviderConfiged)
return
if (!localeData.config?.inputs_config?.enabled && !localeData.config?.outputs_config?.enabled) {
notify({ type: 'error', message: t('appDebug.feature.moderation.modal.content.condition') })
return
}
if (localeData.type === 'keywords' && !localeData.config.keywords) {
notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: locale !== LanguagesSupported[1] ? 'keywords' : '关键词' }) })
return
}
if (localeData.type === 'api' && !localeData.config.api_based_extension_id) {
notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: locale !== LanguagesSupported[1] ? 'API Extension' : 'API 扩展' }) })
return
}
if (systemTypes.findIndex(t => t === localeData.type) < 0 && currentProvider?.form_schema) {
for (let i = 0; i < currentProvider.form_schema.length; i++) {
if (!localeData.config?.[currentProvider.form_schema[i].variable] && currentProvider.form_schema[i].required) {
notify({
type: 'error',
message: t('appDebug.errorMessage.valueOfVarRequired', { key: locale !== LanguagesSupported[1] ? currentProvider.form_schema[i].label['en-US'] : currentProvider.form_schema[i].label['zh-Hans'] }),
})
return
}
}
}
if (localeData.config.inputs_config?.enabled && !localeData.config.inputs_config.preset_response && localeData.type !== 'api') {
notify({ type: 'error', message: t('appDebug.feature.moderation.modal.content.errorMessage') })
return
}
if (localeData.config.outputs_config?.enabled && !localeData.config.outputs_config.preset_response && localeData.type !== 'api') {
notify({ type: 'error', message: t('appDebug.feature.moderation.modal.content.errorMessage') })
return
}
onSave(formatData(localeData))
}
return (
<Modal
isShow
onClose={() => { }}
className='!p-8 !pb-6 !mt-14 !max-w-none !w-[640px]'
>
<div className='mb-2 text-xl font-semibold text-[#1D2939]'>
{t('appDebug.feature.moderation.modal.title')}
</div>
<div className='py-2'>
<div className='leading-9 text-sm font-medium text-gray-900'>
{t('appDebug.feature.moderation.modal.provider.title')}
</div>
<div className='grid gap-2.5 grid-cols-3'>
{
providers.map(provider => (
<div
key={provider.key}
className={`
flex items-center px-3 py-2 rounded-lg text-sm text-gray-900 cursor-pointer
${localeData.type === provider.key ? 'bg-white border-[1.5px] border-primary-400 shadow-sm' : 'border border-gray-100 bg-gray-25'}
${localeData.type === 'openai_moderation' && provider.key === 'openai_moderation' && !openaiProviderConfiged && 'opacity-50'}
`}
onClick={() => handleDataTypeChange(provider.key)}
>
<div className={`
mr-2 w-4 h-4 rounded-full border
${localeData.type === provider.key ? 'border-[5px] border-primary-600' : 'border border-gray-300'}`} />
{provider.name}
</div>
))
}
</div>
{
!isLoading && !openaiProviderConfiged && localeData.type === 'openai_moderation' && (
<div className='flex items-center mt-2 px-3 py-2 bg-[#FFFAEB] rounded-lg border border-[#FEF0C7]'>
<InfoCircle className='mr-1 w-4 h-4 text-[#F79009]' />
<div className='flex items-center text-xs font-medium text-gray-700'>
{t('appDebug.feature.moderation.modal.openaiNotConfig.before')}
<span
className='text-primary-600 cursor-pointer'
onClick={handleOpenSettingsModal}
>
&nbsp;{t('common.settings.provider')}&nbsp;
</span>
{t('appDebug.feature.moderation.modal.openaiNotConfig.after')}
</div>
</div>
)
}
</div>
{
localeData.type === 'keywords' && (
<div className='py-2'>
<div className='mb-1 text-sm font-medium text-gray-900'>{t('appDebug.feature.moderation.modal.provider.keywords')}</div>
<div className='mb-2 text-xs text-gray-500'>{t('appDebug.feature.moderation.modal.keywords.tip')}</div>
<div className='relative px-3 py-2 h-[88px] bg-gray-100 rounded-lg'>
<textarea
value={localeData.config?.keywords || ''}
onChange={handleDataKeywordsChange}
className='block w-full h-full bg-transparent text-sm outline-none appearance-none resize-none'
placeholder={t('appDebug.feature.moderation.modal.keywords.placeholder') || ''}
/>
<div className='absolute bottom-2 right-2 flex items-center px-1 h-5 rounded-md bg-gray-50 text-xs font-medium text-gray-300'>
<span>{(localeData.config?.keywords || '').split('\n').filter(Boolean).length}</span>/<span className='text-gray-500'>100 {t('appDebug.feature.moderation.modal.keywords.line')}</span>
</div>
</div>
</div>
)
}
{
localeData.type === 'api' && (
<div className='py-2'>
<div className='flex items-center justify-between h-9'>
<div className='text-sm font-medium text-gray-900'>{t('common.apiBasedExtension.selector.title')}</div>
<a
href={t('common.apiBasedExtension.linkUrl') || '/'}
target='_blank' rel='noopener noreferrer'
className='group flex items-center text-xs text-gray-500 hover:text-primary-600'
>
<BookOpen01 className='mr-1 w-3 h-3 text-gray-500 group-hover:text-primary-600' />
{t('common.apiBasedExtension.link')}
</a>
</div>
<ApiBasedExtensionSelector
value={localeData.config?.api_based_extension_id || ''}
onChange={handleDataApiBasedChange}
/>
</div>
)
}
{
systemTypes.findIndex(t => t === localeData.type) < 0
&& currentProvider?.form_schema
&& (
<FormGeneration
forms={currentProvider?.form_schema}
value={localeData.config}
onChange={handleDataExtraChange}
/>
)
}
<div className='my-3 h-[1px] bg-gradient-to-r from-[#F3F4F6]'></div>
<ModerationContent
title={t('appDebug.feature.moderation.modal.content.input') || ''}
config={localeData.config?.inputs_config || { enabled: false, preset_response: '' }}
onConfigChange={config => handleDataContentChange('inputs_config', config)}
info={(localeData.type === 'api' && t('appDebug.feature.moderation.modal.content.fromApi')) || ''}
showPreset={!(localeData.type === 'api')}
/>
<ModerationContent
title={t('appDebug.feature.moderation.modal.content.output') || ''}
config={localeData.config?.outputs_config || { enabled: false, preset_response: '' }}
onConfigChange={config => handleDataContentChange('outputs_config', config)}
info={(localeData.type === 'api' && t('appDebug.feature.moderation.modal.content.fromApi')) || ''}
showPreset={!(localeData.type === 'api')}
/>
<div className='mt-1 mb-8 text-xs font-medium text-gray-500'>{t('appDebug.feature.moderation.modal.content.condition')}</div>
<div className='flex items-center justify-end'>
<Button
onClick={onCancel}
className='mr-2'
>
{t('common.operation.cancel')}
</Button>
<Button
variant='primary'
onClick={handleSave}
disabled={localeData.type === 'openai_moderation' && !openaiProviderConfiged}
>
{t('common.operation.save')}
</Button>
</div>
</Modal>
)
}
export default memo(ModerationSettingModal)

View File

@ -44,7 +44,7 @@ const Citation = ({
} }
title={t('appDebug.feature.citation.title')} title={t('appDebug.feature.citation.title')}
value={!!features.citation?.enabled} value={!!features.citation?.enabled}
description={t('appDebug.feature.citation.description')} description={t('appDebug.feature.citation.description')!}
onChange={state => handleChange(FeatureEnum.citation, state)} onChange={state => handleChange(FeatureEnum.citation, state)}
/> />
) )

View File

@ -44,7 +44,7 @@ const FollowUp = ({
} }
title={t('appDebug.feature.suggestedQuestionsAfterAnswer.title')} title={t('appDebug.feature.suggestedQuestionsAfterAnswer.title')}
value={!!features.suggested?.enabled} value={!!features.suggested?.enabled}
description={t('appDebug.feature.suggestedQuestionsAfterAnswer.description')} description={t('appDebug.feature.suggestedQuestionsAfterAnswer.description')!}
onChange={state => handleChange(FeatureEnum.suggested, state)} onChange={state => handleChange(FeatureEnum.suggested, state)}
/> />
) )

View File

@ -21,6 +21,7 @@ import Switch from '@/app/components/base/switch'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import MoreLikeThis from '@/app/components/base/features/new-feature-panel/more-like-this' import MoreLikeThis from '@/app/components/base/features/new-feature-panel/more-like-this'
import Moderation from '@/app/components/base/features/new-feature-panel/moderation'
import SpeechToText from '@/app/components/base/features/new-feature-panel/speech-to-text' import SpeechToText from '@/app/components/base/features/new-feature-panel/speech-to-text'
import TextToSpeech from '@/app/components/base/features/new-feature-panel/text-to-speech' import TextToSpeech from '@/app/components/base/features/new-feature-panel/text-to-speech'
import FollowUp from '@/app/components/base/features/new-feature-panel/follow-up' import FollowUp from '@/app/components/base/features/new-feature-panel/follow-up'
@ -88,6 +89,7 @@ const NewFeaturePanel = ({
{!isChatMode && ( {!isChatMode && (
<MoreLikeThis onChange={onChange} /> <MoreLikeThis onChange={onChange} />
)} )}
<Moderation onChange={onChange} />
{isChatMode && speech2textDefaultModel && ( {isChatMode && speech2textDefaultModel && (
<SpeechToText onChange={onChange} /> <SpeechToText onChange={onChange} />
)} )}

View File

@ -0,0 +1,170 @@
import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import useSWR from 'swr'
import produce from 'immer'
import { useContext } from 'use-context-selector'
import { RiEqualizer2Line } from '@remixicon/react'
import { ContentModeration } from '@/app/components/base/icons/src/vender/features'
import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card'
import Button from '@/app/components/base/button'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
import { useStore } from '@/app/components/workflow/store'
import type { OnFeaturesChange } from '@/app/components/base/features/types'
import { FeatureEnum } from '@/app/components/base/features/types'
import { fetchCodeBasedExtensionList } from '@/service/common'
import { useModalContext } from '@/context/modal-context'
import I18n from '@/context/i18n'
type Props = {
disabled?: boolean
onChange?: OnFeaturesChange
}
const Moderation = ({
disabled,
onChange,
}: Props) => {
const { t } = useTranslation()
const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel)
const { setShowModerationSettingModal } = useModalContext()
const { locale } = useContext(I18n)
const featuresStore = useFeaturesStore()
const moderation = useFeatures(s => s.features.moderation)
const { data: codeBasedExtensionList } = useSWR(
'/code-based-extension?module=moderation',
fetchCodeBasedExtensionList,
)
const [isHovering, setIsHovering] = useState(false)
const handleOpenModerationSettingModal = () => {
if (disabled)
return
const {
features,
setFeatures,
} = featuresStore!.getState()
setShowModerationSettingModal({
payload: moderation as any,
onSaveCallback: (newModeration) => {
const newFeatures = produce(features, (draft) => {
draft.moderation = newModeration
})
setFeatures(newFeatures)
setShowFeaturesPanel(true)
if (onChange)
onChange(newFeatures)
},
onCancelCallback: () => {
setShowFeaturesPanel(true)
},
})
}
const handleChange = useCallback((type: FeatureEnum, enabled: boolean) => {
const {
features,
setFeatures,
} = featuresStore!.getState()
if (enabled && !features.moderation?.type && type === FeatureEnum.moderation) {
setShowModerationSettingModal({
payload: {
enabled: true,
type: 'keywords',
config: {
keywords: '',
inputs_config: {
enabled: true,
preset_response: '',
},
},
},
onSaveCallback: (newModeration) => {
const newFeatures = produce(features, (draft) => {
draft.moderation = newModeration
})
setFeatures(newFeatures)
setShowFeaturesPanel(true)
if (onChange)
onChange(newFeatures)
},
onCancelCallback: () => {
const newFeatures = produce(features, (draft) => {
draft.moderation = { enabled: false }
})
setFeatures(newFeatures)
setShowFeaturesPanel(true)
if (onChange)
onChange(newFeatures)
},
})
}
}, [featuresStore, onChange])
const providerContent = useMemo(() => {
if (moderation?.type === 'openai_moderation')
return t('appDebug.feature.moderation.modal.provider.openai')
else if (moderation?.type === 'keywords')
return t('appDebug.feature.moderation.modal.provider.keywords')
else if (moderation?.type === 'api')
return t('common.apiBasedExtension.selector.title')
else
return codeBasedExtensionList?.data.find(item => item.name === moderation?.type)?.label[locale] || '-'
}, [codeBasedExtensionList, moderation])
const enableContent = useMemo(() => {
if (moderation?.config?.inputs_config?.enabled && moderation.config?.outputs_config?.enabled)
return t('appDebug.feature.moderation.allEnabled')
else if (moderation?.config?.inputs_config?.enabled)
return t('appDebug.feature.moderation.inputEnabled')
else if (moderation?.config?.outputs_config?.enabled)
return t('appDebug.feature.moderation.outputEnabled')
}, [moderation])
return (
<FeatureCard
icon={
<div className='shrink-0 p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-text-success'>
<ContentModeration className='w-4 h-4 text-text-primary-on-surface' />
</div>
}
title={t('appDebug.feature.moderation.title')}
value={!!moderation?.enabled}
onChange={state => handleChange(FeatureEnum.moderation, state)}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
>
<>
{!moderation?.enabled && (
<div className='min-h-8 text-text-tertiary system-xs-regular line-clamp-2'>{t('appDebug.feature.moderation.description')}</div>
)}
{!!moderation?.enabled && (
<>
{!isHovering && (
<div className='pt-0.5 flex items-center gap-4'>
<div className=''>
<div className='mb-0.5 text-text-tertiary system-2xs-medium-uppercase'>{t('appDebug.feature.moderation.modal.provider.title')}</div>
<div className='text-text-secondary system-xs-regular'>{providerContent}</div>
</div>
<div className='w-px h-[27px] bg-divider-subtle rotate-12'></div>
<div className=''>
<div className='mb-0.5 text-text-tertiary system-2xs-medium-uppercase'>{t('appDebug.feature.moderation.contentEnableLabel')}</div>
<div className='text-text-secondary system-xs-regular'>{enableContent}</div>
</div>
</div>
)}
{isHovering && (
<Button className='w-full' onClick={handleOpenModerationSettingModal}>
<RiEqualizer2Line className='mr-1 w-4 h-4' />
{t('common.operation.settings')}
</Button>
)}
</>
)}
</>
</FeatureCard>
)
}
export default Moderation

View File

@ -3,6 +3,7 @@ import { useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiCloseLine } from '@remixicon/react'
import ModerationContent from './moderation-content' import ModerationContent from './moderation-content'
import FormGeneration from './form-generation' import FormGeneration from './form-generation'
import ApiBasedExtensionSelector from '@/app/components/header/account-setting/api-based-extension-page/selector' import ApiBasedExtensionSelector from '@/app/components/header/account-setting/api-based-extension-page/selector'
@ -237,10 +238,11 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
<Modal <Modal
isShow isShow
onClose={() => { }} onClose={() => { }}
className='!p-8 !pb-6 !mt-14 !max-w-none !w-[640px]' className='!p-6 !mt-14 !max-w-none !w-[600px]'
> >
<div className='mb-2 text-xl font-semibold text-[#1D2939]'> <div className='flex items-center justify-between'>
{t('appDebug.feature.moderation.modal.title')} <div className='text-text-primary title-2xl-semi-bold'>{t('appDebug.feature.moderation.modal.title')}</div>
<div className='p-1 cursor-pointer' onClick={onCancel}><RiCloseLine className='w-4 h-4 text-text-tertiary'/></div>
</div> </div>
<div className='py-2'> <div className='py-2'>
<div className='leading-9 text-sm font-medium text-gray-900'> <div className='leading-9 text-sm font-medium text-gray-900'>

View File

@ -45,7 +45,7 @@ const MoreLikeThis = ({
title={t('appDebug.feature.moreLikeThis.title')} title={t('appDebug.feature.moreLikeThis.title')}
tooltip={t('appDebug.feature.moreLikeThis.tip')} tooltip={t('appDebug.feature.moreLikeThis.tip')}
value={!!features.moreLikeThis?.enabled} value={!!features.moreLikeThis?.enabled}
description={t('appDebug.feature.moreLikeThis.description')} description={t('appDebug.feature.moreLikeThis.description')!}
onChange={state => handleChange(FeatureEnum.moreLikeThis, state)} onChange={state => handleChange(FeatureEnum.moreLikeThis, state)}
/> />
) )

View File

@ -6,7 +6,7 @@ import { createContext, useContext, useContextSelector } from 'use-context-selec
import { useRouter, useSearchParams } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import AccountSetting from '@/app/components/header/account-setting' import AccountSetting from '@/app/components/header/account-setting'
import ApiBasedExtensionModal from '@/app/components/header/account-setting/api-based-extension-page/modal' import ApiBasedExtensionModal from '@/app/components/header/account-setting/api-based-extension-page/modal'
import ModerationSettingModal from '@/app/components/app/configuration/toolbox/moderation/moderation-setting-modal' import ModerationSettingModal from '@/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal'
import ExternalDataToolModal from '@/app/components/app/configuration/tools/external-data-tool-modal' import ExternalDataToolModal from '@/app/components/app/configuration/tools/external-data-tool-modal'
import AnnotationFullModal from '@/app/components/billing/annotation-full/modal' import AnnotationFullModal from '@/app/components/billing/annotation-full/modal'
import ModelModal from '@/app/components/header/account-setting/model-provider-page/model-modal' import ModelModal from '@/app/components/header/account-setting/model-provider-page/model-modal'

View File

@ -163,9 +163,10 @@ const translation = {
moderation: { moderation: {
title: 'Content moderation', title: 'Content moderation',
description: 'Secure model output by using moderation API or maintaining a sensitive word list.', description: 'Secure model output by using moderation API or maintaining a sensitive word list.',
allEnabled: 'INPUT/OUTPUT Content Enabled', contentEnableLabel: 'Enabled moderate content',
inputEnabled: 'INPUT Content Enabled', allEnabled: 'INPUT & OUTPUT',
outputEnabled: 'OUTPUT Content Enabled', inputEnabled: 'INPUT',
outputEnabled: 'OUTPUT',
modal: { modal: {
title: 'Content moderation settings', title: 'Content moderation settings',
provider: { provider: {

View File

@ -163,9 +163,10 @@ const translation = {
moderation: { moderation: {
title: '内容审查', title: '内容审查',
description: '您可以调用审查 API 或者维护敏感词库来使模型更安全地输出。', description: '您可以调用审查 API 或者维护敏感词库来使模型更安全地输出。',
allEnabled: '审查输入/审查输出 内容已启用', contentEnableLabel: '启用审查内容',
inputEnabled: '审查输入内容已启用', allEnabled: '输入内容和输出内容',
outputEnabled: '审查输出内容已启用', inputEnabled: '输入内容',
outputEnabled: '输出内容',
modal: { modal: {
title: '内容审查设置', title: '内容审查设置',
provider: { provider: {