Feat/attachments (#9526)
Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: JzoNg <jzongcode@gmail.com>pull/9530/head
@ -1,9 +1,3 @@
|
||||
.logTable td {
|
||||
padding: 7px 8px;
|
||||
box-sizing: border-box;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.pagination li {
|
||||
list-style: none;
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import type { AppPublisherProps } from '@/app/components/app/app-publisher'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import AppPublisher from '@/app/components/app/app-publisher'
|
||||
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import type { ModelAndParameter } from '@/app/components/app/configuration/debug/types'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import { Resolution } from '@/types/app'
|
||||
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
|
||||
type Props = Omit<AppPublisherProps, 'onPublish'> & {
|
||||
onPublish?: (modelAndParameter?: ModelAndParameter, features?: any) => Promise<any> | any
|
||||
publishedConfig?: any
|
||||
resetAppConfig?: () => void
|
||||
}
|
||||
|
||||
const FeaturesWrappedAppPublisher = (props: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const features = useFeatures(s => s.features)
|
||||
const featuresStore = useFeaturesStore()
|
||||
const [restoreConfirmOpen, setRestoreConfirmOpen] = useState(false)
|
||||
const handleConfirm = useCallback(() => {
|
||||
props.resetAppConfig?.()
|
||||
const {
|
||||
features,
|
||||
setFeatures,
|
||||
} = featuresStore!.getState()
|
||||
const newFeatures = produce(features, (draft) => {
|
||||
draft.moreLikeThis = props.publishedConfig.modelConfig.more_like_this || { enabled: false }
|
||||
draft.opening = {
|
||||
enabled: !!props.publishedConfig.modelConfig.opening_statement,
|
||||
opening_statement: props.publishedConfig.modelConfig.opening_statement || '',
|
||||
suggested_questions: props.publishedConfig.modelConfig.suggested_questions || [],
|
||||
}
|
||||
draft.moderation = props.publishedConfig.modelConfig.sensitive_word_avoidance || { enabled: false }
|
||||
draft.speech2text = props.publishedConfig.modelConfig.speech_to_text || { enabled: false }
|
||||
draft.text2speech = props.publishedConfig.modelConfig.text_to_speech || { enabled: false }
|
||||
draft.suggested = props.publishedConfig.modelConfig.suggested_questions_after_answer || { enabled: false }
|
||||
draft.citation = props.publishedConfig.modelConfig.retriever_resource || { enabled: false }
|
||||
draft.annotationReply = props.publishedConfig.modelConfig.annotation_reply || { enabled: false }
|
||||
draft.file = {
|
||||
image: {
|
||||
detail: props.publishedConfig.modelConfig.file_upload?.image?.detail || Resolution.high,
|
||||
enabled: !!props.publishedConfig.modelConfig.file_upload?.image?.enabled,
|
||||
number_limits: props.publishedConfig.modelConfig.file_upload?.image?.number_limits || 3,
|
||||
transfer_methods: props.publishedConfig.modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
|
||||
},
|
||||
enabled: !!(props.publishedConfig.modelConfig.file_upload?.enabled || props.publishedConfig.modelConfig.file_upload?.image?.enabled),
|
||||
allowed_file_types: props.publishedConfig.modelConfig.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
|
||||
allowed_file_extensions: props.publishedConfig.modelConfig.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
|
||||
allowed_file_upload_methods: props.publishedConfig.modelConfig.file_upload?.allowed_file_upload_methods || props.publishedConfig.modelConfig.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
|
||||
number_limits: props.publishedConfig.modelConfig.file_upload?.number_limits || props.publishedConfig.modelConfig.file_upload?.image?.number_limits || 3,
|
||||
} as FileUpload
|
||||
})
|
||||
setFeatures(newFeatures)
|
||||
setRestoreConfirmOpen(false)
|
||||
}, [featuresStore, props])
|
||||
|
||||
const handlePublish = useCallback((modelAndParameter?: ModelAndParameter) => {
|
||||
return props.onPublish?.(modelAndParameter, features)
|
||||
}, [features, props])
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppPublisher {...{
|
||||
...props,
|
||||
onPublish: handlePublish,
|
||||
onRestore: () => setRestoreConfirmOpen(true),
|
||||
}}/>
|
||||
{restoreConfirmOpen && (
|
||||
<Confirm
|
||||
title={t('appDebug.resetConfig.title')}
|
||||
content={t('appDebug.resetConfig.message')}
|
||||
isShow={restoreConfirmOpen}
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={() => setRestoreConfirmOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturesWrappedAppPublisher
|
||||
@ -1,40 +0,0 @@
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 58px;
|
||||
width: 98px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #EAECF0;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item:not(.selected):hover {
|
||||
border-color: #B2CCFF;
|
||||
background-color: #F5F8FF;
|
||||
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
|
||||
.item.selected {
|
||||
color: #155EEF;
|
||||
border-color: #528BFF;
|
||||
background-color: #F5F8FF;
|
||||
box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 13px;
|
||||
color: #667085;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item.selected .text {
|
||||
color: #155EEF;
|
||||
}
|
||||
|
||||
.item:not(.selected):hover {
|
||||
color: #344054;
|
||||
}
|
||||
@ -1,61 +1,103 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import Panel from '../base/feature-panel'
|
||||
import ParamConfig from './param-config'
|
||||
import { Vision } from '@/app/components/base/icons/src/vender/features'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import { Eye } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
// import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
// import { Resolution } from '@/types/app'
|
||||
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
|
||||
const ConfigVision: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
isShowVisionConfig,
|
||||
visionConfig,
|
||||
setVisionConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const { isShowVisionConfig } = useContext(ConfigContext)
|
||||
const file = useFeatures(s => s.features.file)
|
||||
const featuresStore = useFeaturesStore()
|
||||
|
||||
const handleChange = useCallback((data: FileUpload) => {
|
||||
const {
|
||||
features,
|
||||
setFeatures,
|
||||
} = featuresStore!.getState()
|
||||
|
||||
const newFeatures = produce(features, (draft) => {
|
||||
draft.file = {
|
||||
...draft.file,
|
||||
enabled: data.enabled,
|
||||
image: {
|
||||
enabled: data.enabled,
|
||||
detail: data.image?.detail,
|
||||
transfer_methods: data.image?.transfer_methods,
|
||||
number_limits: data.image?.number_limits,
|
||||
},
|
||||
}
|
||||
})
|
||||
setFeatures(newFeatures)
|
||||
}, [featuresStore])
|
||||
|
||||
if (!isShowVisionConfig)
|
||||
return null
|
||||
|
||||
return (<>
|
||||
<Panel
|
||||
className="mt-4"
|
||||
headerIcon={
|
||||
<Eye className='w-4 h-4 text-[#6938EF]'/>
|
||||
}
|
||||
title={
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-1'>{t('appDebug.vision.name')}</div>
|
||||
return (
|
||||
<div className='mt-2 flex items-center gap-2 p-2 rounded-xl border-t-[0.5px] border-l-[0.5px] bg-background-section-burn'>
|
||||
<div className='shrink-0 p-1'>
|
||||
<div className='p-1 rounded-lg border-[0.5px] border-divider-subtle shadow-xs bg-util-colors-indigo-indigo-600'>
|
||||
<Vision className='w-4 h-4 text-text-primary-on-surface' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow flex items-center'>
|
||||
<div className='mr-1 text-text-secondary system-sm-semibold'>{t('appDebug.vision.name')}</div>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[180px]' >
|
||||
{t('appDebug.vision.description')}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='shrink-0 flex items-center'>
|
||||
{/* <div className='mr-2 flex items-center gap-0.5'>
|
||||
<div className='text-text-tertiary system-xs-medium-uppercase'>{t('appDebug.vision.visionSettings.resolution')}</div>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[180px]' >
|
||||
{t('appDebug.vision.description')}
|
||||
{t('appDebug.vision.visionSettings.resolutionTooltip').split('\n').map(item => (
|
||||
<div key={item}>{item}</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
headerRight={
|
||||
<div className='flex items-center'>
|
||||
<ParamConfig />
|
||||
<div className='ml-4 mr-3 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Switch
|
||||
defaultValue={visionConfig.enabled}
|
||||
onChange={value => setVisionConfig({
|
||||
...visionConfig,
|
||||
enabled: value,
|
||||
})}
|
||||
size='md'
|
||||
</div> */}
|
||||
{/* <div className='flex items-center gap-1'>
|
||||
<OptionCard
|
||||
title={t('appDebug.vision.visionSettings.high')}
|
||||
selected={file?.image?.detail === Resolution.high}
|
||||
onSelect={() => handleChange(Resolution.high)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
noBodySpacing
|
||||
/>
|
||||
</>
|
||||
<OptionCard
|
||||
title={t('appDebug.vision.visionSettings.low')}
|
||||
selected={file?.image?.detail === Resolution.low}
|
||||
onSelect={() => handleChange(Resolution.low)}
|
||||
/>
|
||||
</div> */}
|
||||
<ParamConfig />
|
||||
<div className='ml-1 mr-3 w-[1px] h-3.5 bg-divider-subtle'></div>
|
||||
<Switch
|
||||
defaultValue={file?.enabled}
|
||||
onChange={value => handleChange({
|
||||
...(file || {}),
|
||||
enabled: value,
|
||||
})}
|
||||
size='md'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ConfigVision)
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import s from './style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type OPTION = {
|
||||
label: string
|
||||
value: any
|
||||
}
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
options: OPTION[]
|
||||
value: any
|
||||
onChange: (value: any) => void
|
||||
}
|
||||
|
||||
const RadioGroup: FC<Props> = ({
|
||||
className = '',
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn(className, 'flex')}>
|
||||
{options.map(item => (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(s.item, item.value === value && s.checked)}
|
||||
onClick={() => onChange(item.value)}
|
||||
>
|
||||
<div className={s.radio}></div>
|
||||
<div className='text-[13px] font-medium text-gray-900'>{item.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(RadioGroup)
|
||||
@ -1,24 +0,0 @@
|
||||
.item {
|
||||
@apply grow flex items-center h-8 px-2.5 rounded-lg bg-gray-25 border border-gray-100 cursor-pointer space-x-2;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background-color: #ffffff;
|
||||
border-color: #B2CCFF;
|
||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
}
|
||||
|
||||
.item.checked {
|
||||
background-color: #ffffff;
|
||||
border-color: #528BFF;
|
||||
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.06), 0px 1px 3px 0px rgba(16, 24, 40, 0.10);
|
||||
}
|
||||
|
||||
.radio {
|
||||
@apply w-4 h-4 border-[2px] border-gray-200 rounded-full;
|
||||
}
|
||||
|
||||
.item.checked .radio {
|
||||
border-width: 5px;
|
||||
border-color: #155eef;
|
||||
}
|
||||
@ -1,220 +0,0 @@
|
||||
'use client'
|
||||
import useSWR from 'swr'
|
||||
import type { FC } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import React, { Fragment } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Listbox, Transition } from '@headlessui/react'
|
||||
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import classNames from '@/utils/classnames'
|
||||
import RadioGroup from '@/app/components/app/configuration/config-vision/radio-group'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import { fetchAppVoices } from '@/service/apps'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { languages } from '@/i18n/language'
|
||||
import { TtsAutoPlay } from '@/types/app'
|
||||
const VoiceParamConfig: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const pathname = usePathname()
|
||||
const matched = pathname.match(/\/app\/([^/]+)/)
|
||||
const appId = (matched?.length && matched[1]) ? matched[1] : ''
|
||||
|
||||
const {
|
||||
textToSpeechConfig,
|
||||
setTextToSpeechConfig,
|
||||
} = useContext(ConfigContext)
|
||||
|
||||
let languageItem = languages.find(item => item.value === textToSpeechConfig.language)
|
||||
const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select')
|
||||
if (languages && !languageItem && languages.length > 0)
|
||||
languageItem = languages[0]
|
||||
const language = languageItem?.value
|
||||
const voiceItems = useSWR({ appId, language }, fetchAppVoices).data
|
||||
let voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice)
|
||||
if (voiceItems && !voiceItem && voiceItems.length > 0)
|
||||
voiceItem = voiceItems[0]
|
||||
|
||||
const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div className='leading-6 text-base font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.title')}</div>
|
||||
<div className='pt-3 space-y-6'>
|
||||
<div>
|
||||
<div className='mb-2 flex items-center space-x-1'>
|
||||
<div
|
||||
className='leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.language')}</div>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[180px]'>
|
||||
{t('appDebug.voice.voiceSettings.resolutionTooltip').split('\n').map(item => (
|
||||
<div key={item}>{item}</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Listbox
|
||||
value={languageItem}
|
||||
onChange={(value: Item) => {
|
||||
setTextToSpeechConfig({
|
||||
...textToSpeechConfig,
|
||||
language: String(value.value),
|
||||
})
|
||||
}}
|
||||
>
|
||||
<div className={'relative h-9'}>
|
||||
<Listbox.Button
|
||||
className={'w-full h-full rounded-lg border-0 bg-gray-100 py-1.5 pl-3 pr-10 sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200 cursor-pointer'}>
|
||||
<span className={classNames('block truncate text-left', !languageItem?.name && 'text-gray-400')}>
|
||||
{languageItem?.name ? t(`common.voice.language.${languageItem?.value.replace('-', '')}`) : localLanguagePlaceholder}
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<ChevronDownIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
|
||||
<Listbox.Options
|
||||
className="absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm">
|
||||
{languages.map((item: Item) => (
|
||||
<Listbox.Option
|
||||
key={item.value}
|
||||
className={({ active }) =>
|
||||
`relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : ''
|
||||
}`
|
||||
}
|
||||
value={item}
|
||||
disabled={false}
|
||||
>
|
||||
{({ /* active, */ selected }) => (
|
||||
<>
|
||||
<span
|
||||
className={classNames('block', selected && 'font-normal')}>{t(`common.voice.language.${(item.value).toString().replace('-', '')}`)}</span>
|
||||
{(selected || item.value === textToSpeechConfig.language) && (
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700',
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className='mb-2 leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.voice')}</div>
|
||||
<Listbox
|
||||
value={voiceItem ?? {}}
|
||||
disabled={!languageItem}
|
||||
onChange={(value: Item) => {
|
||||
if (!value.value)
|
||||
return
|
||||
setTextToSpeechConfig({
|
||||
...textToSpeechConfig,
|
||||
voice: String(value.value),
|
||||
})
|
||||
}}
|
||||
>
|
||||
<div className={'relative h-9'}>
|
||||
<Listbox.Button
|
||||
className={'w-full h-full rounded-lg border-0 bg-gray-100 py-1.5 pl-3 pr-10 sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200 cursor-pointer'}>
|
||||
<span
|
||||
className={classNames('block truncate text-left', !voiceItem?.name && 'text-gray-400')}>{voiceItem?.name ?? localVoicePlaceholder}</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<ChevronDownIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
|
||||
<Listbox.Options
|
||||
className="absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm">
|
||||
{voiceItems?.map((item: Item) => (
|
||||
<Listbox.Option
|
||||
key={item.value}
|
||||
className={({ active }) =>
|
||||
`relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : ''
|
||||
}`
|
||||
}
|
||||
value={item}
|
||||
disabled={false}
|
||||
>
|
||||
{({ /* active, */ selected }) => (
|
||||
<>
|
||||
<span className={classNames('block', selected && 'font-normal')}>{item.name}</span>
|
||||
{(selected || item.value === textToSpeechConfig.voice) && (
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700',
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className='mb-2 leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.autoPlay')}</div>
|
||||
<RadioGroup
|
||||
className='space-x-3'
|
||||
options={[
|
||||
{
|
||||
label: t('appDebug.voice.voiceSettings.autoPlayEnabled'),
|
||||
value: TtsAutoPlay.enabled,
|
||||
},
|
||||
{
|
||||
label: t('appDebug.voice.voiceSettings.autoPlayDisabled'),
|
||||
value: TtsAutoPlay.disabled,
|
||||
},
|
||||
]}
|
||||
value={textToSpeechConfig.autoPlay ? textToSpeechConfig.autoPlay : TtsAutoPlay.disabled}
|
||||
onChange={(value: TtsAutoPlay) => {
|
||||
setTextToSpeechConfig({
|
||||
...textToSpeechConfig,
|
||||
autoPlay: value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(VoiceParamConfig)
|
||||
@ -1,41 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { memo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import VoiceParamConfig from './param-config-content'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
|
||||
const ParamsConfig: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className={cn('flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
|
||||
<Settings01 className='w-3.5 h-3.5 ' />
|
||||
<div className='ml-1 leading-[18px] text-xs font-medium '>{t('appDebug.voice.settings')}</div>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent style={{ zIndex: 50 }}>
|
||||
<div className='w-80 sm:w-[412px] p-4 bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg space-y-3'>
|
||||
<VoiceParamConfig />
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
export default memo(ParamsConfig)
|
||||
@ -1,40 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PlusIcon } from '@heroicons/react/24/solid'
|
||||
|
||||
export type IAddFeatureBtnProps = {
|
||||
toBottomHeight: number
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const ITEM_HEIGHT = 48
|
||||
|
||||
const AddFeatureBtn: FC<IAddFeatureBtnProps> = ({
|
||||
toBottomHeight,
|
||||
onClick,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div
|
||||
className='absolute z-[9] left-0 right-0 flex justify-center pb-4'
|
||||
style={{
|
||||
top: toBottomHeight - ITEM_HEIGHT,
|
||||
background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.00) 0%, #FFF 100%)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='flex items-center h-8 space-x-2 px-3
|
||||
border border-primary-100 rounded-lg bg-primary-25 hover:bg-primary-50 cursor-pointer
|
||||
text-xs font-semibold text-primary-600 uppercase
|
||||
'
|
||||
onClick={onClick}
|
||||
>
|
||||
<PlusIcon className='w-4 h-4 font-semibold' />
|
||||
<div>{t('appDebug.operation.addFeature')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(AddFeatureBtn)
|
||||
@ -1,52 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import s from './style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
|
||||
export type IFeatureItemProps = {
|
||||
icon: React.ReactNode
|
||||
previewImgClassName?: string
|
||||
title: string
|
||||
description: string
|
||||
value: boolean
|
||||
onChange: (value: boolean) => void
|
||||
}
|
||||
|
||||
const FeatureItem: FC<IFeatureItemProps> = ({
|
||||
icon,
|
||||
previewImgClassName,
|
||||
title,
|
||||
description,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn(s.wrap, 'relative flex justify-between p-3 rounded-xl border border-transparent bg-gray-50 hover:border-gray-200 cursor-pointer')}>
|
||||
<div className='flex space-x-3 mr-2'>
|
||||
{/* icon */}
|
||||
<div
|
||||
className='shrink-0 flex items-center justify-center w-8 h-8 rounded-lg border border-gray-200 bg-white'
|
||||
style={{
|
||||
boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)',
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className='text-sm font-semibold text-gray-800'>{title}</div>
|
||||
<div className='text-xs font-normal text-gray-500'>{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Switch onChange={onChange} defaultValue={value} />
|
||||
{
|
||||
previewImgClassName && (
|
||||
<div className={cn(s.preview, s[previewImgClassName])}>
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(FeatureItem)
|
||||
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@ -1,41 +0,0 @@
|
||||
.preview {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%;
|
||||
transform: translate(32px, -54px);
|
||||
width: 280px;
|
||||
height: 360px;
|
||||
background: center center no-repeat;
|
||||
background-size: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.wrap:hover .preview {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.openingStatementPreview {
|
||||
background-image: url(./preview-imgs/opening-statement.png);
|
||||
}
|
||||
|
||||
.suggestedQuestionsAfterAnswerPreview {
|
||||
background-image: url(./preview-imgs/suggested-questions-after-answer.png);
|
||||
}
|
||||
|
||||
.moreLikeThisPreview {
|
||||
background-image: url(./preview-imgs/more-like-this.png);
|
||||
}
|
||||
|
||||
.speechToTextPreview {
|
||||
background-image: url(./preview-imgs/speech-to-text.png);
|
||||
}
|
||||
|
||||
.textToSpeechPreview {
|
||||
@apply shadow-lg rounded-lg;
|
||||
background-image: url(./preview-imgs/text-to-audio-preview-assistant@2x.png);
|
||||
}
|
||||
|
||||
.citationPreview {
|
||||
background-image: url(./preview-imgs/citation.png);
|
||||
}
|
||||
@ -1,172 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import FeatureGroup from '../feature-group'
|
||||
import MoreLikeThisIcon from '../../../base/icons/more-like-this-icon'
|
||||
import FeatureItem from './feature-item'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import SuggestedQuestionsAfterAnswerIcon from '@/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon'
|
||||
import { Microphone01, Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||
import { Citations } from '@/app/components/base/icons/src/vender/solid/editor'
|
||||
import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files'
|
||||
import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
type IConfig = {
|
||||
openingStatement: boolean
|
||||
moreLikeThis: boolean
|
||||
suggestedQuestionsAfterAnswer: boolean
|
||||
speechToText: boolean
|
||||
textToSpeech: boolean
|
||||
citation: boolean
|
||||
moderation: boolean
|
||||
annotation: boolean
|
||||
}
|
||||
|
||||
export type IChooseFeatureProps = {
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
config: IConfig
|
||||
isChatApp: boolean
|
||||
onChange: (key: string, value: boolean) => void
|
||||
showTextToSpeechItem?: boolean
|
||||
showSpeechToTextItem?: boolean
|
||||
}
|
||||
|
||||
const OpeningStatementIcon = (
|
||||
<svg width="15" height="13" viewBox="0 0 15 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M8.33328 0.333252C4.83548 0.333252 1.99995 3.16878 1.99995 6.66659C1.99995 7.37325 2.11594 8.05419 2.33045 8.6906C2.36818 8.80254 2.39039 8.86877 2.40482 8.91762L2.40955 8.93407L2.40705 8.93928C2.38991 8.97462 2.36444 9.02207 2.31681 9.11025L1.21555 11.1486C1.1473 11.2749 1.07608 11.4066 1.02711 11.5212C0.978424 11.6351 0.899569 11.844 0.938369 12.0916C0.98385 12.3819 1.15471 12.6375 1.40556 12.7905C1.61957 12.9211 1.84276 12.9281 1.96659 12.9267C2.09117 12.9252 2.24012 12.9098 2.3829 12.895L5.81954 12.5397C5.87458 12.534 5.90335 12.5311 5.92443 12.5295L5.92715 12.5293L5.93539 12.5322C5.96129 12.5415 5.99642 12.555 6.05705 12.5784C6.76435 12.8509 7.53219 12.9999 8.33328 12.9999C11.8311 12.9999 14.6666 10.1644 14.6666 6.66659C14.6666 3.16878 11.8311 0.333252 8.33328 0.333252ZM5.97966 4.7214C6.73118 4.08722 7.73139 4.27352 8.3312 4.96609C8.931 4.27352 9.9183 4.09389 10.6827 4.7214C11.4472 5.34892 11.5401 6.41591 10.9499 7.16596C10.5843 7.63065 9.66655 8.47935 9.02117 9.05789C8.78411 9.2704 8.66558 9.37666 8.52332 9.41947C8.40129 9.4562 8.2611 9.4562 8.13907 9.41947C7.99682 9.37666 7.87829 9.2704 7.64122 9.05789C6.99584 8.47935 6.07814 7.63065 5.71251 7.16596C5.12234 6.41591 5.22815 5.35559 5.97966 4.7214Z" fill="#DD2590" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const ChooseFeature: FC<IChooseFeatureProps> = ({
|
||||
isShow,
|
||||
onClose,
|
||||
isChatApp,
|
||||
config,
|
||||
onChange,
|
||||
showTextToSpeechItem,
|
||||
showSpeechToTextItem,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className='w-[400px]'
|
||||
title={t('appDebug.operation.addFeature')}
|
||||
closable
|
||||
overflowVisible
|
||||
>
|
||||
<div className='pt-5 pb-10'>
|
||||
{/* Chat Feature */}
|
||||
{isChatApp && (
|
||||
<FeatureGroup
|
||||
title={t('appDebug.feature.groupChat.title')}
|
||||
description={t('appDebug.feature.groupChat.description') as string}
|
||||
>
|
||||
<>
|
||||
<FeatureItem
|
||||
icon={OpeningStatementIcon}
|
||||
previewImgClassName='openingStatementPreview'
|
||||
title={t('appDebug.feature.conversationOpener.title')}
|
||||
description={t('appDebug.feature.conversationOpener.description')}
|
||||
value={config.openingStatement}
|
||||
onChange={value => onChange('openingStatement', value)}
|
||||
/>
|
||||
<FeatureItem
|
||||
icon={<SuggestedQuestionsAfterAnswerIcon />}
|
||||
previewImgClassName='suggestedQuestionsAfterAnswerPreview'
|
||||
title={t('appDebug.feature.suggestedQuestionsAfterAnswer.title')}
|
||||
description={t('appDebug.feature.suggestedQuestionsAfterAnswer.description')}
|
||||
value={config.suggestedQuestionsAfterAnswer}
|
||||
onChange={value => onChange('suggestedQuestionsAfterAnswer', value)}
|
||||
/>
|
||||
{
|
||||
showTextToSpeechItem && (
|
||||
<FeatureItem
|
||||
icon={<Speaker className='w-4 h-4 text-[#7839EE]' />}
|
||||
previewImgClassName='textToSpeechPreview'
|
||||
title={t('appDebug.feature.textToSpeech.title')}
|
||||
description={t('appDebug.feature.textToSpeech.description')}
|
||||
value={config.textToSpeech}
|
||||
onChange={value => onChange('textToSpeech', value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
showSpeechToTextItem && (
|
||||
<FeatureItem
|
||||
icon={<Microphone01 className='w-4 h-4 text-[#7839EE]' />}
|
||||
previewImgClassName='speechToTextPreview'
|
||||
title={t('appDebug.feature.speechToText.title')}
|
||||
description={t('appDebug.feature.speechToText.description')}
|
||||
value={config.speechToText}
|
||||
onChange={value => onChange('speechToText', value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<FeatureItem
|
||||
icon={<Citations className='w-4 h-4 text-[#FD853A]' />}
|
||||
previewImgClassName='citationPreview'
|
||||
title={t('appDebug.feature.citation.title')}
|
||||
description={t('appDebug.feature.citation.description')}
|
||||
value={config.citation}
|
||||
onChange={value => onChange('citation', value)}
|
||||
/>
|
||||
</>
|
||||
</FeatureGroup>
|
||||
)}
|
||||
|
||||
{/* Text Generation Feature */}
|
||||
{!isChatApp && (
|
||||
<FeatureGroup title={t('appDebug.feature.groupExperience.title')}>
|
||||
<>
|
||||
<FeatureItem
|
||||
icon={<MoreLikeThisIcon />}
|
||||
previewImgClassName='moreLikeThisPreview'
|
||||
title={t('appDebug.feature.moreLikeThis.title')}
|
||||
description={t('appDebug.feature.moreLikeThis.description')}
|
||||
value={config.moreLikeThis}
|
||||
onChange={value => onChange('moreLikeThis', value)}
|
||||
/>
|
||||
{
|
||||
showTextToSpeechItem && (
|
||||
<FeatureItem
|
||||
icon={<Speaker className='w-4 h-4 text-[#7839EE]' />}
|
||||
previewImgClassName='textToSpeechPreview'
|
||||
title={t('appDebug.feature.textToSpeech.title')}
|
||||
description={t('appDebug.feature.textToSpeech.description')}
|
||||
value={config.textToSpeech}
|
||||
onChange={value => onChange('textToSpeech', value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
</FeatureGroup>
|
||||
)}
|
||||
<FeatureGroup title={t('appDebug.feature.toolbox.title')}>
|
||||
<>
|
||||
<FeatureItem
|
||||
icon={<FileSearch02 className='w-4 h-4 text-[#039855]' />}
|
||||
previewImgClassName=''
|
||||
title={t('appDebug.feature.moderation.title')}
|
||||
description={t('appDebug.feature.moderation.description')}
|
||||
value={config.moderation}
|
||||
onChange={value => onChange('moderation', value)}
|
||||
/>
|
||||
{isChatApp && (
|
||||
<FeatureItem
|
||||
icon={<MessageFast className='w-4 h-4 text-[#444CE7]' />}
|
||||
title={t('appDebug.feature.annotation.title')}
|
||||
description={t('appDebug.feature.annotation.description')}
|
||||
value={config.annotation}
|
||||
onChange={value => onChange('annotation', value)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</FeatureGroup>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
export default React.memo(ChooseFeature)
|
||||
@ -1,31 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import GroupName from '@/app/components/app/configuration/base/group-name'
|
||||
|
||||
export type IFeatureGroupProps = {
|
||||
title: string
|
||||
description?: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const FeatureGroup: FC<IFeatureGroupProps> = ({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className='mb-6'>
|
||||
<div className='mb-2'>
|
||||
<GroupName name={title} />
|
||||
{description && (
|
||||
<div className='text-xs font-normal text-gray-500'>{description}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(FeatureGroup)
|
||||
@ -0,0 +1,109 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Select from '@/app/components/base/select'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
|
||||
import type { Inputs } from '@/models/debug'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
inputs: Inputs
|
||||
}
|
||||
|
||||
const ChatUserInput = ({
|
||||
inputs,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { modelConfig, setInputs } = useContext(ConfigContext)
|
||||
|
||||
const promptVariables = modelConfig.configs.prompt_variables.filter(({ key, name }) => {
|
||||
return key && key?.trim() && name && name?.trim()
|
||||
})
|
||||
|
||||
const promptVariableObj = (() => {
|
||||
const obj: Record<string, boolean> = {}
|
||||
promptVariables.forEach((input) => {
|
||||
obj[input.key] = true
|
||||
})
|
||||
return obj
|
||||
})()
|
||||
|
||||
const handleInputValueChange = (key: string, value: string) => {
|
||||
if (!(key in promptVariableObj))
|
||||
return
|
||||
|
||||
const newInputs = { ...inputs }
|
||||
promptVariables.forEach((input) => {
|
||||
if (input.key === key)
|
||||
newInputs[key] = value
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}
|
||||
|
||||
if (!promptVariables.length)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className={cn('bg-components-panel-on-panel-item-bg rounded-xl border-[0.5px] border-components-panel-border-subtle shadow-xs z-[1]')}>
|
||||
<div className='px-4 pt-3 pb-4'>
|
||||
{promptVariables.map(({ key, name, type, options, max_length, required }, index) => (
|
||||
<div
|
||||
key={key}
|
||||
className='mb-4 last-of-type:mb-0'
|
||||
>
|
||||
<div>
|
||||
<div className='h-6 mb-1 flex items-center gap-1 text-text-secondary system-sm-semibold'>
|
||||
<div className='truncate'>{name || key}</div>
|
||||
{!required && <span className='text-text-tertiary system-xs-regular'>{t('workflow.panel.optional')}</span>}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
{type === 'string' && (
|
||||
<Input
|
||||
value={inputs[key] ? `${inputs[key]}` : ''}
|
||||
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
|
||||
placeholder={name}
|
||||
autoFocus={index === 0}
|
||||
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
|
||||
/>
|
||||
)}
|
||||
{type === 'paragraph' && (
|
||||
<Textarea
|
||||
className='grow h-[120px]'
|
||||
placeholder={name}
|
||||
value={inputs[key] ? `${inputs[key]}` : ''}
|
||||
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
|
||||
/>
|
||||
)}
|
||||
{type === 'select' && (
|
||||
<Select
|
||||
className='w-full'
|
||||
defaultValue={inputs[key] as string}
|
||||
onSelect={(i) => { handleInputValueChange(key, i.value as string) }}
|
||||
items={(options || []).map(i => ({ name: i, value: i }))}
|
||||
allowSearch={false}
|
||||
bgClassName='bg-gray-50'
|
||||
/>
|
||||
)}
|
||||
{type === 'number' && (
|
||||
<Input
|
||||
type='number'
|
||||
value={inputs[key] ? `${inputs[key]}` : ''}
|
||||
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
|
||||
placeholder={name}
|
||||
autoFocus={index === 0}
|
||||
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChatUserInput
|
||||
@ -1,25 +0,0 @@
|
||||
'use client'
|
||||
import React, { type FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||
import { Citations } from '@/app/components/base/icons/src/vender/solid/editor'
|
||||
|
||||
const Citation: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<div className='flex items-center gap-2'>
|
||||
<div>{t('appDebug.feature.citation.title')}</div>
|
||||
</div>
|
||||
}
|
||||
headerIcon={<Citations className='w-4 h-4 text-[#FD853A]' />}
|
||||
headerRight={
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.feature.citation.resDes')}</div>
|
||||
}
|
||||
noBodySpacing
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(Citation)
|
||||
@ -1,65 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import GroupName from '../../base/group-name'
|
||||
import type { IOpeningStatementProps } from './opening-statement'
|
||||
import OpeningStatement from './opening-statement'
|
||||
import SuggestedQuestionsAfterAnswer from './suggested-questions-after-answer'
|
||||
import SpeechToText from './speech-to-text'
|
||||
import TextToSpeech from './text-to-speech'
|
||||
import Citation from './citation'
|
||||
/*
|
||||
* Include
|
||||
* 1. Conversation Opener
|
||||
* 2. Opening Suggestion
|
||||
* 3. Next question suggestion
|
||||
*/
|
||||
type ChatGroupProps = {
|
||||
isShowOpeningStatement: boolean
|
||||
openingStatementConfig: IOpeningStatementProps
|
||||
isShowSuggestedQuestionsAfterAnswer: boolean
|
||||
isShowSpeechText: boolean
|
||||
isShowTextToSpeech: boolean
|
||||
isShowCitation: boolean
|
||||
}
|
||||
const ChatGroup: FC<ChatGroupProps> = ({
|
||||
isShowOpeningStatement,
|
||||
openingStatementConfig,
|
||||
isShowSuggestedQuestionsAfterAnswer,
|
||||
isShowSpeechText,
|
||||
isShowTextToSpeech,
|
||||
isShowCitation,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='mt-7'>
|
||||
<GroupName name={t('appDebug.feature.groupChat.title')} />
|
||||
<div className='space-y-3'>
|
||||
{isShowOpeningStatement && (
|
||||
<OpeningStatement {...openingStatementConfig} />
|
||||
)}
|
||||
{isShowSuggestedQuestionsAfterAnswer && (
|
||||
<SuggestedQuestionsAfterAnswer />
|
||||
)}
|
||||
{
|
||||
isShowTextToSpeech && (
|
||||
<TextToSpeech />
|
||||
)
|
||||
}
|
||||
{
|
||||
isShowSpeechText && (
|
||||
<SpeechToText />
|
||||
)
|
||||
}
|
||||
{
|
||||
isShowCitation && (
|
||||
<Citation />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ChatGroup)
|
||||
@ -1,25 +0,0 @@
|
||||
'use client'
|
||||
import React, { type FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||
import { Microphone01 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||
|
||||
const SpeechToTextConfig: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<div className='flex items-center gap-2'>
|
||||
<div>{t('appDebug.feature.speechToText.title')}</div>
|
||||
</div>
|
||||
}
|
||||
headerIcon={<Microphone01 className='w-4 h-4 text-[#7839EE]' />}
|
||||
headerRight={
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.feature.speechToText.resDes')}</div>
|
||||
}
|
||||
noBodySpacing
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(SpeechToTextConfig)
|
||||
@ -1,34 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||
import SuggestedQuestionsAfterAnswerIcon from '@/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
const SuggestedQuestionsAfterAnswer: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<div className='flex items-center gap-1'>
|
||||
<div>{t('appDebug.feature.suggestedQuestionsAfterAnswer.title')}</div>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[180px]'>
|
||||
{t('appDebug.feature.suggestedQuestionsAfterAnswer.description')}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
headerIcon={<SuggestedQuestionsAfterAnswerIcon />}
|
||||
headerRight={
|
||||
<div className='text-xs text-gray-500'>{t('appDebug.feature.suggestedQuestionsAfterAnswer.resDes')}</div>
|
||||
}
|
||||
noBodySpacing
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(SuggestedQuestionsAfterAnswer)
|
||||
@ -1,55 +0,0 @@
|
||||
'use client'
|
||||
import useSWR from 'swr'
|
||||
import React, { type FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import Panel from '@/app/components/app/configuration/base/feature-panel'
|
||||
import { Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import { languages } from '@/i18n/language'
|
||||
import { fetchAppVoices } from '@/service/apps'
|
||||
import AudioBtn from '@/app/components/base/audio-btn'
|
||||
|
||||
const TextToSpeech: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
textToSpeechConfig,
|
||||
} = useContext(ConfigContext)
|
||||
|
||||
const pathname = usePathname()
|
||||
const matched = pathname.match(/\/app\/([^/]+)/)
|
||||
const appId = (matched?.length && matched[1]) ? matched[1] : ''
|
||||
const language = textToSpeechConfig.language
|
||||
const languageInfo = languages.find(i => i.value === textToSpeechConfig.language)
|
||||
|
||||
const voiceItems = useSWR({ appId, language }, fetchAppVoices).data
|
||||
const voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice)
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<div className='flex items-center'>
|
||||
<div>{t('appDebug.feature.textToSpeech.title')}</div>
|
||||
</div>
|
||||
}
|
||||
headerIcon={<Speaker className='w-4 h-4 text-[#7839EE]' />}
|
||||
headerRight={
|
||||
<div className='text-xs text-gray-500 inline-flex items-center gap-2'>
|
||||
{languageInfo && (`${languageInfo?.name} - `)}{voiceItem?.name ?? t('appDebug.voice.defaultDisplay')}
|
||||
{ languageInfo?.example && (
|
||||
<AudioBtn
|
||||
value={languageInfo?.example}
|
||||
isAudition
|
||||
voice={textToSpeechConfig.voice}
|
||||
noCache
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
noBodySpacing
|
||||
isShowTextToSpeech
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(TextToSpeech)
|
||||
@ -0,0 +1,13 @@
|
||||
import type { PromptVariable } from '@/models/debug'
|
||||
|
||||
export function replaceStringWithValues(str: string, promptVariables: PromptVariable[], inputs: Record<string, any>) {
|
||||
return str.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
||||
const name = inputs[key]
|
||||
if (name) { // has set value
|
||||
return name
|
||||
}
|
||||
|
||||
const valueObj: PromptVariable | undefined = promptVariables.find(v => v.key === key)
|
||||
return valueObj ? `{{${valueObj.name}}}` : match
|
||||
})
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files'
|
||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import { fetchCodeBasedExtensionList } from '@/service/common'
|
||||
import I18n from '@/context/i18n'
|
||||
const Moderation = () => {
|
||||
const { t } = useTranslation()
|
||||
const { setShowModerationSettingModal } = useModalContext()
|
||||
const { locale } = useContext(I18n)
|
||||
const {
|
||||
moderationConfig,
|
||||
setModerationConfig,
|
||||
} = useContext(ConfigContext)
|
||||
const { data: codeBasedExtensionList } = useSWR(
|
||||
'/code-based-extension?module=moderation',
|
||||
fetchCodeBasedExtensionList,
|
||||
)
|
||||
|
||||
const handleOpenModerationSettingModal = () => {
|
||||
setShowModerationSettingModal({
|
||||
payload: moderationConfig,
|
||||
onSaveCallback: setModerationConfig,
|
||||
})
|
||||
}
|
||||
|
||||
const renderInfo = () => {
|
||||
let prefix = ''
|
||||
let suffix = ''
|
||||
if (moderationConfig.type === 'openai_moderation')
|
||||
prefix = t('appDebug.feature.moderation.modal.provider.openai')
|
||||
else if (moderationConfig.type === 'keywords')
|
||||
prefix = t('appDebug.feature.moderation.modal.provider.keywords')
|
||||
else if (moderationConfig.type === 'api')
|
||||
prefix = t('common.apiBasedExtension.selector.title')
|
||||
else
|
||||
prefix = codeBasedExtensionList?.data.find(item => item.name === moderationConfig.type)?.label[locale] || ''
|
||||
|
||||
if (moderationConfig.config?.inputs_config?.enabled && moderationConfig.config?.outputs_config?.enabled)
|
||||
suffix = t('appDebug.feature.moderation.allEnabled')
|
||||
else if (moderationConfig.config?.inputs_config?.enabled)
|
||||
suffix = t('appDebug.feature.moderation.inputEnabled')
|
||||
else if (moderationConfig.config?.outputs_config?.enabled)
|
||||
suffix = t('appDebug.feature.moderation.outputEnabled')
|
||||
|
||||
return `${prefix} · ${suffix}`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex items-center px-3 h-12 bg-gray-50 rounded-xl overflow-hidden'>
|
||||
<div className='shrink-0 flex items-center justify-center mr-1 w-6 h-6'>
|
||||
<FileSearch02 className='shrink-0 w-4 h-4 text-[#039855]' />
|
||||
</div>
|
||||
<div className='shrink-0 mr-2 whitespace-nowrap text-sm text-gray-800 font-semibold'>
|
||||
{t('appDebug.feature.moderation.title')}
|
||||
</div>
|
||||
<div
|
||||
className='grow block w-0 text-right text-xs text-gray-500 truncate'
|
||||
title={renderInfo()}>
|
||||
{renderInfo()}
|
||||
</div>
|
||||
<div className='shrink-0 ml-4 mr-1 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<div
|
||||
className={`
|
||||
shrink-0 flex items-center px-3 h-7 cursor-pointer rounded-md
|
||||
text-xs text-gray-700 font-medium hover:bg-gray-200
|
||||
`}
|
||||
onClick={handleOpenModerationSettingModal}
|
||||
>
|
||||
<Settings01 className='mr-[5px] w-3.5 h-3.5' />
|
||||
{t('common.operation.settings')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Moderation
|
||||
@ -1,9 +1,3 @@
|
||||
.logTable td {
|
||||
padding: 7px 8px;
|
||||
box-sizing: border-box;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.pagination li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@ -1,9 +1,3 @@
|
||||
.logTable td {
|
||||
padding: 7px 8px;
|
||||
box-sizing: border-box;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.pagination li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiArrowRightSLine,
|
||||
RiHammerFill,
|
||||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import type { ToolInfoInThought } from '../type'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type ToolDetailProps = {
|
||||
payload: ToolInfoInThought
|
||||
}
|
||||
const ToolDetail = ({
|
||||
payload,
|
||||
}: ToolDetailProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { name, label, input, isFinished, output } = payload
|
||||
const toolLabel = name.startsWith('dataset_') ? t('dataset.knowledge') : label
|
||||
const [expand, setExpand] = useState(false)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-xl',
|
||||
!expand && 'border-l-[0.25px] border-components-panel-border bg-workflow-process-bg',
|
||||
expand && 'border-[0.5px] border-components-panel-border-subtle bg-background-section-burn',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center system-xs-medium text-text-tertiary px-2.5 py-2 cursor-pointer',
|
||||
expand && 'pb-1.5',
|
||||
)}
|
||||
onClick={() => setExpand(!expand)}
|
||||
>
|
||||
{isFinished && <RiHammerFill className='mr-1 w-3.5 h-3.5' />}
|
||||
{!isFinished && <RiLoader2Line className='mr-1 w-3.5 h-3.5 animate-spin' />}
|
||||
{t(`tools.thought.${isFinished ? 'used' : 'using'}`)}
|
||||
<div className='mx-1 text-text-secondary'>{toolLabel}</div>
|
||||
{!expand && <RiArrowRightSLine className='w-4 h-4' />}
|
||||
{expand && <RiArrowDownSLine className='ml-auto w-4 h-4' />}
|
||||
</div>
|
||||
{
|
||||
expand && (
|
||||
<>
|
||||
<div className='mb-0.5 mx-1 rounded-[10px] bg-components-panel-on-panel-item-bg text-text-secondary'>
|
||||
<div className='flex items-center justify-between px-2 pt-1 h-7 system-xs-semibold-uppercase'>
|
||||
{t('tools.thought.requestTitle')}
|
||||
</div>
|
||||
<div className='pt-1 px-3 pb-2 code-xs-regular break-words'>
|
||||
{input}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mx-1 mb-1 rounded-[10px] bg-components-panel-on-panel-item-bg text-text-secondary'>
|
||||
<div className='flex items-center justify-between px-2 pt-1 h-7 system-xs-semibold-uppercase'>
|
||||
{t('tools.thought.responseTitle')}
|
||||
</div>
|
||||
<div className='pt-1 px-3 pb-2 code-xs-regular break-words'>
|
||||
{output}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ToolDetail
|
||||