index.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useEffect, useRef, useState } from 'react'
  4. import { useBoolean } from 'ahooks'
  5. import { t } from 'i18next'
  6. import cn from 'classnames'
  7. import TextGenerationRes from '@/app/components/app/text-generate/item'
  8. import NoData from '@/app/components/share/text-generation/no-data'
  9. import Toast from '@/app/components/base/toast'
  10. import { sendCompletionMessage, updateFeedback } from '@/service/share'
  11. import type { Feedbacktype } from '@/app/components/app/chat/type'
  12. import Loading from '@/app/components/base/loading'
  13. import type { PromptConfig } from '@/models/debug'
  14. import type { InstalledApp } from '@/models/explore'
  15. export type IResultProps = {
  16. isCallBatchAPI: boolean
  17. isPC: boolean
  18. isMobile: boolean
  19. isInstalledApp: boolean
  20. installedAppInfo?: InstalledApp
  21. isError: boolean
  22. promptConfig: PromptConfig | null
  23. moreLikeThisEnabled: boolean
  24. inputs: Record<string, any>
  25. controlSend?: number
  26. controlRetry?: number
  27. controlStopResponding?: number
  28. onShowRes: () => void
  29. handleSaveMessage: (messageId: string) => void
  30. taskId?: number
  31. onCompleted: (completionRes: string, taskId?: number, success?: boolean) => void
  32. }
  33. const Result: FC<IResultProps> = ({
  34. isCallBatchAPI,
  35. isPC,
  36. isMobile,
  37. isInstalledApp,
  38. installedAppInfo,
  39. isError,
  40. promptConfig,
  41. moreLikeThisEnabled,
  42. inputs,
  43. controlSend,
  44. controlRetry,
  45. controlStopResponding,
  46. onShowRes,
  47. handleSaveMessage,
  48. taskId,
  49. onCompleted,
  50. }) => {
  51. const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false)
  52. useEffect(() => {
  53. if (controlStopResponding)
  54. setResponsingFalse()
  55. }, [controlStopResponding])
  56. const [completionRes, doSetCompletionRes] = useState('')
  57. const completionResRef = useRef('')
  58. const setCompletionRes = (res: string) => {
  59. completionResRef.current = res
  60. doSetCompletionRes(res)
  61. }
  62. const getCompletionRes = () => completionResRef.current
  63. const { notify } = Toast
  64. const isNoData = !completionRes
  65. const [messageId, setMessageId] = useState<string | null>(null)
  66. const [feedback, setFeedback] = useState<Feedbacktype>({
  67. rating: null,
  68. })
  69. const handleFeedback = async (feedback: Feedbacktype) => {
  70. await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, installedAppInfo?.id)
  71. setFeedback(feedback)
  72. }
  73. const logError = (message: string) => {
  74. notify({ type: 'error', message })
  75. }
  76. const checkCanSend = () => {
  77. // batch will check outer
  78. if (isCallBatchAPI)
  79. return true
  80. const prompt_variables = promptConfig?.prompt_variables
  81. if (!prompt_variables || prompt_variables?.length === 0)
  82. return true
  83. let hasEmptyInput = ''
  84. const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
  85. const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
  86. return res
  87. }) || [] // compatible with old version
  88. requiredVars.forEach(({ key, name }) => {
  89. if (hasEmptyInput)
  90. return
  91. if (!inputs[key])
  92. hasEmptyInput = name
  93. })
  94. if (hasEmptyInput) {
  95. logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
  96. return false
  97. }
  98. return !hasEmptyInput
  99. }
  100. const handleSend = async () => {
  101. if (isResponsing) {
  102. notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
  103. return false
  104. }
  105. if (!checkCanSend())
  106. return
  107. const data = {
  108. inputs,
  109. }
  110. setMessageId(null)
  111. setFeedback({
  112. rating: null,
  113. })
  114. setCompletionRes('')
  115. const res: string[] = []
  116. let tempMessageId = ''
  117. if (!isPC)
  118. onShowRes()
  119. setResponsingTrue()
  120. const startTime = Date.now()
  121. let isTimeout = false
  122. const runId = setInterval(() => {
  123. if (Date.now() - startTime > 1000 * 60) { // 1min timeout
  124. clearInterval(runId)
  125. setResponsingFalse()
  126. onCompleted(getCompletionRes(), taskId, false)
  127. isTimeout = true
  128. console.log(`[#${taskId}]: timeout`)
  129. }
  130. }, 1000)
  131. sendCompletionMessage(data, {
  132. onData: (data: string, _isFirstMessage: boolean, { messageId }) => {
  133. tempMessageId = messageId
  134. res.push(data)
  135. setCompletionRes(res.join(''))
  136. },
  137. onCompleted: () => {
  138. if (isTimeout)
  139. return
  140. setResponsingFalse()
  141. setMessageId(tempMessageId)
  142. onCompleted(getCompletionRes(), taskId, true)
  143. clearInterval(runId)
  144. },
  145. onError() {
  146. if (isTimeout)
  147. return
  148. setResponsingFalse()
  149. onCompleted(getCompletionRes(), taskId, false)
  150. clearInterval(runId)
  151. },
  152. }, isInstalledApp, installedAppInfo?.id)
  153. }
  154. const [controlClearMoreLikeThis, setControlClearMoreLikeThis] = useState(0)
  155. useEffect(() => {
  156. if (controlSend) {
  157. handleSend()
  158. setControlClearMoreLikeThis(Date.now())
  159. }
  160. }, [controlSend])
  161. useEffect(() => {
  162. if (controlRetry)
  163. handleSend()
  164. }, [controlRetry])
  165. const renderTextGenerationRes = () => (
  166. <TextGenerationRes
  167. className='mt-3'
  168. isError={isError}
  169. onRetry={handleSend}
  170. content={completionRes}
  171. messageId={messageId}
  172. isInWebApp
  173. moreLikeThis={moreLikeThisEnabled}
  174. onFeedback={handleFeedback}
  175. feedback={feedback}
  176. onSave={handleSaveMessage}
  177. isMobile={isMobile}
  178. isInstalledApp={isInstalledApp}
  179. installedAppId={installedAppInfo?.id}
  180. isLoading={isCallBatchAPI ? (!completionRes && isResponsing) : false}
  181. taskId={isCallBatchAPI ? ((taskId as number) < 10 ? `0${taskId}` : `${taskId}`) : undefined}
  182. controlClearMoreLikeThis={controlClearMoreLikeThis}
  183. />
  184. )
  185. return (
  186. <div className={cn(isNoData && !isCallBatchAPI && 'h-full')}>
  187. {!isCallBatchAPI && (
  188. (isResponsing && !completionRes)
  189. ? (
  190. <div className='flex h-full w-full justify-center items-center'>
  191. <Loading type='area' />
  192. </div>)
  193. : (
  194. <>
  195. {isNoData
  196. ? <NoData />
  197. : renderTextGenerationRes()
  198. }
  199. </>
  200. )
  201. )}
  202. {isCallBatchAPI && (
  203. <div className='mt-2'>
  204. {renderTextGenerationRes()}
  205. </div>
  206. )}
  207. </div>
  208. )
  209. }
  210. export default React.memo(Result)