param-config-content.tsx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. 'use client'
  2. import useSWR from 'swr'
  3. import type { FC } from 'react'
  4. import { useContext } from 'use-context-selector'
  5. import React, { Fragment } from 'react'
  6. import { usePathname } from 'next/navigation'
  7. import { useTranslation } from 'react-i18next'
  8. import { Listbox, Transition } from '@headlessui/react'
  9. import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid'
  10. import classNames from '@/utils/classnames'
  11. import RadioGroup from '@/app/components/app/configuration/config-vision/radio-group'
  12. import type { Item } from '@/app/components/base/select'
  13. import ConfigContext from '@/context/debug-configuration'
  14. import { fetchAppVoices } from '@/service/apps'
  15. import Tooltip from '@/app/components/base/tooltip'
  16. import { languages } from '@/i18n/language'
  17. import { TtsAutoPlay } from '@/types/app'
  18. const VoiceParamConfig: FC = () => {
  19. const { t } = useTranslation()
  20. const pathname = usePathname()
  21. const matched = pathname.match(/\/app\/([^/]+)/)
  22. const appId = (matched?.length && matched[1]) ? matched[1] : ''
  23. const {
  24. textToSpeechConfig,
  25. setTextToSpeechConfig,
  26. } = useContext(ConfigContext)
  27. let languageItem = languages.find(item => item.value === textToSpeechConfig.language)
  28. const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select')
  29. if (languages && !languageItem && languages.length > 0)
  30. languageItem = languages[0]
  31. const language = languageItem?.value
  32. const voiceItems = useSWR({ appId, language }, fetchAppVoices).data
  33. let voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice)
  34. if (voiceItems && !voiceItem && voiceItems.length > 0)
  35. voiceItem = voiceItems[0]
  36. const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select')
  37. return (
  38. <div>
  39. <div>
  40. <div className='leading-6 text-base font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.title')}</div>
  41. <div className='pt-3 space-y-6'>
  42. <div>
  43. <div className='mb-2 flex items-center space-x-1'>
  44. <div
  45. className='leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.language')}</div>
  46. <Tooltip
  47. popupContent={
  48. <div className='w-[180px]'>
  49. {t('appDebug.voice.voiceSettings.resolutionTooltip').split('\n').map(item => (
  50. <div key={item}>{item}</div>
  51. ))}
  52. </div>
  53. }
  54. />
  55. </div>
  56. <Listbox
  57. value={languageItem}
  58. onChange={(value: Item) => {
  59. setTextToSpeechConfig({
  60. ...textToSpeechConfig,
  61. language: String(value.value),
  62. })
  63. }}
  64. >
  65. <div className={'relative h-9'}>
  66. <Listbox.Button
  67. 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'}>
  68. <span className={classNames('block truncate text-left', !languageItem?.name && 'text-gray-400')}>
  69. {languageItem?.name ? t(`common.voice.language.${languageItem?.value.replace('-', '')}`) : localLanguagePlaceholder}
  70. </span>
  71. <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
  72. <ChevronDownIcon
  73. className="h-5 w-5 text-gray-400"
  74. aria-hidden="true"
  75. />
  76. </span>
  77. </Listbox.Button>
  78. <Transition
  79. as={Fragment}
  80. leave="transition ease-in duration-100"
  81. leaveFrom="opacity-100"
  82. leaveTo="opacity-0"
  83. >
  84. <Listbox.Options
  85. 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">
  86. {languages.map((item: Item) => (
  87. <Listbox.Option
  88. key={item.value}
  89. className={({ active }) =>
  90. `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : ''
  91. }`
  92. }
  93. value={item}
  94. disabled={false}
  95. >
  96. {({ /* active, */ selected }) => (
  97. <>
  98. <span
  99. className={classNames('block', selected && 'font-normal')}>{t(`common.voice.language.${(item.value).toString().replace('-', '')}`)}</span>
  100. {(selected || item.value === textToSpeechConfig.language) && (
  101. <span
  102. className={classNames(
  103. 'absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700',
  104. )}
  105. >
  106. <CheckIcon className="h-5 w-5" aria-hidden="true" />
  107. </span>
  108. )}
  109. </>
  110. )}
  111. </Listbox.Option>
  112. ))}
  113. </Listbox.Options>
  114. </Transition>
  115. </div>
  116. </Listbox>
  117. </div>
  118. <div>
  119. <div
  120. className='mb-2 leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.voice')}</div>
  121. <Listbox
  122. value={voiceItem ?? {}}
  123. disabled={!languageItem}
  124. onChange={(value: Item) => {
  125. if (!value.value)
  126. return
  127. setTextToSpeechConfig({
  128. ...textToSpeechConfig,
  129. voice: String(value.value),
  130. })
  131. }}
  132. >
  133. <div className={'relative h-9'}>
  134. <Listbox.Button
  135. 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'}>
  136. <span
  137. className={classNames('block truncate text-left', !voiceItem?.name && 'text-gray-400')}>{voiceItem?.name ?? localVoicePlaceholder}</span>
  138. <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
  139. <ChevronDownIcon
  140. className="h-5 w-5 text-gray-400"
  141. aria-hidden="true"
  142. />
  143. </span>
  144. </Listbox.Button>
  145. <Transition
  146. as={Fragment}
  147. leave="transition ease-in duration-100"
  148. leaveFrom="opacity-100"
  149. leaveTo="opacity-0"
  150. >
  151. <Listbox.Options
  152. 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">
  153. {voiceItems?.map((item: Item) => (
  154. <Listbox.Option
  155. key={item.value}
  156. className={({ active }) =>
  157. `relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : ''
  158. }`
  159. }
  160. value={item}
  161. disabled={false}
  162. >
  163. {({ /* active, */ selected }) => (
  164. <>
  165. <span className={classNames('block', selected && 'font-normal')}>{item.name}</span>
  166. {(selected || item.value === textToSpeechConfig.voice) && (
  167. <span
  168. className={classNames(
  169. 'absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700',
  170. )}
  171. >
  172. <CheckIcon className="h-5 w-5" aria-hidden="true" />
  173. </span>
  174. )}
  175. </>
  176. )}
  177. </Listbox.Option>
  178. ))}
  179. </Listbox.Options>
  180. </Transition>
  181. </div>
  182. </Listbox>
  183. </div>
  184. <div>
  185. <div
  186. className='mb-2 leading-[18px] text-[13px] font-semibold text-gray-800'>{t('appDebug.voice.voiceSettings.autoPlay')}</div>
  187. <RadioGroup
  188. className='space-x-3'
  189. options={[
  190. {
  191. label: t('appDebug.voice.voiceSettings.autoPlayEnabled'),
  192. value: TtsAutoPlay.enabled,
  193. },
  194. {
  195. label: t('appDebug.voice.voiceSettings.autoPlayDisabled'),
  196. value: TtsAutoPlay.disabled,
  197. },
  198. ]}
  199. value={textToSpeechConfig.autoPlay ? textToSpeechConfig.autoPlay : TtsAutoPlay.disabled}
  200. onChange={(value: TtsAutoPlay) => {
  201. setTextToSpeechConfig({
  202. ...textToSpeechConfig,
  203. autoPlay: value,
  204. })
  205. }}
  206. />
  207. </div>
  208. </div>
  209. </div>
  210. </div>
  211. )
  212. }
  213. export default React.memo(VoiceParamConfig)