index.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. 'use client'
  2. import { useRef, useState } from 'react'
  3. import { t } from 'i18next'
  4. import { useParams, usePathname } from 'next/navigation'
  5. import s from './style.module.css'
  6. import Tooltip from '@/app/components/base/tooltip'
  7. import { randomString } from '@/utils'
  8. import { textToAudio } from '@/service/share'
  9. type AudioBtnProps = {
  10. value: string
  11. voice?: string
  12. className?: string
  13. isAudition?: boolean
  14. }
  15. const AudioBtn = ({
  16. value,
  17. voice,
  18. className,
  19. isAudition,
  20. }: AudioBtnProps) => {
  21. const audioRef = useRef<HTMLAudioElement | null>(null)
  22. const [isPlaying, setIsPlaying] = useState(false)
  23. const [isPause, setPause] = useState(false)
  24. const [hasEnded, setHasEnded] = useState(false)
  25. const selector = useRef(`play-tooltip-${randomString(4)}`)
  26. const params = useParams()
  27. const pathname = usePathname()
  28. const removeCodeBlocks = (inputText: any) => {
  29. const codeBlockRegex = /```[\s\S]*?```/g
  30. if (inputText)
  31. return inputText.replace(codeBlockRegex, '')
  32. return ''
  33. }
  34. const playAudio = async () => {
  35. const formData = new FormData()
  36. if (value !== '') {
  37. formData.append('text', removeCodeBlocks(value))
  38. formData.append('voice', removeCodeBlocks(voice))
  39. let url = ''
  40. let isPublic = false
  41. if (params.token) {
  42. url = '/text-to-audio'
  43. isPublic = true
  44. }
  45. else if (params.appId) {
  46. if (pathname.search('explore/installed') > -1)
  47. url = `/installed-apps/${params.appId}/text-to-audio`
  48. else
  49. url = `/apps/${params.appId}/text-to-audio`
  50. }
  51. try {
  52. const audioResponse = await textToAudio(url, isPublic, formData)
  53. const blob_bytes = Buffer.from(audioResponse.data, 'latin1')
  54. const blob = new Blob([blob_bytes], { type: 'audio/wav' })
  55. const audioUrl = URL.createObjectURL(blob)
  56. const audio = new Audio(audioUrl)
  57. audioRef.current = audio
  58. audio.play().then(() => {
  59. setIsPlaying(true)
  60. }).catch(() => {
  61. setIsPlaying(false)
  62. URL.revokeObjectURL(audioUrl)
  63. })
  64. audio.onended = () => setHasEnded(true)
  65. }
  66. catch (error) {
  67. setIsPlaying(false)
  68. console.error('Error playing audio:', error)
  69. }
  70. }
  71. }
  72. const togglePlayPause = () => {
  73. if (audioRef.current) {
  74. if (isPlaying) {
  75. setPause(true)
  76. audioRef.current.pause()
  77. }
  78. else if (!hasEnded) {
  79. setPause(false)
  80. audioRef.current.play()
  81. }
  82. else if (!isPlaying) {
  83. playAudio().then()
  84. }
  85. setIsPlaying(prevIsPlaying => !prevIsPlaying)
  86. }
  87. else {
  88. playAudio().then()
  89. }
  90. }
  91. return (
  92. <div className={`${(isPlaying && !hasEnded) ? 'mr-1' : className}`}>
  93. <Tooltip
  94. selector={selector.current}
  95. content={(!isPause ? ((isPlaying && !hasEnded) ? t('appApi.playing') : t('appApi.play')) : t('appApi.pause')) as string}
  96. className='z-10'
  97. >
  98. <div
  99. className={`box-border p-0.5 flex items-center justify-center cursor-pointer ${isAudition || 'rounded-md bg-white'}`}
  100. style={{ boxShadow: !isAudition ? '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)' : '' }}
  101. onClick={togglePlayPause}>
  102. <div className={`w-6 h-6 rounded-md ${!isAudition ? 'hover:bg-gray-200' : 'hover:bg-gray-50'} ${!isPause ? ((isPlaying && !hasEnded) ? s.playIcon : s.stopIcon) : s.pauseIcon}`}></div>
  103. </div>
  104. </Tooltip>
  105. </div>
  106. )
  107. }
  108. export default AudioBtn