index.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useEffect, useState } from 'react'
  4. import { useBoolean, useGetState } 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'
  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. promptConfig: PromptConfig | null
  22. moreLikeThisEnabled: boolean
  23. inputs: Record<string, any>
  24. query: string
  25. controlSend?: number
  26. controlStopResponding?: number
  27. onShowRes: () => void
  28. handleSaveMessage: (messageId: string) => void
  29. taskId?: number
  30. onCompleted: (completionRes: string, taskId?: number, success?: boolean) => void
  31. }
  32. const Result: FC<IResultProps> = ({
  33. isCallBatchAPI,
  34. isPC,
  35. isMobile,
  36. isInstalledApp,
  37. installedAppInfo,
  38. promptConfig,
  39. moreLikeThisEnabled,
  40. inputs,
  41. query,
  42. controlSend,
  43. controlStopResponding,
  44. onShowRes,
  45. handleSaveMessage,
  46. taskId,
  47. onCompleted,
  48. }) => {
  49. const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false)
  50. useEffect(() => {
  51. if (controlStopResponding)
  52. setResponsingFalse()
  53. }, [controlStopResponding])
  54. const [completionRes, setCompletionRes, getCompletionRes] = useGetState('')
  55. const { notify } = Toast
  56. const isNoData = !completionRes
  57. const [messageId, setMessageId] = useState<string | null>(null)
  58. const [feedback, setFeedback] = useState<Feedbacktype>({
  59. rating: null,
  60. })
  61. const handleFeedback = async (feedback: Feedbacktype) => {
  62. await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, installedAppInfo?.id)
  63. setFeedback(feedback)
  64. }
  65. const logError = (message: string) => {
  66. notify({ type: 'error', message })
  67. }
  68. const checkCanSend = () => {
  69. // batch will check outer
  70. if (isCallBatchAPI)
  71. return true
  72. const prompt_variables = promptConfig?.prompt_variables
  73. if (!prompt_variables || prompt_variables?.length === 0)
  74. return true
  75. let hasEmptyInput = ''
  76. const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
  77. const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
  78. return res
  79. }) || [] // compatible with old version
  80. requiredVars.forEach(({ key, name }) => {
  81. if (hasEmptyInput)
  82. return
  83. if (!inputs[key])
  84. hasEmptyInput = name
  85. })
  86. if (hasEmptyInput) {
  87. logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
  88. return false
  89. }
  90. return !hasEmptyInput
  91. }
  92. const handleSend = async () => {
  93. if (isResponsing) {
  94. notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
  95. return false
  96. }
  97. if (!checkCanSend())
  98. return
  99. if (!query) {
  100. logError(t('appDebug.errorMessage.queryRequired'))
  101. return false
  102. }
  103. const data = {
  104. inputs,
  105. query,
  106. }
  107. setMessageId(null)
  108. setFeedback({
  109. rating: null,
  110. })
  111. setCompletionRes('')
  112. const res: string[] = []
  113. let tempMessageId = ''
  114. if (!isPC)
  115. onShowRes()
  116. setResponsingTrue()
  117. sendCompletionMessage(data, {
  118. onData: (data: string, _isFirstMessage: boolean, { messageId }: any) => {
  119. tempMessageId = messageId
  120. res.push(data)
  121. setCompletionRes(res.join(''))
  122. },
  123. onCompleted: () => {
  124. setResponsingFalse()
  125. setMessageId(tempMessageId)
  126. onCompleted(getCompletionRes(), taskId, true)
  127. },
  128. onError() {
  129. setResponsingFalse()
  130. onCompleted(getCompletionRes(), taskId, false)
  131. },
  132. }, isInstalledApp, installedAppInfo?.id)
  133. }
  134. const [controlClearMoreLikeThis, setControlClearMoreLikeThis] = useState(0)
  135. useEffect(() => {
  136. if (controlSend) {
  137. handleSend()
  138. setControlClearMoreLikeThis(Date.now())
  139. }
  140. }, [controlSend])
  141. const renderTextGenerationRes = () => (
  142. <TextGenerationRes
  143. className='mt-3'
  144. content={completionRes}
  145. messageId={messageId}
  146. isInWebApp
  147. moreLikeThis={moreLikeThisEnabled}
  148. onFeedback={handleFeedback}
  149. feedback={feedback}
  150. onSave={handleSaveMessage}
  151. isMobile={isMobile}
  152. isInstalledApp={isInstalledApp}
  153. installedAppId={installedAppInfo?.id}
  154. isLoading={isCallBatchAPI ? (!completionRes && isResponsing) : false}
  155. taskId={isCallBatchAPI ? ((taskId as number) < 10 ? `0${taskId}` : `${taskId}`) : undefined}
  156. controlClearMoreLikeThis={controlClearMoreLikeThis}
  157. />
  158. )
  159. return (
  160. <div className={cn(isNoData && !isCallBatchAPI && 'h-full')}>
  161. {!isCallBatchAPI && (
  162. (isResponsing && !completionRes)
  163. ? (
  164. <div className='flex h-full w-full justify-center items-center'>
  165. <Loading type='area' />
  166. </div>)
  167. : (
  168. <>
  169. {isNoData
  170. ? <NoData />
  171. : renderTextGenerationRes()
  172. }
  173. </>
  174. )
  175. )}
  176. {isCallBatchAPI && (
  177. <div className='mt-2'>
  178. {renderTextGenerationRes()}
  179. </div>
  180. )}
  181. </div>
  182. )
  183. }
  184. export default React.memo(Result)