Browse Source

Chore: chat log refactor (#5523)

KVOJJJin 1 year ago
parent
commit
8294e97113
97 changed files with 165 additions and 5249 deletions
  1. 1 4
      web/app/(shareLayout)/chat/[token]/page.tsx
  2. 1 3
      web/app/(shareLayout)/chatbot/[token]/page.tsx
  3. 2 5
      web/app/(shareLayout)/completion/[token]/page.tsx
  4. 2 4
      web/app/(shareLayout)/workflow/[token]/page.tsx
  5. 0 428
      web/app/components/app/chat/answer/index.tsx
  6. 0 43
      web/app/components/app/chat/icon-component/index.tsx
  7. 0 3
      web/app/components/app/chat/icons/answer.svg
  8. BIN
      web/app/components/app/chat/icons/default-avatar.jpg
  9. 0 3
      web/app/components/app/chat/icons/edit.svg
  10. 0 3
      web/app/components/app/chat/icons/question.svg
  11. 0 10
      web/app/components/app/chat/icons/robot.svg
  12. 0 3
      web/app/components/app/chat/icons/send-active.svg
  13. 0 3
      web/app/components/app/chat/icons/send.svg
  14. 0 19
      web/app/components/app/chat/icons/typing.svg
  15. 0 10
      web/app/components/app/chat/icons/user.svg
  16. 0 455
      web/app/components/app/chat/index.tsx
  17. 0 23
      web/app/components/app/chat/more-info/index.tsx
  18. 0 14
      web/app/components/app/chat/operation/index.tsx
  19. 0 52
      web/app/components/app/chat/question/index.tsx
  20. 0 136
      web/app/components/app/chat/style.module.css
  21. 0 7
      web/app/components/app/chat/thought/style.module.css
  22. 2 2
      web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx
  23. 2 2
      web/app/components/app/configuration/debug/debug-with-single-model/index.tsx
  24. 112 45
      web/app/components/app/log/list.tsx
  25. 1 1
      web/app/components/app/overview/embedded/index.tsx
  26. 1 1
      web/app/components/app/store.ts
  27. 1 1
      web/app/components/app/text-generate/item/index.tsx
  28. 1 1
      web/app/components/base/agent-log-modal/detail.tsx
  29. 1 1
      web/app/components/base/agent-log-modal/index.tsx
  30. 2 2
      web/app/components/base/chat/chat-with-history/config-panel/index.tsx
  31. 1 1
      web/app/components/base/chat/chat-with-history/sidebar/index.tsx
  32. 0 0
      web/app/components/base/chat/chat-with-history/sidebar/rename-modal.tsx
  33. 1 1
      web/app/components/base/chat/chat/answer/agent-content.tsx
  34. 2 2
      web/app/components/base/chat/chat/answer/index.tsx
  35. 2 2
      web/app/components/base/chat/chat/answer/operation.tsx
  36. 0 0
      web/app/components/base/chat/chat/citation/index.tsx
  37. 0 0
      web/app/components/base/chat/chat/citation/popup.tsx
  38. 0 0
      web/app/components/base/chat/chat/citation/progress-tooltip.tsx
  39. 0 0
      web/app/components/base/chat/chat/citation/tooltip.tsx
  40. 3 0
      web/app/components/base/chat/chat/index.tsx
  41. 0 0
      web/app/components/base/chat/chat/loading-anim/index.tsx
  42. 0 0
      web/app/components/base/chat/chat/loading-anim/style.module.css
  43. 1 1
      web/app/components/app/chat/log/index.tsx
  44. 1 1
      web/app/components/app/chat/thought/index.tsx
  45. 0 0
      web/app/components/base/chat/chat/thought/panel.tsx
  46. 0 0
      web/app/components/base/chat/chat/thought/tool.tsx
  47. 1 1
      web/app/components/app/chat/type.ts
  48. 2 2
      web/app/components/base/chat/embedded-chatbot/config-panel/index.tsx
  49. 2 7
      web/app/components/base/chat/embedded-chatbot/header.tsx
  50. 1 1
      web/app/components/base/chat/types.ts
  51. 0 0
      web/app/components/base/copy-btn/index.tsx
  52. 0 0
      web/app/components/base/copy-btn/style.module.css
  53. 3 3
      web/app/components/base/markdown.tsx
  54. 0 0
      web/app/components/base/mermaid/index.tsx
  55. 1 1
      web/app/components/base/message-log-modal/index.tsx
  56. 1 1
      web/app/components/base/prompt-log-modal/index.tsx
  57. 0 0
      web/app/components/base/svg/index.tsx
  58. 0 0
      web/app/components/base/svg/style.module.css
  59. 0 13
      web/app/components/share/chat/config-scence/index.tsx
  60. 0 73
      web/app/components/share/chat/hooks/use-conversation.ts
  61. 0 953
      web/app/components/share/chat/index.tsx
  62. 0 28
      web/app/components/share/chat/sidebar/app-info/index.tsx
  63. 0 3
      web/app/components/share/chat/sidebar/card.module.css
  64. 0 19
      web/app/components/share/chat/sidebar/card.tsx
  65. 0 167
      web/app/components/share/chat/sidebar/index.tsx
  66. 0 143
      web/app/components/share/chat/sidebar/list/index.tsx
  67. 0 77
      web/app/components/share/chat/sidebar/list/item.tsx
  68. 0 77
      web/app/components/share/chat/value-panel/index.tsx
  69. 0 3
      web/app/components/share/chat/value-panel/style.module.css
  70. 0 388
      web/app/components/share/chat/welcome/index.tsx
  71. 0 74
      web/app/components/share/chat/welcome/massive-component.tsx
  72. 0 22
      web/app/components/share/chat/welcome/style.module.css
  73. 0 13
      web/app/components/share/chatbot/config-scence/index.tsx
  74. 0 72
      web/app/components/share/chatbot/hooks/use-conversation.ts
  75. 0 824
      web/app/components/share/chatbot/index.tsx
  76. 0 28
      web/app/components/share/chatbot/sidebar/app-info/index.tsx
  77. 0 3
      web/app/components/share/chatbot/sidebar/card.module.css
  78. 0 19
      web/app/components/share/chatbot/sidebar/card.tsx
  79. 0 152
      web/app/components/share/chatbot/sidebar/index.tsx
  80. 0 115
      web/app/components/share/chatbot/sidebar/list/index.tsx
  81. 0 7
      web/app/components/share/chatbot/sidebar/list/style.module.css
  82. 0 77
      web/app/components/share/chatbot/value-panel/index.tsx
  83. 0 3
      web/app/components/share/chatbot/value-panel/style.module.css
  84. 0 389
      web/app/components/share/chatbot/welcome/index.tsx
  85. 0 75
      web/app/components/share/chatbot/welcome/massive-component.tsx
  86. 0 22
      web/app/components/share/chatbot/welcome/style.module.css
  87. 0 88
      web/app/components/share/header.tsx
  88. 1 1
      web/app/components/share/text-generation/result/content.tsx
  89. 1 1
      web/app/components/share/text-generation/result/header.tsx
  90. 1 1
      web/app/components/share/text-generation/result/index.tsx
  91. 1 1
      web/app/components/tools/utils/index.ts
  92. 5 5
      web/app/components/workflow/panel/chat-record/index.tsx
  93. 1 1
      web/app/components/workflow/run/output-panel.tsx
  94. 1 1
      web/app/components/workflow/run/result-text.tsx
  95. 1 1
      web/service/base.ts
  96. 1 1
      web/service/debug.ts
  97. 1 1
      web/service/share.ts

+ 1 - 4
web/app/(shareLayout)/chat/[token]/page.tsx

@@ -1,11 +1,8 @@
 'use client'
-import type { FC } from 'react'
 import React from 'react'
-
-import type { IMainProps } from '@/app/components/share/chat'
 import ChatWithHistoryWrap from '@/app/components/base/chat/chat-with-history'
 
-const Chat: FC<IMainProps> = () => {
+const Chat = () => {
   return (
     <ChatWithHistoryWrap />
   )

+ 1 - 3
web/app/(shareLayout)/chatbot/[token]/page.tsx

@@ -1,14 +1,12 @@
 'use client'
-import type { FC } from 'react'
 import React, { useEffect } from 'react'
 import cn from 'classnames'
-import type { IMainProps } from '@/app/components/share/chat'
 import EmbeddedChatbot from '@/app/components/base/chat/embedded-chatbot'
 import Loading from '@/app/components/base/loading'
 import { fetchSystemFeatures } from '@/service/share'
 import LogoSite from '@/app/components/base/logo/logo-site'
 
-const Chatbot: FC<IMainProps> = () => {
+const Chatbot = () => {
   const [isSSOEnforced, setIsSSOEnforced] = React.useState(true)
   const [loading, setLoading] = React.useState(true)
 

+ 2 - 5
web/app/(shareLayout)/completion/[token]/page.tsx

@@ -1,13 +1,10 @@
-import type { FC } from 'react'
 import React from 'react'
-
-import type { IMainProps } from '@/app/components/share/chat'
 import Main from '@/app/components/share/text-generation'
 
-const TextGeneration: FC<IMainProps> = () => {
+const Completion = () => {
   return (
     <Main />
   )
 }
 
-export default React.memo(TextGeneration)
+export default React.memo(Completion)

+ 2 - 4
web/app/(shareLayout)/workflow/[token]/page.tsx

@@ -1,13 +1,11 @@
-import type { FC } from 'react'
 import React from 'react'
 
-import type { IMainProps } from '@/app/components/share/text-generation'
 import Main from '@/app/components/share/text-generation'
 
-const TextGeneration: FC<IMainProps> = () => {
+const Workflow = () => {
   return (
     <Main isWorkflow />
   )
 }
 
-export default React.memo(TextGeneration)
+export default React.memo(Workflow)

+ 0 - 428
web/app/components/app/chat/answer/index.tsx

@@ -1,428 +0,0 @@
-'use client'
-import type { FC, ReactNode } from 'react'
-import React, { useEffect, useMemo, useRef, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { UserCircleIcon } from '@heroicons/react/24/solid'
-import cn from 'classnames'
-import type { CitationItem, DisplayScene, FeedbackFunc, Feedbacktype, IChatItem } from '../type'
-import OperationBtn from '../operation'
-import LoadingAnim from '../loading-anim'
-import { RatingIcon } from '../icon-component'
-import s from '../style.module.css'
-import MoreInfo from '../more-info'
-import CopyBtn from '../copy-btn'
-import Thought from '../thought'
-import Citation from '../citation'
-import AudioBtn from '@/app/components/base/audio-btn'
-import { randomString } from '@/utils'
-import type { MessageRating } from '@/models/log'
-import Tooltip from '@/app/components/base/tooltip'
-import { Markdown } from '@/app/components/base/markdown'
-import type { DataSet } from '@/models/datasets'
-import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn'
-import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal'
-import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
-import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
-import type { Emoji } from '@/app/components/tools/types'
-import type { VisionFile } from '@/types/app'
-import ImageGallery from '@/app/components/base/image-gallery'
-import Log from '@/app/components/app/chat/log'
-
-const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => {
-  return <div className={'rounded-lg h-6 w-6 flex items-center justify-center hover:bg-gray-100'}>
-    {children}
-  </div>
-}
-export type IAnswerProps = {
-  item: IChatItem
-  index: number
-  feedbackDisabled: boolean
-  isHideFeedbackEdit: boolean
-  onQueryChange: (query: string) => void
-  onFeedback?: FeedbackFunc
-  displayScene: DisplayScene
-  isResponding?: boolean
-  answerIcon?: ReactNode
-  citation?: CitationItem[]
-  dataSets?: DataSet[]
-  isShowCitation?: boolean
-  isShowCitationHitInfo?: boolean
-  isShowTextToSpeech?: boolean
-  // Annotation props
-  supportAnnotation?: boolean
-  appId?: string
-  question: string
-  onAnnotationEdited?: (question: string, answer: string, index: number) => void
-  onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string, index: number) => void
-  onAnnotationRemoved?: (index: number) => void
-  allToolIcons?: Record<string, string | Emoji>
-  isShowPromptLog?: boolean
-}
-// The component needs to maintain its own state to control whether to display input component
-const Answer: FC<IAnswerProps> = ({
-  item,
-  index,
-  onQueryChange,
-  feedbackDisabled = false,
-  isHideFeedbackEdit = false,
-  onFeedback,
-  displayScene = 'web',
-  isResponding,
-  answerIcon,
-  citation,
-  isShowCitation,
-  isShowCitationHitInfo = false,
-  isShowTextToSpeech,
-  supportAnnotation,
-  appId,
-  question,
-  onAnnotationEdited,
-  onAnnotationAdded,
-  onAnnotationRemoved,
-  allToolIcons,
-  isShowPromptLog,
-}) => {
-  const { id, content, more, feedback, adminFeedback, annotation, agent_thoughts } = item
-  const isAgentMode = !!agent_thoughts && agent_thoughts.length > 0
-  const hasAnnotation = useMemo(() => !!annotation, [annotation])
-  // const [annotation, setAnnotation] = useState<Annotation | undefined | null>(initAnnotation)
-  // const [inputValue, setInputValue] = useState<string>(initAnnotation?.content ?? '')
-  const [localAdminFeedback, setLocalAdminFeedback] = useState<Feedbacktype | undefined | null>(adminFeedback)
-  // const { userProfile } = useContext(AppContext)
-  const { t } = useTranslation()
-
-  const [isShowReplyModal, setIsShowReplyModal] = useState(false)
-
-  /**
- * Render feedback results (distinguish between users and administrators)
- * User reviews cannot be cancelled in Console
- * @param rating feedback result
- * @param isUserFeedback Whether it is user's feedback
- * @param isWebScene Whether it is web scene
- * @returns comp
- */
-  const renderFeedbackRating = (rating: MessageRating | undefined, isUserFeedback = true, isWebScene = true) => {
-    if (!rating)
-      return null
-
-    const isLike = rating === 'like'
-    const ratingIconClassname = isLike ? 'text-primary-600 bg-primary-100 hover:bg-primary-200' : 'text-red-600 bg-red-100 hover:bg-red-200'
-    const UserSymbol = <UserCircleIcon className='absolute top-[-2px] left-[18px] w-3 h-3 rounded-lg text-gray-400 bg-white' />
-    // The tooltip is always displayed, but the content is different for different scenarios.
-    return (
-      <Tooltip
-        selector={`user-feedback-${randomString(16)}`}
-        content={((isWebScene || (!isUserFeedback && !isWebScene)) ? isLike ? t('appDebug.operation.cancelAgree') : t('appDebug.operation.cancelDisagree') : (!isWebScene && isUserFeedback) ? `${t('appDebug.operation.userAction')}${isLike ? t('appDebug.operation.agree') : t('appDebug.operation.disagree')}` : '') as string}
-      >
-        <div
-          className={`relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800 ${(!isWebScene && isUserFeedback) ? '!cursor-default' : ''}`}
-          style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
-          {...((isWebScene || (!isUserFeedback && !isWebScene))
-            ? {
-              onClick: async () => {
-                const res = await onFeedback?.(id, { rating: null })
-                if (res && !isWebScene)
-                  setLocalAdminFeedback({ rating: null })
-              },
-            }
-            : {})}
-        >
-          <div className={`${ratingIconClassname} rounded-lg h-6 w-6 flex items-center justify-center`}>
-            <RatingIcon isLike={isLike} />
-          </div>
-          {!isWebScene && isUserFeedback && UserSymbol}
-        </div>
-      </Tooltip>
-    )
-  }
-
-  /**
-   * Different scenarios have different operation items.
-   * @param isWebScene  Whether it is web scene
-   * @returns comp
-   */
-  const renderItemOperation = (isWebScene = true) => {
-    const userOperation = () => {
-      return feedback?.rating
-        ? null
-        : <div className='flex gap-1'>
-          <Tooltip selector={`user-feedback-${randomString(16)}`} content={t('appLog.detail.operation.like') as string}>
-            {OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={true} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'like' }) })}
-          </Tooltip>
-          <Tooltip selector={`user-feedback-${randomString(16)}`} content={t('appLog.detail.operation.dislike') as string}>
-            {OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={false} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'dislike' }) })}
-          </Tooltip>
-        </div>
-    }
-
-    const adminOperation = () => {
-      return <div className='flex gap-1'>
-        {!localAdminFeedback?.rating && <>
-          <Tooltip selector={`user-feedback-${randomString(16)}`} content={t('appLog.detail.operation.like') as string}>
-            {OperationBtn({
-              innerContent: <IconWrapper><RatingIcon isLike={true} /></IconWrapper>,
-              onClick: async () => {
-                const res = await onFeedback?.(id, { rating: 'like' })
-                if (res)
-                  setLocalAdminFeedback({ rating: 'like' })
-              },
-            })}
-          </Tooltip>
-          <Tooltip selector={`user-feedback-${randomString(16)}`} content={t('appLog.detail.operation.dislike') as string}>
-            {OperationBtn({
-              innerContent: <IconWrapper><RatingIcon isLike={false} /></IconWrapper>,
-              onClick: async () => {
-                const res = await onFeedback?.(id, { rating: 'dislike' })
-                if (res)
-                  setLocalAdminFeedback({ rating: 'dislike' })
-              },
-            })}
-          </Tooltip>
-        </>}
-      </div>
-    }
-
-    return (
-      <div className={`${s.itemOperation} flex gap-2`}>
-        {isWebScene ? userOperation() : adminOperation()}
-      </div>
-    )
-  }
-
-  const getImgs = (list?: VisionFile[]) => {
-    if (!list)
-      return []
-    return list.filter(file => file.type === 'image' && file.belongs_to === 'assistant')
-  }
-
-  const agentModeAnswer = (
-    <div>
-      {agent_thoughts?.map((item, index) => (
-        <div key={index}>
-          {item.thought && (
-            <Markdown content={item.thought} />
-          )}
-          {/* {item.tool} */}
-          {/* perhaps not use tool */}
-          {!!item.tool && (
-            <Thought
-              thought={item}
-              allToolIcons={allToolIcons || {}}
-              isFinished={!!item.observation || !isResponding}
-            />
-          )}
-
-          {getImgs(item.message_files).length > 0 && (
-            <ImageGallery srcs={getImgs(item.message_files).map(item => item.url)} />
-          )}
-        </div>
-      ))}
-    </div>
-  )
-
-  const [containerWidth, setContainerWidth] = useState(0)
-  const [contentWidth, setContentWidth] = useState(0)
-  const containerRef = useRef<HTMLDivElement>(null)
-  const contentRef = useRef<HTMLDivElement>(null)
-
-  const getContainerWidth = () => {
-    if (containerRef.current)
-      setContainerWidth(containerRef.current?.clientWidth + 24)
-  }
-  const getContentWidth = () => {
-    if (contentRef.current)
-      setContentWidth(contentRef.current?.clientWidth)
-  }
-
-  useEffect(() => {
-    getContainerWidth()
-  }, [])
-
-  useEffect(() => {
-    if (!isResponding)
-      getContentWidth()
-  }, [isResponding])
-
-  const operationWidth = useMemo(() => {
-    let width = 0
-    if (!item.isOpeningStatement)
-      width += 28
-    if (!item.isOpeningStatement && isShowPromptLog)
-      width += 102 + 8
-    if (!item.isOpeningStatement && isShowTextToSpeech)
-      width += 33
-    if (!item.isOpeningStatement && supportAnnotation)
-      width += 96 + 8
-    if (!feedbackDisabled && !item.feedbackDisabled)
-      width += 60 + 8
-    if (!feedbackDisabled && localAdminFeedback?.rating && !item.isOpeningStatement)
-      width += 60 + 8
-    if (!feedbackDisabled && feedback?.rating && !item.isOpeningStatement)
-      width += 28 + 8
-    return width
-  }, [item.isOpeningStatement, item.feedbackDisabled, isShowPromptLog, isShowTextToSpeech, supportAnnotation, feedbackDisabled, localAdminFeedback?.rating, feedback?.rating])
-
-  const positionRight = useMemo(() => operationWidth < containerWidth - contentWidth - 4, [operationWidth, containerWidth, contentWidth])
-
-  return (
-    // data-id for debug the item message is right
-    <div key={id} data-id={id}>
-      <div className='flex items-start'>
-        {
-          answerIcon || (
-            <div className={`${s.answerIcon} w-10 h-10 shrink-0`}>
-              {isResponding
-                && <div className={s.typeingIcon}>
-                  <LoadingAnim type='avatar' />
-                </div>
-              }
-            </div>
-          )
-        }
-        <div ref={containerRef} className={cn(s.answerWrapWrap, 'chat-answer-container')}>
-          <div className={cn(s.answerWrap, 'group')}>
-            <div ref={contentRef} className={`${s.answer} relative text-sm text-gray-900`}>
-              <div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}>
-                {(isResponding && (isAgentMode ? (!content && (agent_thoughts || []).filter(item => !!item.thought || !!item.tool).length === 0) : !content))
-                  ? (
-                    <div className='flex items-center justify-center w-6 h-5'>
-                      <LoadingAnim type='text' />
-                    </div>
-                  )
-                  : (
-                    <div>
-                      {annotation?.logAnnotation && (
-                        <div className='mb-1'>
-                          <div className='mb-3'>
-                            {isAgentMode
-                              ? (<div className='line-through !text-gray-400'>{agentModeAnswer}</div>)
-                              : (
-                                <Markdown className='line-through !text-gray-400' content={content} />
-                              )}
-                          </div>
-                          <EditTitle title={t('appAnnotation.editBy', {
-                            author: annotation?.logAnnotation.account?.name,
-                          })} />
-                        </div>
-                      )}
-                      <div>
-                        {annotation?.logAnnotation
-                          ? (
-                            <Markdown content={annotation?.logAnnotation.content || ''} />
-                          )
-                          : (isAgentMode
-                            ? agentModeAnswer
-                            : (
-                              <Markdown content={content} />
-                            ))}
-                      </div>
-                      {(hasAnnotation && !annotation?.logAnnotation) && (
-                        <EditTitle className='mt-1' title={t('appAnnotation.editBy', {
-                          author: annotation?.authorName,
-                        })} />
-                      )}
-                      {item.isOpeningStatement && item.suggestedQuestions && item.suggestedQuestions.filter(q => !!q && q.trim()).length > 0 && (
-                        <div className='flex flex-wrap'>
-                          {item.suggestedQuestions.filter(q => !!q && q.trim()).map((question, index) => (
-                            <div
-                              key={index}
-                              className='mt-1 mr-1 max-w-full last:mr-0 shrink-0 py-[5px] leading-[18px] items-center px-4 rounded-lg border border-gray-200 shadow-xs bg-white text-xs font-medium text-primary-600 cursor-pointer'
-                              onClick={() => onQueryChange(question)}
-                            >
-                              {question}
-                            </div>),
-                          )}
-                        </div>
-                      )}
-                    </div>
-                  )}
-                {
-                  !!citation?.length && isShowCitation && !isResponding && (
-                    <Citation data={citation} showHitInfo={isShowCitationHitInfo} />
-                  )
-                }
-              </div>
-              {hasAnnotation && (
-                <div
-                  className={cn(s.hasAnnotationBtn, 'absolute -top-3.5 -right-3.5 box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-[#444CE7]')}
-                  style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
-                >
-                  <div className='p-1 rounded-lg bg-[#EEF4FF] '>
-                    <MessageFast className='w-4 h-4' />
-                  </div>
-                </div>
-              )}
-              <div
-                className={cn(
-                  'absolute -top-3.5 flex justify-end gap-1',
-                  positionRight ? '!top-[9px]' : '-right-3.5',
-                )}
-                style={positionRight ? { left: contentWidth + 8 } : {}}
-              >
-                {!item.isOpeningStatement && (
-                  <CopyBtn
-                    value={content}
-                    className={cn(s.copyBtn, 'mr-1')}
-                  />
-                )}
-                {((isShowPromptLog && !isResponding) || (!item.isOpeningStatement && isShowTextToSpeech)) && (
-                  <div className='hidden group-hover:flex items-center w-max h-[28px] p-0.5 rounded-lg bg-white border-[0.5px] border-gray-100 shadow-md shrink-0'>
-                    {isShowPromptLog && !isResponding && (
-                      <Log logItem={item} />
-                    )}
-                    {!item.isOpeningStatement && isShowTextToSpeech && (
-                      <>
-                        <div className='mx-1 w-[1px] h-[14px] bg-gray-200'/>
-                        <AudioBtn
-                          value={content}
-                          noCache={false}
-                          className={cn(s.playBtn)}
-                        />
-                      </>
-                    )}
-                  </div>
-                )}
-                {(!item.isOpeningStatement && supportAnnotation) && (
-                  <AnnotationCtrlBtn
-                    appId={appId!}
-                    messageId={id}
-                    annotationId={annotation?.id || ''}
-                    className={cn(s.annotationBtn, 'ml-1 shrink-0')}
-                    cached={hasAnnotation}
-                    query={question}
-                    answer={content}
-                    onAdded={(id, authorName) => onAnnotationAdded?.(id, authorName, question, content, index)}
-                    onEdit={() => setIsShowReplyModal(true)}
-                    onRemoved={() => onAnnotationRemoved!(index)}
-                  />
-                )}
-
-                <EditReplyModal
-                  isShow={isShowReplyModal}
-                  onHide={() => setIsShowReplyModal(false)}
-                  query={question}
-                  answer={content}
-                  onEdited={(editedQuery, editedAnswer) => onAnnotationEdited!(editedQuery, editedAnswer, index)}
-                  onAdded={(annotationId, authorName, editedQuery, editedAnswer) => onAnnotationAdded!(annotationId, authorName, editedQuery, editedAnswer, index)}
-                  appId={appId!}
-                  messageId={id}
-                  annotationId={annotation?.id || ''}
-                  createdAt={annotation?.created_at}
-                  onRemove={() => { }}
-                />
-
-                {!feedbackDisabled && !item.feedbackDisabled && renderItemOperation(displayScene !== 'console')}
-                {/* Admin feedback is displayed only in the background. */}
-                {!feedbackDisabled && renderFeedbackRating(localAdminFeedback?.rating, false, false)}
-                {/* User feedback must be displayed */}
-                {!feedbackDisabled && renderFeedbackRating(feedback?.rating, !isHideFeedbackEdit, displayScene !== 'console')}
-              </div>
-            </div>
-            {more && <MoreInfo className='invisible group-hover:visible' more={more} isQuestion={false} />}
-          </div>
-        </div>
-      </div>
-    </div>
-  )
-}
-export default React.memo(Answer)

File diff suppressed because it is too large
+ 0 - 43
web/app/components/app/chat/icon-component/index.tsx


+ 0 - 3
web/app/components/app/chat/icons/answer.svg

@@ -1,3 +0,0 @@
-<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M1.03647 1.5547C0.59343 0.890144 1.06982 0 1.86852 0H8V12L1.03647 1.5547Z" fill="#F3F4F6"/>
-</svg>

BIN
web/app/components/app/chat/icons/default-avatar.jpg


File diff suppressed because it is too large
+ 0 - 3
web/app/components/app/chat/icons/edit.svg


+ 0 - 3
web/app/components/app/chat/icons/question.svg

@@ -1,3 +0,0 @@
-<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z" fill="#E1EFFE"/>
-</svg>

File diff suppressed because it is too large
+ 0 - 10
web/app/components/app/chat/icons/robot.svg


File diff suppressed because it is too large
+ 0 - 3
web/app/components/app/chat/icons/send-active.svg


File diff suppressed because it is too large
+ 0 - 3
web/app/components/app/chat/icons/send.svg


+ 0 - 19
web/app/components/app/chat/icons/typing.svg

@@ -1,19 +0,0 @@
-<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g filter="url(#filter0_d_2358_1380)">
-<rect x="2" y="1" width="16" height="16" rx="8" fill="white"/>
-<path opacity="0.7" d="M13.5 9H13.505M14 9C14 9.13261 13.9473 9.25979 13.8536 9.35355C13.7598 9.44732 13.6326 9.5 13.5 9.5C13.3674 9.5 13.2402 9.44732 13.1464 9.35355C13.0527 9.25979 13 9.13261 13 9C13 8.86739 13.0527 8.74021 13.1464 8.64645C13.2402 8.55268 13.3674 8.5 13.5 8.5C13.6326 8.5 13.7598 8.55268 13.8536 8.64645C13.9473 8.74021 14 8.86739 14 9Z" stroke="#155EEF" stroke-linecap="round" stroke-linejoin="round"/>
-<path opacity="0.6" d="M10 9H10.005M10.5 9C10.5 9.13261 10.4473 9.25979 10.3536 9.35355C10.2598 9.44732 10.1326 9.5 10 9.5C9.86739 9.5 9.74021 9.44732 9.64645 9.35355C9.55268 9.25979 9.5 9.13261 9.5 9C9.5 8.86739 9.55268 8.74021 9.64645 8.64645C9.74021 8.55268 9.86739 8.5 10 8.5C10.1326 8.5 10.2598 8.55268 10.3536 8.64645C10.4473 8.74021 10.5 8.86739 10.5 9Z" stroke="#155EEF" stroke-linecap="round" stroke-linejoin="round"/>
-<path opacity="0.3" d="M6.5 9H6.505M7 9C7 9.13261 6.94732 9.25979 6.85355 9.35355C6.75979 9.44732 6.63261 9.5 6.5 9.5C6.36739 9.5 6.24021 9.44732 6.14645 9.35355C6.05268 9.25979 6 9.13261 6 9C6 8.86739 6.05268 8.74021 6.14645 8.64645C6.24021 8.55268 6.36739 8.5 6.5 8.5C6.63261 8.5 6.75979 8.55268 6.85355 8.64645C6.94732 8.74021 7 8.86739 7 9Z" stroke="#155EEF" stroke-linecap="round" stroke-linejoin="round"/>
-</g>
-<defs>
-<filter id="filter0_d_2358_1380" x="0" y="0" width="20" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dy="1"/>
-<feGaussianBlur stdDeviation="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
-<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2358_1380"/>
-<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2358_1380" result="shape"/>
-</filter>
-</defs>
-</svg>

File diff suppressed because it is too large
+ 0 - 10
web/app/components/app/chat/icons/user.svg


+ 0 - 455
web/app/components/app/chat/index.tsx

@@ -1,455 +0,0 @@
-'use client'
-import type { FC, ReactNode } from 'react'
-import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
-import Textarea from 'rc-textarea'
-import { useContext } from 'use-context-selector'
-import cn from 'classnames'
-import Recorder from 'js-audio-recorder'
-import { useTranslation } from 'react-i18next'
-import s from './style.module.css'
-import type { DisplayScene, FeedbackFunc, IChatItem } from './type'
-import { TryToAskIcon, stopIcon } from './icon-component'
-import Answer from './answer'
-import Question from './question'
-import TooltipPlus from '@/app/components/base/tooltip-plus'
-import { ToastContext } from '@/app/components/base/toast'
-import Button from '@/app/components/base/button'
-import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
-import VoiceInput from '@/app/components/base/voice-input'
-import { Microphone01 } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
-import { Microphone01 as Microphone01Solid } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
-import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
-import type { DataSet } from '@/models/datasets'
-import ChatImageUploader from '@/app/components/base/image-uploader/chat-image-uploader'
-import ImageList from '@/app/components/base/image-uploader/image-list'
-import { TransferMethod, type VisionFile, type VisionSettings } from '@/types/app'
-import { useClipboardUploader, useDraggableUploader, useImageFiles } from '@/app/components/base/image-uploader/hooks'
-import type { Annotation } from '@/models/log'
-import type { Emoji } from '@/app/components/tools/types'
-
-export type IChatProps = {
-  appId?: string
-  configElem?: React.ReactNode
-  chatList: IChatItem[]
-  onChatListChange?: (chatList: IChatItem[]) => void
-  controlChatUpdateAllConversation?: number
-  /**
-   * Whether to display the editing area and rating status
-   */
-  feedbackDisabled?: boolean
-  /**
-   * Whether to display the input area
-   */
-  isHideFeedbackEdit?: boolean
-  isHideSendInput?: boolean
-  onFeedback?: FeedbackFunc
-  checkCanSend?: () => boolean
-  query?: string
-  onQueryChange?: (query: string) => void
-  onSend?: (message: string, files: VisionFile[]) => void
-  displayScene?: DisplayScene
-  useCurrentUserAvatar?: boolean
-  isResponding?: boolean
-  canStopResponding?: boolean
-  abortResponding?: () => void
-  controlClearQuery?: number
-  controlFocus?: number
-  isShowSuggestion?: boolean
-  suggestionList?: string[]
-  isShowSpeechToText?: boolean
-  isShowTextToSpeech?: boolean
-  isShowCitation?: boolean
-  answerIcon?: ReactNode
-  isShowConfigElem?: boolean
-  dataSets?: DataSet[]
-  isShowCitationHitInfo?: boolean
-  isShowPromptLog?: boolean
-  visionConfig?: VisionSettings
-  supportAnnotation?: boolean
-  allToolIcons?: Record<string, string | Emoji>
-  customDisclaimer?: string
-}
-
-const Chat: FC<IChatProps> = ({
-  configElem,
-  chatList,
-  query = '',
-  onQueryChange = () => { },
-  feedbackDisabled = false,
-  isHideFeedbackEdit = false,
-  isHideSendInput = false,
-  onFeedback,
-  checkCanSend,
-  onSend = () => { },
-  displayScene,
-  useCurrentUserAvatar,
-  isResponding,
-  canStopResponding,
-  abortResponding,
-  controlClearQuery,
-  controlFocus,
-  isShowSuggestion,
-  suggestionList,
-  isShowSpeechToText,
-  isShowTextToSpeech,
-  isShowCitation,
-  answerIcon,
-  isShowConfigElem,
-  dataSets,
-  isShowCitationHitInfo,
-  isShowPromptLog,
-  visionConfig,
-  appId,
-  supportAnnotation,
-  onChatListChange,
-  allToolIcons,
-  customDisclaimer,
-}) => {
-  const { t } = useTranslation()
-  const { notify } = useContext(ToastContext)
-  const {
-    files,
-    onUpload,
-    onRemove,
-    onReUpload,
-    onImageLinkLoadError,
-    onImageLinkLoadSuccess,
-    onClear,
-  } = useImageFiles()
-  const { onPaste } = useClipboardUploader({ onUpload, visionConfig, files })
-  const { onDragEnter, onDragLeave, onDragOver, onDrop, isDragActive } = useDraggableUploader<HTMLTextAreaElement>({ onUpload, files, visionConfig })
-  const isUseInputMethod = useRef(false)
-
-  const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
-    const value = e.target.value
-    onQueryChange(value)
-  }
-
-  const logError = (message: string) => {
-    notify({ type: 'error', message, duration: 3000 })
-  }
-
-  const valid = (q?: string) => {
-    const sendQuery = q || query
-    if (!sendQuery || sendQuery.trim() === '') {
-      logError('Message cannot be empty')
-      return false
-    }
-    return true
-  }
-
-  useEffect(() => {
-    if (controlClearQuery)
-      onQueryChange('')
-  }, [controlClearQuery])
-
-  const handleSend = (q?: string) => {
-    if (!valid(q) || (checkCanSend && !checkCanSend()))
-      return
-    onSend(q || query, files.filter(file => file.progress !== -1).map(fileItem => ({
-      type: 'image',
-      transfer_method: fileItem.type,
-      url: fileItem.url,
-      upload_file_id: fileItem.fileId,
-    })))
-    if (!files.find(item => item.type === TransferMethod.local_file && !item.fileId)) {
-      if (files.length)
-        onClear()
-      if (!isResponding)
-        onQueryChange('')
-    }
-  }
-
-  const handleKeyUp = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
-    if (e.code === 'Enter') {
-      e.preventDefault()
-      // prevent send message when using input method enter
-      if (!e.shiftKey && !isUseInputMethod.current)
-        handleSend()
-    }
-  }
-
-  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
-    isUseInputMethod.current = e.nativeEvent.isComposing
-    if (e.code === 'Enter' && !e.shiftKey) {
-      onQueryChange(query.replace(/\n$/, ''))
-      e.preventDefault()
-    }
-  }
-
-  const media = useBreakpoints()
-  const isMobile = media === MediaType.mobile
-  const sendBtn = <div className={cn(!(!query || query.trim() === '') && s.sendBtnActive, `${s.sendBtn} w-8 h-8 cursor-pointer rounded-md`)} onClick={() => handleSend()}></div>
-
-  const suggestionListRef = useRef<HTMLDivElement>(null)
-  const [hasScrollbar, setHasScrollbar] = useState(false)
-  useLayoutEffect(() => {
-    if (suggestionListRef.current) {
-      const listDom = suggestionListRef.current
-      const hasScrollbar = listDom.scrollWidth > listDom.clientWidth
-      setHasScrollbar(hasScrollbar)
-    }
-  }, [suggestionList])
-
-  const [voiceInputShow, setVoiceInputShow] = useState(false)
-  const handleVoiceInputShow = () => {
-    (Recorder as any).getPermission().then(() => {
-      setVoiceInputShow(true)
-    }, () => {
-      logError(t('common.voiceInput.notAllow'))
-    })
-  }
-  const handleQueryChangeFromAnswer = useCallback((val: string) => {
-    onQueryChange(val)
-    handleSend(val)
-  }, [])
-  const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => {
-    onChatListChange?.(chatList.map((item, i) => {
-      if (i === index - 1) {
-        return {
-          ...item,
-          content: query,
-        }
-      }
-      if (i === index) {
-        return {
-          ...item,
-          annotation: {
-            ...item.annotation,
-            logAnnotation: {
-              ...item.annotation?.logAnnotation,
-              content: answer,
-            },
-          } as any,
-        }
-      }
-      return item
-    }))
-  }, [chatList])
-  const handleAnnotationAdded = useCallback((annotationId: string, authorName: string, query: string, answer: string, index: number) => {
-    onChatListChange?.(chatList.map((item, i) => {
-      if (i === index - 1) {
-        return {
-          ...item,
-          content: query,
-        }
-      }
-      if (i === index) {
-        const answerItem = {
-          ...item,
-          content: item.content,
-          annotation: {
-            id: annotationId,
-            authorName,
-            logAnnotation: {
-              content: answer,
-              account: {
-                id: '',
-                name: authorName,
-                email: '',
-              },
-            },
-          } as Annotation,
-        }
-        return answerItem
-      }
-      return item
-    }))
-  }, [chatList])
-  const handleAnnotationRemoved = useCallback((index: number) => {
-    onChatListChange?.(chatList.map((item, i) => {
-      if (i === index) {
-        return {
-          ...item,
-          content: item.content,
-          annotation: undefined,
-        }
-      }
-      return item
-    }))
-  }, [chatList])
-
-  return (
-    <div className={cn('px-3.5', 'h-full')}>
-      {isShowConfigElem && (configElem || null)}
-      {/* Chat List */}
-      <div className={cn((isShowConfigElem && configElem) ? 'h-0' : 'h-full', 'space-y-[30px]')}>
-        {chatList.map((item, index) => {
-          if (item.isAnswer) {
-            const isLast = item.id === chatList[chatList.length - 1].id
-            const citation = item.citation
-            return <Answer
-              key={item.id}
-              item={item}
-              index={index}
-              onQueryChange={handleQueryChangeFromAnswer}
-              feedbackDisabled={feedbackDisabled}
-              isHideFeedbackEdit={isHideFeedbackEdit}
-              onFeedback={onFeedback}
-              displayScene={displayScene ?? 'web'}
-              isResponding={isResponding && isLast}
-              answerIcon={answerIcon}
-              citation={citation}
-              dataSets={dataSets}
-              isShowCitation={isShowCitation}
-              isShowCitationHitInfo={isShowCitationHitInfo}
-              isShowTextToSpeech={isShowTextToSpeech}
-              supportAnnotation={supportAnnotation}
-              appId={appId}
-              question={chatList[index - 1]?.content}
-              onAnnotationEdited={handleAnnotationEdited}
-              onAnnotationAdded={handleAnnotationAdded}
-              onAnnotationRemoved={handleAnnotationRemoved}
-              allToolIcons={allToolIcons}
-              isShowPromptLog={isShowPromptLog}
-            />
-          }
-          return (
-            <Question
-              key={item.id}
-              id={item.id}
-              content={item.content}
-              more={item.more}
-              useCurrentUserAvatar={useCurrentUserAvatar}
-              item={item}
-              isShowPromptLog={isShowPromptLog}
-              isResponding={isResponding}
-            />
-          )
-        })}
-      </div>
-      {!isHideSendInput && (
-        <div className={cn(!feedbackDisabled && '!left-3.5 !right-3.5', 'absolute z-10 bottom-0 left-0 right-0')}>
-          {/* Thinking is sync and can not be stopped */}
-          {(isResponding && canStopResponding && ((!!chatList[chatList.length - 1]?.content) || (chatList[chatList.length - 1]?.agent_thoughts && chatList[chatList.length - 1].agent_thoughts!.length > 0))) && (
-            <div className='flex justify-center mb-4'>
-              <Button className='flex items-center space-x-1 bg-white' onClick={() => abortResponding?.()}>
-                {stopIcon}
-                <span className='text-xs text-gray-500 font-normal'>{t('appDebug.operation.stopResponding')}</span>
-              </Button>
-            </div>
-          )}
-          {isShowSuggestion && (
-            <div className='pt-2'>
-              <div className='flex items-center justify-center mb-2.5'>
-                <div className='grow h-[1px]'
-                  style={{
-                    background: 'linear-gradient(270deg, #F3F4F6 0%, rgba(243, 244, 246, 0) 100%)',
-                  }}></div>
-                <div className='shrink-0 flex items-center px-3 space-x-1'>
-                  {TryToAskIcon}
-                  <span className='text-xs text-gray-500 font-medium'>{t('appDebug.feature.suggestedQuestionsAfterAnswer.tryToAsk')}</span>
-                </div>
-                <div className='grow h-[1px]'
-                  style={{
-                    background: 'linear-gradient(270deg, rgba(243, 244, 246, 0) 0%, #F3F4F6 100%)',
-                  }}></div>
-              </div>
-              {/* has scrollbar would hide part of first item */}
-              <div ref={suggestionListRef} className={cn(!hasScrollbar && 'justify-center', 'flex overflow-x-auto pb-2')}>
-                {suggestionList?.map((item, index) => (
-                  <div key={item} className='shrink-0 flex justify-center mr-2'>
-                    <Button
-                      key={index}
-                      onClick={() => onQueryChange(item)}
-                    >
-                      <span className='text-primary-600 text-xs font-medium'>{item}</span>
-                    </Button>
-                  </div>
-                ))}
-              </div>
-            </div>
-          )}
-          <div className='relative'>
-            <div className={cn('relative p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto', isDragActive && 'border-primary-600')}>
-              {visionConfig?.enabled && (
-                <>
-                  <div className='absolute bottom-2 left-2 flex items-center'>
-                    <ChatImageUploader
-                      settings={visionConfig}
-                      onUpload={onUpload}
-                      disabled={files.length >= visionConfig.number_limits}
-                    />
-                    <div className='mx-1 w-[1px] h-4 bg-black/5' />
-                  </div>
-                  <div className='pl-[52px]'>
-                    <ImageList
-                      list={files}
-                      onRemove={onRemove}
-                      onReUpload={onReUpload}
-                      onImageLinkLoadSuccess={onImageLinkLoadSuccess}
-                      onImageLinkLoadError={onImageLinkLoadError}
-                    />
-                  </div>
-                </>
-              )}
-              <Textarea
-                className={`
-                  block w-full px-2 pr-[118px] py-[7px] leading-5 max-h-none text-sm text-gray-700 outline-none appearance-none resize-none
-                  ${visionConfig?.enabled && 'pl-12'}
-                `}
-                value={query}
-                onChange={handleContentChange}
-                onKeyUp={handleKeyUp}
-                onKeyDown={handleKeyDown}
-                onPaste={onPaste}
-                onDragEnter={onDragEnter}
-                onDragLeave={onDragLeave}
-                onDragOver={onDragOver}
-                onDrop={onDrop}
-                autoSize
-              />
-            </div>
-            <div className="absolute bottom-2 right-2 flex items-center h-8">
-              <div className={`${s.count} mr-4 h-5 leading-5 text-sm bg-gray-50 text-gray-500`}>{query.trim().length}</div>
-              {
-                query
-                  ? (
-                    <div className='flex justify-center items-center w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => onQueryChange('')}>
-                      <XCircle className='w-4 h-4 text-[#98A2B3]' />
-                    </div>
-                  )
-                  : isShowSpeechToText
-                    ? (
-                      <div
-                        className='group flex justify-center items-center w-8 h-8 hover:bg-primary-50 rounded-lg cursor-pointer'
-                        onClick={handleVoiceInputShow}
-                      >
-                        <Microphone01 className='block w-4 h-4 text-gray-500 group-hover:hidden' />
-                        <Microphone01Solid className='hidden w-4 h-4 text-primary-600 group-hover:block' />
-                      </div>
-                    )
-                    : null
-              }
-              <div className='mx-2 w-[1px] h-4 bg-black opacity-5' />
-              {isMobile
-                ? sendBtn
-                : (
-                  <TooltipPlus
-                    popupContent={
-                      <div>
-                        <div>{t('common.operation.send')} Enter</div>
-                        <div>{t('common.operation.lineBreak')} Shift Enter</div>
-                      </div>
-                    }
-                  >
-                    {sendBtn}
-                  </TooltipPlus>
-                )}
-            </div>
-            {voiceInputShow && (
-              <VoiceInput
-                onCancel={() => setVoiceInputShow(false)}
-                onConverted={text => onQueryChange(text)}
-              />
-            )}
-          </div>
-          {customDisclaimer && <div className='text-xs text-gray-500 mt-1 text-center'>
-            {customDisclaimer}
-          </div>}
-        </div>
-      )}
-    </div>
-  )
-}
-export default React.memo(Chat)

+ 0 - 23
web/app/components/app/chat/more-info/index.tsx

@@ -1,23 +0,0 @@
-'use client'
-import type { FC } from 'react'
-import React from 'react'
-import { useTranslation } from 'react-i18next'
-import type { MessageMore } from '../type'
-import { formatNumber } from '@/utils/format'
-
-export type IMoreInfoProps = {
-  more: MessageMore
-  isQuestion: boolean
-  className?: string
-}
-
-const MoreInfo: FC<IMoreInfoProps> = ({ more, isQuestion, className }) => {
-  const { t } = useTranslation()
-  return (<div className={`mt-1 w-full text-xs text-gray-400 ${isQuestion ? 'mr-2 text-right ' : 'pl-2 text-left float-right'} ${className}`}>
-    <span className='mr-2'>{`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`}</span>
-    <span className='mr-2'>{`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`}</span>
-    <span className='mr-2'>·</span>
-    <span>{more.time}</span>
-  </div>)
-}
-export default React.memo(MoreInfo)

+ 0 - 14
web/app/components/app/chat/operation/index.tsx

@@ -1,14 +0,0 @@
-'use client'
-import React from 'react'
-
-const OperationBtn = ({ innerContent, onClick, className }: { innerContent: React.ReactNode; onClick?: () => void; className?: string }) => (
-  <div
-    className={`relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800 ${className ?? ''}`}
-    style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
-    onClick={onClick && onClick}
-  >
-    {innerContent}
-  </div>
-)
-
-export default OperationBtn

+ 0 - 52
web/app/components/app/chat/question/index.tsx

@@ -1,52 +0,0 @@
-'use client'
-import type { FC } from 'react'
-import React, { useRef } from 'react'
-import { useContext } from 'use-context-selector'
-import s from '../style.module.css'
-import type { IChatItem } from '../type'
-import MoreInfo from '../more-info'
-import AppContext from '@/context/app-context'
-import { Markdown } from '@/app/components/base/markdown'
-import ImageGallery from '@/app/components/base/image-gallery'
-
-type IQuestionProps = Pick<IChatItem, 'id' | 'content' | 'more' | 'useCurrentUserAvatar'> & {
-  isShowPromptLog?: boolean
-  item: IChatItem
-  isResponding?: boolean
-}
-
-const Question: FC<IQuestionProps> = ({ id, content, more, useCurrentUserAvatar, isShowPromptLog, item }) => {
-  const { userProfile } = useContext(AppContext)
-  const userName = userProfile?.name
-  const ref = useRef(null)
-  const imgSrcs = item.message_files?.map(item => item.url)
-
-  return (
-    <div className={`flex items-start justify-end ${isShowPromptLog && 'first-of-type:pt-[14px]'}`} key={id} ref={ref}>
-      <div className={s.questionWrapWrap}>
-
-        <div className={`${s.question} group relative text-sm text-gray-900`}>
-          <div
-            className={'mr-2 py-3 px-4 bg-blue-500 rounded-tl-2xl rounded-b-2xl'}
-          >
-            {imgSrcs && imgSrcs.length > 0 && (
-              <ImageGallery srcs={imgSrcs} />
-            )}
-            <Markdown content={content} />
-          </div>
-        </div>
-        {more && <MoreInfo more={more} isQuestion={true} />}
-      </div>
-      {useCurrentUserAvatar
-        ? (
-          <div className='w-10 h-10 shrink-0 leading-10 text-center mr-2 rounded-full bg-primary-600 text-white'>
-            {userName?.[0].toLocaleUpperCase()}
-          </div>
-        )
-        : (
-          <div className={`${s.questionIcon} w-10 h-10 shrink-0 `}></div>
-        )}
-    </div>
-  )
-}
-export default React.memo(Question)

+ 0 - 136
web/app/components/app/chat/style.module.css

@@ -1,136 +0,0 @@
-.answerIcon {
-  position: relative;
-  background: url(./icons/robot.svg) 100%/100%;
-}
-
-.typeingIcon {
-  position: absolute;
-  top: 0px;
-  left: 0px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  width: 16px;
-  height: 16px;
-  background: #FFFFFF;
-  box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
-  border-radius: 16px;
-}
-
-
-.questionIcon {
-  background: url(./icons/default-avatar.jpg);
-  background-size: contain;
-  border-radius: 50%;
-}
-
-.answer::before,
-.question::before {
-  content: '';
-  position: absolute;
-  top: 0;
-  width: 8px;
-  height: 12px;
-}
-
-.answer::before {
-  left: 0;
-  background: url(./icons/answer.svg) no-repeat;
-}
-
-.copyBtn,
-.playBtn,
-.annotationBtn {
-  display: none;
-}
-
-pre:hover .copyBtn {
-  display: block;
-}
-
-.answerWrapWrap,
-.questionWrapWrap {
-  width: 0;
-  flex-grow: 1;
-}
-
-.questionWrapWrap {
-  display: flex;
-  justify-content: flex-end;
-}
-
-.question {
-  display: inline-block;
-  max-width: 100%;
-}
-
-.answer {
-  display: inline-block;
-  max-width: 100%;
-}
-
-.answerWrap:hover .copyBtn,
-.answerWrap:hover .playBtn,
-.answerWrap:hover .annotationBtn {
-  display: block;
-}
-
-.answerWrap:hover .hasAnnotationBtn {
-  display: none;
-}
-
-.answerWrap .itemOperation {
-  display: none;
-}
-
-.answerWrap:hover .itemOperation {
-  display: flex;
-}
-
-.question::before {
-  right: 0;
-  background: url(./icons/question.svg) no-repeat;
-}
-
-.textArea {
-  padding-top: 13px;
-  padding-bottom: 13px;
-  padding-right: 130px;
-  border-radius: 12px;
-  line-height: 20px;
-  background-color: #fff;
-}
-
-.textArea:hover {
-  background-color: #fff;
-}
-
-/* .textArea:focus {
-  box-shadow: 0px 3px 15px -3px rgba(0, 0, 0, 0.1), 0px 4px 6px rgba(0, 0, 0, 0.05);
-} */
-
-.count {
-  /* display: none; */
-  padding: 0 2px;
-}
-
-.sendBtn {
-  background: url(./icons/send.svg) center center no-repeat;
-}
-
-.sendBtnActive {
-  background-image: url(./icons/send-active.svg);
-}
-
-.sendBtn:hover {
-  background-image: url(./icons/send-active.svg);
-  background-color: #EBF5FF;
-}
-
-.textArea:focus+div .count {
-  display: block;
-}
-
-.textArea:focus+div .sendBtn {
-  background-image: url(./icons/send-active.svg);
-}

+ 0 - 7
web/app/components/app/chat/thought/style.module.css

@@ -1,7 +0,0 @@
-.wrap {
-  background-color: rgba(255, 255, 255, 0.92);
-}
-
-.wrapHoverEffect:hover{
-  box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.06), 0px 1px 3px 0px rgba(16, 24, 40, 0.1);
-}

+ 2 - 2
web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx

@@ -20,7 +20,7 @@ import type { OnSend } from '@/app/components/base/chat/types'
 import { useEventEmitterContextContext } from '@/context/event-emitter'
 import { useProviderContext } from '@/context/provider-context'
 import {
-  fetchConvesationMessages,
+  fetchConversationMessages,
   fetchSuggestedQuestions,
   stopChatMessageResponding,
 } from '@/service/debug'
@@ -89,7 +89,7 @@ const ChatItem: FC<ChatItemProps> = ({
       `apps/${appId}/chat-messages`,
       data,
       {
-        onGetConvesationMessages: (conversationId, getAbortController) => fetchConvesationMessages(appId, conversationId, getAbortController),
+        onGetConvesationMessages: (conversationId, getAbortController) => fetchConversationMessages(appId, conversationId, getAbortController),
         onGetSuggestedQuestions: (responseItemId, getAbortController) => fetchSuggestedQuestions(appId, responseItemId, getAbortController),
       },
     )

+ 2 - 2
web/app/components/app/configuration/debug/debug-with-single-model/index.tsx

@@ -15,7 +15,7 @@ import { useDebugConfigurationContext } from '@/context/debug-configuration'
 import type { OnSend } from '@/app/components/base/chat/types'
 import { useProviderContext } from '@/context/provider-context'
 import {
-  fetchConvesationMessages,
+  fetchConversationMessages,
   fetchSuggestedQuestions,
   stopChatMessageResponding,
 } from '@/service/debug'
@@ -94,7 +94,7 @@ const DebugWithSingleModel = forwardRef<DebugWithSingleModelRefType, DebugWithSi
       `apps/${appId}/chat-messages`,
       data,
       {
-        onGetConvesationMessages: (conversationId, getAbortController) => fetchConvesationMessages(appId, conversationId, getAbortController),
+        onGetConvesationMessages: (conversationId, getAbortController) => fetchConversationMessages(appId, conversationId, getAbortController),
         onGetSuggestedQuestions: (responseItemId, getAbortController) => fetchSuggestedQuestions(appId, responseItemId, getAbortController),
       },
     )

+ 112 - 45
web/app/components/app/log/list.tsx

@@ -1,6 +1,6 @@
 'use client'
 import type { FC } from 'react'
-import React, { useEffect, useRef, useState } from 'react'
+import React, { useCallback, useEffect, useRef, useState } from 'react'
 import useSWR from 'swr'
 import {
   HandThumbDownIcon,
@@ -8,6 +8,7 @@ import {
   InformationCircleIcon,
   XMarkIcon,
 } from '@heroicons/react/24/outline'
+import { RiEditFill } from '@remixicon/react'
 import { get } from 'lodash-es'
 import InfiniteScroll from 'react-infinite-scroll-component'
 import dayjs from 'dayjs'
@@ -20,14 +21,13 @@ import cn from 'classnames'
 import s from './style.module.css'
 import VarPanel from './var-panel'
 import { randomString } from '@/utils'
-import { EditIconSolid } from '@/app/components/app/chat/icon-component'
-import type { FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc } from '@/app/components/app/chat/type'
-import type { ChatConversationFullDetailResponse, ChatConversationGeneralDetail, ChatConversationsResponse, ChatMessage, ChatMessagesRequest, CompletionConversationFullDetailResponse, CompletionConversationGeneralDetail, CompletionConversationsResponse, LogAnnotation } from '@/models/log'
+import type { FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc } from '@/app/components/base/chat/chat/type'
+import type { Annotation, ChatConversationFullDetailResponse, ChatConversationGeneralDetail, ChatConversationsResponse, ChatMessage, ChatMessagesRequest, CompletionConversationFullDetailResponse, CompletionConversationGeneralDetail, CompletionConversationsResponse, LogAnnotation } from '@/models/log'
 import type { App } from '@/types/app'
 import Loading from '@/app/components/base/loading'
 import Drawer from '@/app/components/base/drawer'
 import Popover from '@/app/components/base/popover'
-import Chat from '@/app/components/app/chat'
+import Chat from '@/app/components/base/chat/chat'
 import Tooltip from '@/app/components/base/tooltip'
 import { ToastContext } from '@/app/components/base/toast'
 import { fetchChatConversationDetail, fetchChatMessages, fetchCompletionConversationDetail, updateLogMessageAnnotations, updateLogMessageFeedbacks } from '@/service/log'
@@ -38,8 +38,6 @@ import ModelName from '@/app/components/header/account-setting/model-provider-pa
 import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
 import TextGeneration from '@/app/components/app/text-generate/item'
 import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
-import AgentLogModal from '@/app/components/base/agent-log-modal'
-import PromptLogModal from '@/app/components/base/prompt-log-modal'
 import MessageLogModal from '@/app/components/base/message-log-modal'
 import { useStore as useAppStore } from '@/app/components/app/store'
 import { useAppContext } from '@/context/app-context'
@@ -166,13 +164,9 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
   const { userProfile: { timezone } } = useAppContext()
   const { formatTime } = useTimestamp()
   const { onClose, appDetail } = useContext(DrawerContext)
-  const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
+  const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
     currentLogItem: state.currentLogItem,
     setCurrentLogItem: state.setCurrentLogItem,
-    showPromptLogModal: state.showPromptLogModal,
-    setShowPromptLogModal: state.setShowPromptLogModal,
-    showAgentLogModal: state.showAgentLogModal,
-    setShowAgentLogModal: state.setShowAgentLogModal,
     showMessageLogModal: state.showMessageLogModal,
     setShowMessageLogModal: state.setShowMessageLogModal,
     currentLogModalActiveTab: state.currentLogModalActiveTab,
@@ -218,6 +212,72 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
     }
   }
 
+  const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => {
+    setItems(items.map((item, i) => {
+      if (i === index - 1) {
+        return {
+          ...item,
+          content: query,
+        }
+      }
+      if (i === index) {
+        return {
+          ...item,
+          annotation: {
+            ...item.annotation,
+            logAnnotation: {
+              ...item.annotation?.logAnnotation,
+              content: answer,
+            },
+          } as any,
+        }
+      }
+      return item
+    }))
+  }, [items])
+  const handleAnnotationAdded = useCallback((annotationId: string, authorName: string, query: string, answer: string, index: number) => {
+    setItems(items.map((item, i) => {
+      if (i === index - 1) {
+        return {
+          ...item,
+          content: query,
+        }
+      }
+      if (i === index) {
+        const answerItem = {
+          ...item,
+          content: item.content,
+          annotation: {
+            id: annotationId,
+            authorName,
+            logAnnotation: {
+              content: answer,
+              account: {
+                id: '',
+                name: authorName,
+                email: '',
+              },
+            },
+          } as Annotation,
+        }
+        return answerItem
+      }
+      return item
+    }))
+  }, [items])
+  const handleAnnotationRemoved = useCallback((index: number) => {
+    setItems(items.map((item, i) => {
+      if (i === index) {
+        return {
+          ...item,
+          content: item.content,
+          annotation: undefined,
+        }
+      }
+      return item
+    }))
+  }, [items])
+
   useEffect(() => {
     if (appDetail?.id && detail.id && appDetail?.mode !== 'completion')
       fetchData()
@@ -374,24 +434,36 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
             isShowTextToSpeech
             appId={appDetail?.id}
             varList={varList}
+            siteInfo={null}
           />
         </div>
         : items.length < 8
-          ? <div className="px-2.5 pt-4 mb-4">
+          ? <div className="pt-4 mb-4">
             <Chat
+              config={{
+                appId: appDetail?.id,
+                text_to_speech: {
+                  enabled: true,
+                },
+                supportAnnotation: true,
+                annotation_reply: {
+                  enabled: true,
+                },
+                supportFeedback: true,
+              } as any}
               chatList={items}
-              isHideSendInput={true}
+              onAnnotationAdded={handleAnnotationAdded}
+              onAnnotationEdited={handleAnnotationEdited}
+              onAnnotationRemoved={handleAnnotationRemoved}
               onFeedback={onFeedback}
-              displayScene='console'
-              isShowPromptLog
-              supportAnnotation
-              isShowTextToSpeech
-              appId={appDetail?.id}
-              onChatListChange={setItems}
+              noChatInput
+              showPromptLog
+              hideProcessDetail
+              chatContainerInnerClassName='px-6'
             />
           </div>
           : <div
-            className="px-2.5 py-4"
+            className="py-4"
             id="scrollableDiv"
             style={{
               height: 1000, // Specify a value
@@ -422,35 +494,30 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
               inverse={true}
             >
               <Chat
+                config={{
+                  app_id: appDetail?.id,
+                  text_to_speech: {
+                    enabled: true,
+                  },
+                  supportAnnotation: true,
+                  annotation_reply: {
+                    enabled: true,
+                  },
+                  supportFeedback: true,
+                } as any}
                 chatList={items}
-                isHideSendInput={true}
+                onAnnotationAdded={handleAnnotationAdded}
+                onAnnotationEdited={handleAnnotationEdited}
+                onAnnotationRemoved={handleAnnotationRemoved}
                 onFeedback={onFeedback}
-                displayScene='console'
-                isShowPromptLog
+                noChatInput
+                showPromptLog
+                hideProcessDetail
+                chatContainerInnerClassName='px-6'
               />
             </InfiniteScroll>
           </div>
       }
-      {showPromptLogModal && (
-        <PromptLogModal
-          width={width}
-          currentLogItem={currentLogItem}
-          onCancel={() => {
-            setCurrentLogItem()
-            setShowPromptLogModal(false)
-          }}
-        />
-      )}
-      {showAgentLogModal && (
-        <AgentLogModal
-          width={width}
-          currentLogItem={currentLogItem}
-          onCancel={() => {
-            setCurrentLogItem()
-            setShowAgentLogModal(false)
-          }}
-        />
-      )}
       {showMessageLogModal && (
         <MessageLogModal
           width={width}
@@ -575,7 +642,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
       <Tooltip
         htmlContent={
           <span className='text-xs text-gray-500 inline-flex items-center'>
-            <EditIconSolid className='mr-1' />{`${t('appLog.detail.annotationTip', { user: annotation?.account?.name })} ${formatTime(annotation?.created_at || dayjs().unix(), 'MM-DD hh:mm A')}`}
+            <RiEditFill className='w-3 h-3 mr-1' />{`${t('appLog.detail.annotationTip', { user: annotation?.account?.name })} ${formatTime(annotation?.created_at || dayjs().unix(), 'MM-DD hh:mm A')}`}
           </span>
         }
         className={(isHighlight && !isChatMode) ? '' : '!hidden'}

+ 1 - 1
web/app/components/app/overview/embedded/index.tsx

@@ -4,7 +4,7 @@ import cn from 'classnames'
 import copy from 'copy-to-clipboard'
 import style from './style.module.css'
 import Modal from '@/app/components/base/modal'
-import copyStyle from '@/app/components/app/chat/copy-btn/style.module.css'
+import copyStyle from '@/app/components/base/copy-btn/style.module.css'
 import Tooltip from '@/app/components/base/tooltip'
 import { useAppContext } from '@/context/app-context'
 import { IS_CE_EDITION } from '@/config'

+ 1 - 1
web/app/components/app/store.ts

@@ -1,6 +1,6 @@
 import { create } from 'zustand'
 import type { App } from '@/types/app'
-import type { IChatItem } from '@/app/components/app/chat/type'
+import type { IChatItem } from '@/app/components/base/chat/chat/type'
 
 type State = {
   appDetail?: App

+ 1 - 1
web/app/components/app/text-generate/item/index.tsx

@@ -16,7 +16,7 @@ import { Markdown } from '@/app/components/base/markdown'
 import Loading from '@/app/components/base/loading'
 import Toast from '@/app/components/base/toast'
 import AudioBtn from '@/app/components/base/audio-btn'
-import type { Feedbacktype } from '@/app/components/app/chat/type'
+import type { Feedbacktype } from '@/app/components/base/chat/chat/type'
 import { fetchMoreLikeThis, updateFeedback } from '@/service/share'
 import { File02 } from '@/app/components/base/icons/src/vender/line/files'
 import { Bookmark } from '@/app/components/base/icons/src/vender/line/general'

+ 1 - 1
web/app/components/base/agent-log-modal/detail.tsx

@@ -12,7 +12,7 @@ import Loading from '@/app/components/base/loading'
 import { fetchAgentLogDetail } from '@/service/log'
 import type { AgentIteration, AgentLogDetailResponse } from '@/models/log'
 import { useStore as useAppStore } from '@/app/components/app/store'
-import type { IChatItem } from '@/app/components/app/chat/type'
+import type { IChatItem } from '@/app/components/base/chat/chat/type'
 
 export type AgentLogDetailProps = {
   activeTab?: 'DETAIL' | 'TRACING'

+ 1 - 1
web/app/components/base/agent-log-modal/index.tsx

@@ -5,7 +5,7 @@ import { RiCloseLine } from '@remixicon/react'
 import { useEffect, useRef, useState } from 'react'
 import { useClickAway } from 'ahooks'
 import AgentLogDetail from './detail'
-import type { IChatItem } from '@/app/components/app/chat/type'
+import type { IChatItem } from '@/app/components/base/chat/chat/type'
 
 type AgentLogModalProps = {
   currentLogItem?: IChatItem

+ 2 - 2
web/app/components/base/chat/chat-with-history/config-panel/index.tsx

@@ -7,7 +7,7 @@ import AppIcon from '@/app/components/base/app-icon'
 import { MessageDotsCircle } from '@/app/components/base/icons/src/vender/solid/communication'
 import { Edit02 } from '@/app/components/base/icons/src/vender/line/general'
 import { Star06 } from '@/app/components/base/icons/src/vender/solid/shapes'
-import { FootLogo } from '@/app/components/share/chat/welcome/massive-component'
+import LogoSite from '@/app/components/base/logo/logo-site'
 
 const ConfigPanel = () => {
   const { t } = useTranslation()
@@ -153,7 +153,7 @@ const ConfigPanel = () => {
                       {
                         customConfig?.replace_webapp_logo
                           ? <img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
-                          : <FootLogo />
+                          : <LogoSite className='!h-5' />
                       }
                     </div>
                   </div>

+ 1 - 1
web/app/components/base/chat/chat-with-history/sidebar/index.tsx

@@ -10,7 +10,7 @@ import Button from '@/app/components/base/button'
 import { Edit05 } from '@/app/components/base/icons/src/vender/line/general'
 import type { ConversationItem } from '@/models/share'
 import Confirm from '@/app/components/base/confirm'
-import RenameModal from '@/app/components/share/chat/sidebar/rename-modal'
+import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
 
 const Sidebar = () => {
   const { t } = useTranslation()

web/app/components/share/chat/sidebar/rename-modal/index.tsx → web/app/components/base/chat/chat-with-history/sidebar/rename-modal.tsx


+ 1 - 1
web/app/components/base/chat/chat/answer/agent-content.tsx

@@ -5,7 +5,7 @@ import type {
   VisionFile,
 } from '../../types'
 import { Markdown } from '@/app/components/base/markdown'
-import Thought from '@/app/components/app/chat/thought'
+import Thought from '@/app/components/base/chat/chat/thought'
 import ImageGallery from '@/app/components/base/image-gallery'
 import type { Emoji } from '@/app/components/tools/types'
 

+ 2 - 2
web/app/components/base/chat/chat/answer/index.tsx

@@ -16,8 +16,8 @@ import More from './more'
 import WorkflowProcess from './workflow-process'
 import { AnswerTriangle } from '@/app/components/base/icons/src/vender/solid/general'
 import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
-import LoadingAnim from '@/app/components/app/chat/loading-anim'
-import Citation from '@/app/components/app/chat/citation'
+import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
+import Citation from '@/app/components/base/chat/chat/citation'
 import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
 import type { Emoji } from '@/app/components/tools/types'
 import type { AppData } from '@/models/share'

+ 2 - 2
web/app/components/base/chat/chat/answer/operation.tsx

@@ -8,7 +8,7 @@ import cn from 'classnames'
 import { useTranslation } from 'react-i18next'
 import type { ChatItem } from '../../types'
 import { useChatContext } from '../context'
-import CopyBtn from '@/app/components/app/chat/copy-btn'
+import CopyBtn from '@/app/components/base/copy-btn'
 import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
 import AudioBtn from '@/app/components/base/audio-btn'
 import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn'
@@ -18,7 +18,7 @@ import {
   ThumbsUp,
 } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
 import TooltipPlus from '@/app/components/base/tooltip-plus'
-import Log from '@/app/components/app/chat/log'
+import Log from '@/app/components/base/chat/chat/log'
 
 type OperationProps = {
   item: ChatItem

web/app/components/app/chat/citation/index.tsx → web/app/components/base/chat/chat/citation/index.tsx


web/app/components/app/chat/citation/popup.tsx → web/app/components/base/chat/chat/citation/popup.tsx


web/app/components/app/chat/citation/progress-tooltip.tsx → web/app/components/base/chat/chat/citation/progress-tooltip.tsx


web/app/components/app/chat/citation/tooltip.tsx → web/app/components/base/chat/chat/citation/tooltip.tsx


+ 3 - 0
web/app/components/base/chat/chat/index.tsx

@@ -263,6 +263,9 @@ const Chat: FC<ChatProps> = ({
                 />
               )
             }
+            {appData && appData.site.custom_disclaimer && <div className='text-xs text-gray-500 mt-1 text-center'>
+              {appData.site.custom_disclaimer}
+            </div>}
           </div>
         </div>
         {showPromptLogModal && (

web/app/components/app/chat/loading-anim/index.tsx → web/app/components/base/chat/chat/loading-anim/index.tsx


web/app/components/app/chat/loading-anim/style.module.css → web/app/components/base/chat/chat/loading-anim/style.module.css


+ 1 - 1
web/app/components/app/chat/log/index.tsx

@@ -1,7 +1,7 @@
 import type { FC } from 'react'
 import { useTranslation } from 'react-i18next'
 import { File02 } from '@/app/components/base/icons/src/vender/line/files'
-import type { IChatItem } from '@/app/components/app/chat/type'
+import type { IChatItem } from '@/app/components/base/chat/chat/type'
 import { useStore as useAppStore } from '@/app/components/app/store'
 
 type LogProps = {

+ 1 - 1
web/app/components/app/chat/thought/index.tsx

@@ -3,7 +3,7 @@ import type { FC } from 'react'
 import React from 'react'
 import { useContext } from 'use-context-selector'
 import type { ThoughtItem, ToolInfoInThought } from '../type'
-import Tool from '@/app/components/app/chat/thought/tool'
+import Tool from '@/app/components/base/chat/chat/thought/tool'
 import type { Emoji } from '@/app/components/tools/types'
 
 import I18n from '@/context/i18n'

web/app/components/app/chat/thought/panel.tsx → web/app/components/base/chat/chat/thought/panel.tsx


web/app/components/app/chat/thought/tool.tsx → web/app/components/base/chat/chat/thought/tool.tsx


+ 1 - 1
web/app/components/app/chat/type.ts

@@ -1,4 +1,4 @@
-import type { TypeWithI18N } from '../../header/account-setting/model-provider-page/declarations'
+import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
 import type { Annotation, MessageRating } from '@/models/log'
 import type { VisionFile } from '@/types/app'
 

+ 2 - 2
web/app/components/base/chat/embedded-chatbot/config-panel/index.tsx

@@ -8,7 +8,7 @@ import AppIcon from '@/app/components/base/app-icon'
 import { MessageDotsCircle } from '@/app/components/base/icons/src/vender/solid/communication'
 import { Edit02 } from '@/app/components/base/icons/src/vender/line/general'
 import { Star06 } from '@/app/components/base/icons/src/vender/solid/shapes'
-import { FootLogo } from '@/app/components/share/chat/welcome/massive-component'
+import LogoSite from '@/app/components/base/logo/logo-site'
 
 const ConfigPanel = () => {
   const { t } = useTranslation()
@@ -154,7 +154,7 @@ const ConfigPanel = () => {
                       {
                         customConfig?.replace_webapp_logo
                           ? <img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
-                          : <FootLogo />
+                          : <LogoSite className='!h-5' />
                       }
                     </div>
                   </div>

+ 2 - 7
web/app/components/base/chat/embedded-chatbot/header.tsx

@@ -1,24 +1,19 @@
 import type { FC } from 'react'
 import React from 'react'
+import { RiRefreshLine } from '@remixicon/react'
 import { useTranslation } from 'react-i18next'
-// import AppIcon from '@/app/components/base/app-icon'
-import { ReplayIcon } from '@/app/components/app/chat/icon-component'
 import Tooltip from '@/app/components/base/tooltip'
 
 export type IHeaderProps = {
   isMobile?: boolean
   customerIcon?: React.ReactNode
   title: string
-  // icon: string
-  // icon_background: string
   onCreateNewChat?: () => void
 }
 const Header: FC<IHeaderProps> = ({
   isMobile,
   customerIcon,
   title,
-  // icon,
-  // icon_background,
   onCreateNewChat,
 }) => {
   const { t } = useTranslation()
@@ -48,7 +43,7 @@ const Header: FC<IHeaderProps> = ({
         <div className='flex cursor-pointer hover:rounded-lg hover:bg-black/5 w-8 h-8 items-center justify-center' onClick={() => {
           onCreateNewChat?.()
         }}>
-          <ReplayIcon className="h-4 w-4 text-sm font-bold text-white" />
+          <RiRefreshLine className="h-4 w-4 text-sm font-bold text-white" />
         </div>
       </Tooltip>
     </div>

+ 1 - 1
web/app/components/base/chat/types.ts

@@ -3,7 +3,7 @@ import type {
   VisionFile,
   VisionSettings,
 } from '@/types/app'
-import type { IChatItem } from '@/app/components/app/chat/type'
+import type { IChatItem } from '@/app/components/base/chat/chat/type'
 import type { NodeTracing } from '@/types/workflow'
 import type { WorkflowRunningStatus } from '@/app/components/workflow/types'
 

web/app/components/app/chat/copy-btn/index.tsx → web/app/components/base/copy-btn/index.tsx


web/app/components/app/chat/copy-btn/style.module.css → web/app/components/base/copy-btn/style.module.css


+ 3 - 3
web/app/components/base/markdown.tsx

@@ -9,9 +9,9 @@ import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs
 import type { RefObject } from 'react'
 import { useEffect, useRef, useState } from 'react'
 import cn from 'classnames'
-import CopyBtn from '@/app/components/app/chat/copy-btn'
-import SVGBtn from '@/app/components/app/chat/svg'
-import Flowchart from '@/app/components/app/chat/mermaid'
+import CopyBtn from '@/app/components/base/copy-btn'
+import SVGBtn from '@/app/components/base/svg'
+import Flowchart from '@/app/components/base/mermaid'
 
 // Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
 const capitalizationLanguageNameMap: Record<string, string> = {

web/app/components/app/chat/mermaid/index.tsx → web/app/components/base/mermaid/index.tsx


+ 1 - 1
web/app/components/base/message-log-modal/index.tsx

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
 import { useBoolean, useClickAway } from 'ahooks'
 import { RiCloseLine } from '@remixicon/react'
 import IterationResultPanel from '../../workflow/run/iteration-result-panel'
-import type { IChatItem } from '@/app/components/app/chat/type'
+import type { IChatItem } from '@/app/components/base/chat/chat/type'
 import Run from '@/app/components/workflow/run'
 import type { NodeTracing } from '@/types/workflow'
 

+ 1 - 1
web/app/components/base/prompt-log-modal/index.tsx

@@ -4,7 +4,7 @@ import { useClickAway } from 'ahooks'
 import { RiCloseLine } from '@remixicon/react'
 import Card from './card'
 import { CopyFeedbackNew } from '@/app/components/base/copy-feedback'
-import type { IChatItem } from '@/app/components/app/chat/type'
+import type { IChatItem } from '@/app/components/base/chat/chat/type'
 
 type PromptLogModalProps = {
   currentLogItem?: IChatItem

web/app/components/app/chat/svg/index.tsx → web/app/components/base/svg/index.tsx


web/app/components/app/chat/svg/style.module.css → web/app/components/base/svg/style.module.css


+ 0 - 13
web/app/components/share/chat/config-scence/index.tsx

@@ -1,13 +0,0 @@
-import type { FC } from 'react'
-import React from 'react'
-import type { IWelcomeProps } from '../welcome'
-import Welcome from '../welcome'
-
-const ConfigSence: FC<IWelcomeProps> = (props) => {
-  return (
-    <div className='mb-5 antialiased font-sans shrink-0'>
-      <Welcome {...props} />
-    </div>
-  )
-}
-export default React.memo(ConfigSence)

+ 0 - 73
web/app/components/share/chat/hooks/use-conversation.ts

@@ -1,73 +0,0 @@
-import { useCallback, useState } from 'react'
-import produce from 'immer'
-import { useGetState } from 'ahooks'
-import type { ConversationItem } from '@/models/share'
-
-const storageConversationIdKey = 'conversationIdInfo'
-
-type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'>
-function useConversation() {
-  const [conversationList, setConversationList] = useState<ConversationItem[]>([])
-  const [pinnedConversationList, setPinnedConversationList] = useState<ConversationItem[]>([])
-  const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState<string>('-1')
-  // when set conversation id, we do not have set appId
-  const setCurrConversationId = useCallback((id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => {
-    doSetCurrConversationId(id)
-    if (isSetToLocalStroge && id !== '-1') {
-      // conversationIdInfo: {[appId1]: conversationId1, [appId2]: conversationId2}
-      const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {}
-      conversationIdInfo[appId] = id
-      globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo))
-    }
-  }, [doSetCurrConversationId])
-
-  const getConversationIdFromStorage = (appId: string) => {
-    const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {}
-    const id = conversationIdInfo[appId]
-    return id
-  }
-
-  const isNewConversation = currConversationId === '-1'
-  // input can be updated by user
-  const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any> | null>(null)
-  const resetNewConversationInputs = () => {
-    if (!newConversationInputs)
-      return
-    setNewConversationInputs(produce(newConversationInputs, (draft) => {
-      Object.keys(draft).forEach((key) => {
-        draft[key] = ''
-      })
-    }))
-  }
-  const [existConversationInputs, setExistConversationInputs] = useState<Record<string, any> | null>(null)
-  const currInputs = isNewConversation ? newConversationInputs : existConversationInputs
-  const setCurrInputs = isNewConversation ? setNewConversationInputs : setExistConversationInputs
-
-  // info is muted
-  const [newConversationInfo, setNewConversationInfo] = useState<ConversationInfoType | null>(null)
-  const [existConversationInfo, setExistConversationInfo] = useState<ConversationInfoType | null>(null)
-  const currConversationInfo = isNewConversation ? newConversationInfo : existConversationInfo
-
-  return {
-    conversationList,
-    setConversationList,
-    pinnedConversationList,
-    setPinnedConversationList,
-    currConversationId,
-    getCurrConversationId,
-    setCurrConversationId,
-    getConversationIdFromStorage,
-    isNewConversation,
-    currInputs,
-    newConversationInputs,
-    existConversationInputs,
-    resetNewConversationInputs,
-    setCurrInputs,
-    currConversationInfo,
-    setNewConversationInfo,
-    existConversationInfo,
-    setExistConversationInfo,
-  }
-}
-
-export default useConversation

+ 0 - 953
web/app/components/share/chat/index.tsx

@@ -1,953 +0,0 @@
-/* eslint-disable @typescript-eslint/no-use-before-define */
-'use client'
-import type { FC } from 'react'
-import React, { useCallback, useEffect, useRef, useState } from 'react'
-import cn from 'classnames'
-import useSWR from 'swr'
-import { useTranslation } from 'react-i18next'
-import { useContext } from 'use-context-selector'
-import produce, { setAutoFreeze } from 'immer'
-import { useBoolean, useGetState } from 'ahooks'
-import AppUnavailable from '../../base/app-unavailable'
-import { checkOrSetAccessToken } from '../utils'
-import { addFileInfos, sortAgentSorts } from '../../tools/utils'
-import useConversation from './hooks/use-conversation'
-import { ToastContext } from '@/app/components/base/toast'
-import Sidebar from '@/app/components/share/chat/sidebar'
-import ConfigSence from '@/app/components/share/chat/config-scence'
-import Header from '@/app/components/share/header'
-import {
-  delConversation,
-  fetchAppInfo,
-  fetchAppMeta,
-  fetchAppParams,
-  fetchChatList,
-  fetchConversations,
-  fetchSuggestedQuestions,
-  generationConversationName,
-  pinConversation,
-  sendChatMessage,
-  stopChatMessageResponding,
-  unpinConversation,
-  updateFeedback,
-} from '@/service/share'
-import type { AppMeta, ConversationItem, SiteInfo } from '@/models/share'
-
-import type {
-  CitationConfig,
-  PromptConfig,
-  SpeechToTextConfig,
-  SuggestedQuestionsAfterAnswerConfig,
-  TextToSpeechConfig,
-} from '@/models/debug'
-import type { Feedbacktype, IChatItem } from '@/app/components/app/chat/type'
-import Chat from '@/app/components/app/chat'
-import { changeLanguage } from '@/i18n/i18next-config'
-import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
-import Loading from '@/app/components/base/loading'
-import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
-import { userInputsFormToPromptVariables } from '@/utils/model-config'
-import type { InstalledApp } from '@/models/explore'
-import Confirm from '@/app/components/base/confirm'
-import type { VisionFile, VisionSettings } from '@/types/app'
-import { Resolution, TransferMethod } from '@/types/app'
-import { fetchFileUploadConfig } from '@/service/common'
-import type { Annotation as AnnotationType } from '@/models/log'
-
-export type IMainProps = {
-  isInstalledApp?: boolean
-  installedAppInfo?: InstalledApp
-  isSupportPlugin?: boolean
-}
-
-const Main: FC<IMainProps> = ({
-  isInstalledApp = false,
-  installedAppInfo,
-}) => {
-  const { t } = useTranslation()
-  const { notify } = useContext(ToastContext)
-  const media = useBreakpoints()
-  const isMobile = media === MediaType.mobile
-
-  /*
-  * app info
-  */
-  const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
-  const [isUnknownReason, setIsUnknwonReason] = useState<boolean>(false)
-  const [appId, setAppId] = useState<string>('')
-  const [isPublicVersion, setIsPublicVersion] = useState<boolean>(true)
-  const [siteInfo, setSiteInfo] = useState<SiteInfo | null>()
-  const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null)
-  const [inited, setInited] = useState<boolean>(false)
-  const [plan, setPlan] = useState<string>('basic') // basic/plus/pro
-  const [canReplaceLogo, setCanReplaceLogo] = useState<boolean>(false)
-  const [customConfig, setCustomConfig] = useState<any>(null)
-  const [appMeta, setAppMeta] = useState<AppMeta | null>(null)
-  // in mobile, show sidebar by click button
-  const [isShowSidebar, { setTrue: showSidebar, setFalse: hideSidebar }] = useBoolean(false)
-  // Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
-  useEffect(() => {
-    if (siteInfo?.title) {
-      if (canReplaceLogo)
-        document.title = `${siteInfo.title}`
-      else
-        document.title = `${siteInfo.title} - Powered by Dify`
-    }
-  }, [siteInfo?.title, canReplaceLogo])
-
-  /*
-  * conversation info
-  */
-  const [allConversationList, setAllConversationList] = useState<ConversationItem[]>([])
-  const [isClearConversationList, { setTrue: clearConversationListTrue, setFalse: clearConversationListFalse }] = useBoolean(false)
-  const [isClearPinnedConversationList, { setTrue: clearPinnedConversationListTrue, setFalse: clearPinnedConversationListFalse }] = useBoolean(false)
-  const {
-    conversationList,
-    setConversationList,
-    pinnedConversationList,
-    setPinnedConversationList,
-    currConversationId,
-    getCurrConversationId,
-    setCurrConversationId,
-    getConversationIdFromStorage,
-    isNewConversation,
-    currConversationInfo,
-    currInputs,
-    newConversationInputs,
-    // existConversationInputs,
-    resetNewConversationInputs,
-    setCurrInputs,
-    setNewConversationInfo,
-    existConversationInfo,
-    setExistConversationInfo,
-  } = useConversation()
-  const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
-  const [hasMore, setHasMore] = useState<boolean>(true)
-  const [hasPinnedMore, setHasPinnedMore] = useState<boolean>(true)
-  const [isShowSuggestion, setIsShowSuggestion] = useState(false)
-  const onMoreLoaded = useCallback(({ data: conversations, has_more }: any) => {
-    setHasMore(has_more)
-    if (isClearConversationList) {
-      setConversationList(conversations)
-      clearConversationListFalse()
-    }
-    else {
-      setConversationList([...conversationList, ...conversations])
-    }
-  }, [conversationList, setConversationList, isClearConversationList, clearConversationListFalse])
-  const onPinnedMoreLoaded = useCallback(({ data: conversations, has_more }: any) => {
-    setHasPinnedMore(has_more)
-    if (isClearPinnedConversationList) {
-      setPinnedConversationList(conversations)
-      clearPinnedConversationListFalse()
-    }
-    else {
-      setPinnedConversationList([...pinnedConversationList, ...conversations])
-    }
-  }, [pinnedConversationList, setPinnedConversationList, isClearPinnedConversationList, clearPinnedConversationListFalse])
-  const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0)
-  const noticeUpdateList = useCallback(() => {
-    setHasMore(true)
-    clearConversationListTrue()
-
-    setHasPinnedMore(true)
-    clearPinnedConversationListTrue()
-
-    setControlUpdateConversationList(Date.now())
-  }, [clearConversationListTrue, clearPinnedConversationListTrue])
-  const handlePin = useCallback(async (id: string) => {
-    await pinConversation(isInstalledApp, installedAppInfo?.id, id)
-    notify({ type: 'success', message: t('common.api.success') })
-    noticeUpdateList()
-  }, [isInstalledApp, installedAppInfo?.id, t, notify, noticeUpdateList])
-
-  const handleUnpin = useCallback(async (id: string) => {
-    await unpinConversation(isInstalledApp, installedAppInfo?.id, id)
-    notify({ type: 'success', message: t('common.api.success') })
-    noticeUpdateList()
-  }, [isInstalledApp, installedAppInfo?.id, t, notify, noticeUpdateList])
-  const [isShowConfirm, { setTrue: showConfirm, setFalse: hideConfirm }] = useBoolean(false)
-  const [toDeleteConversationId, setToDeleteConversationId] = useState('')
-  const handleDelete = useCallback((id: string) => {
-    setToDeleteConversationId(id)
-    hideSidebar() // mobile
-    showConfirm()
-  }, [hideSidebar, showConfirm])
-
-  const didDelete = async () => {
-    await delConversation(isInstalledApp, installedAppInfo?.id, toDeleteConversationId)
-    notify({ type: 'success', message: t('common.api.success') })
-    hideConfirm()
-    if (currConversationId === toDeleteConversationId)
-      handleConversationIdChange('-1')
-
-    noticeUpdateList()
-  }
-
-  const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
-  const [speechToTextConfig, setSpeechToTextConfig] = useState<SpeechToTextConfig | null>(null)
-  const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig | null>(null)
-  const [citationConfig, setCitationConfig] = useState<CitationConfig | null>(null)
-  const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
-  const chatListDomRef = useRef<HTMLDivElement>(null)
-  const [isResponding, { setTrue: setRespondingTrue, setFalse: setRespondingFalse }] = useBoolean(false)
-  const [abortController, setAbortController] = useState<AbortController | null>(null)
-  const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false)
-  const [isChatStarted, { setTrue: setChatStarted, setFalse: setChatNotStarted }] = useBoolean(false)
-  const conversationIntroduction = currConversationInfo?.introduction || ''
-  const createNewChat = useCallback(async () => {
-    // if new chat is already exist, do not create new chat
-    abortController?.abort()
-    setRespondingFalse()
-    if (conversationList.some(item => item.id === '-1'))
-      return
-
-    setConversationList(produce(conversationList, (draft) => {
-      draft.unshift({
-        id: '-1',
-        name: t('share.chat.newChatDefaultName'),
-        inputs: newConversationInputs,
-        introduction: conversationIntroduction,
-      })
-    }))
-  }, [
-    abortController,
-    setRespondingFalse,
-    setConversationList,
-    conversationList,
-    newConversationInputs,
-    conversationIntroduction,
-    t,
-  ])
-  const handleStartChat = useCallback((inputs: Record<string, any>) => {
-    createNewChat()
-    setConversationIdChangeBecauseOfNew(true)
-    setCurrInputs(inputs)
-    setChatStarted()
-    // parse variables in introduction
-    setChatList(generateNewChatListWithOpenstatement('', inputs))
-  }, [
-    createNewChat,
-    setConversationIdChangeBecauseOfNew,
-    setCurrInputs,
-    setChatStarted,
-    setChatList,
-  ])
-  const hasSetInputs = (() => {
-    if (!isNewConversation)
-      return true
-
-    return isChatStarted
-  })()
-
-  const conversationName = currConversationInfo?.name || t('share.chat.newChatDefaultName') as string
-  const [controlChatUpdateAllConversation, setControlChatUpdateAllConversation] = useState(0)
-
-  // onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
-  useEffect(() => {
-    setAutoFreeze(false)
-    return () => {
-      setAutoFreeze(true)
-    }
-  }, [])
-
-  useEffect(() => {
-    (async () => {
-      if (controlChatUpdateAllConversation && !isNewConversation) {
-        const { data: allConversations } = await fetchAllConversations() as { data: ConversationItem[]; has_more: boolean }
-        const item = allConversations.find(item => item.id === currConversationId)
-        setAllConversationList(allConversations)
-        if (item) {
-          setExistConversationInfo({
-            ...existConversationInfo,
-            name: item?.name || '',
-          } as any)
-        }
-      }
-    })()
-  }, [controlChatUpdateAllConversation])
-
-  const handleConversationSwitch = () => {
-    if (!inited)
-      return
-    if (!appId) {
-      // wait for appId
-      setTimeout(handleConversationSwitch, 100)
-      return
-    }
-
-    // update inputs of current conversation
-    let notSyncToStateIntroduction = ''
-    let notSyncToStateInputs: Record<string, any> | undefined | null = {}
-    if (!isNewConversation) {
-      const item = allConversationList.find(item => item.id === currConversationId)
-      notSyncToStateInputs = item?.inputs || {}
-      setCurrInputs(notSyncToStateInputs)
-      notSyncToStateIntroduction = item?.introduction || ''
-      setExistConversationInfo({
-        name: item?.name || '',
-        introduction: notSyncToStateIntroduction,
-      })
-    }
-    else {
-      notSyncToStateInputs = newConversationInputs
-      setCurrInputs(notSyncToStateInputs)
-    }
-
-    // update chat list of current conversation
-    if (!isNewConversation && !conversationIdChangeBecauseOfNew) {
-      fetchChatList(currConversationId, isInstalledApp, installedAppInfo?.id).then((res: any) => {
-        const { data } = res
-        const newChatList: IChatItem[] = generateNewChatListWithOpenstatement(notSyncToStateIntroduction, notSyncToStateInputs)
-
-        data.forEach((item: any) => {
-          newChatList.push({
-            id: `question-${item.id}`,
-            content: item.query,
-            isAnswer: false,
-            message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [],
-          })
-          newChatList.push({
-            id: item.id,
-            content: item.answer,
-            agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files),
-            feedback: item.feedback,
-            isAnswer: true,
-            citation: item.retriever_resources,
-            message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
-          })
-        })
-        setChatList(newChatList)
-      })
-    }
-
-    if (isNewConversation && isChatStarted)
-      setChatList(generateNewChatListWithOpenstatement())
-
-    setControlFocus(Date.now())
-  }
-  useEffect(handleConversationSwitch, [currConversationId, inited])
-
-  /*
-  * chat info. chat is under conversation.
-  */
-  useEffect(() => {
-    // scroll to bottom
-    if (chatListDomRef.current)
-      chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight
-  }, [chatList, currConversationId])
-  // user can not edit inputs if user had send message
-  const canEditInpus = !chatList.some(item => item.isAnswer === false) && isNewConversation
-
-  const handleConversationIdChange = useCallback((id: string) => {
-    if (id === '-1') {
-      createNewChat()
-      setConversationIdChangeBecauseOfNew(true)
-    }
-    else {
-      setConversationIdChangeBecauseOfNew(false)
-    }
-    // trigger handleConversationSwitch
-    setCurrConversationId(id, appId)
-    setIsShowSuggestion(false)
-    hideSidebar()
-  }, [
-    appId,
-    createNewChat,
-    hideSidebar,
-    setCurrConversationId,
-    setIsShowSuggestion,
-    setConversationIdChangeBecauseOfNew,
-  ])
-
-  // sometime introduction is not applied to state
-  const generateNewChatListWithOpenstatement = (introduction?: string, inputs?: Record<string, any> | null) => {
-    let caculatedIntroduction = introduction || conversationIntroduction || ''
-    const caculatedPromptVariables = inputs || currInputs || null
-    if (caculatedIntroduction && caculatedPromptVariables)
-      caculatedIntroduction = replaceStringWithValues(caculatedIntroduction, promptConfig?.prompt_variables || [], caculatedPromptVariables)
-
-    const openstatement = {
-      id: `${Date.now()}`,
-      content: caculatedIntroduction,
-      isAnswer: true,
-      feedbackDisabled: true,
-      isOpeningStatement: true,
-      suggestedQuestions: openingSuggestedQuestions,
-    }
-    if (caculatedIntroduction)
-      return [openstatement]
-
-    return []
-  }
-
-  const fetchAllConversations = () => {
-    return fetchConversations(isInstalledApp, installedAppInfo?.id, undefined, undefined, 100)
-  }
-
-  const fetchInitData = async () => {
-    if (!isInstalledApp)
-      await checkOrSetAccessToken()
-
-    return Promise.all([isInstalledApp
-      ? {
-        app_id: installedAppInfo?.id,
-        site: {
-          title: installedAppInfo?.app.name,
-          icon: installedAppInfo?.app.icon,
-          icon_background: installedAppInfo?.app.icon_background,
-          prompt_public: false,
-          copyright: '',
-        },
-        plan: 'basic',
-      }
-      : fetchAppInfo(), fetchAllConversations(), fetchAppParams(isInstalledApp, installedAppInfo?.id), fetchAppMeta(isInstalledApp, installedAppInfo?.id)])
-  }
-
-  const { data: fileUploadConfigResponse } = useSWR(isInstalledApp ? { url: '/files/upload' } : null, fetchFileUploadConfig)
-
-  // init
-  useEffect(() => {
-    (async () => {
-      try {
-        const [appData, conversationData, appParams, appMeta]: any = await fetchInitData()
-        setAppMeta(appMeta)
-        const { app_id: appId, site: siteInfo, plan, can_replace_logo, custom_config }: any = appData
-        setAppId(appId)
-        setPlan(plan)
-        setCanReplaceLogo(can_replace_logo)
-        setCustomConfig(custom_config)
-        const tempIsPublicVersion = siteInfo.prompt_public
-        setIsPublicVersion(tempIsPublicVersion)
-        const prompt_template = ''
-        // handle current conversation id
-        const { data: allConversations } = conversationData as { data: ConversationItem[]; has_more: boolean }
-        const _conversationId = getConversationIdFromStorage(appId)
-        const isNotNewConversation = allConversations.some(item => item.id === _conversationId)
-        setAllConversationList(allConversations)
-        // fetch new conversation info
-        const { user_input_form, opening_statement: introduction, suggested_questions, suggested_questions_after_answer, speech_to_text, text_to_speech, retriever_resource, file_upload, sensitive_word_avoidance }: any = appParams
-        setVisionConfig({
-          ...file_upload.image,
-          image_file_size_limit: appParams?.system_parameters?.image_file_size_limit,
-        })
-        const prompt_variables = userInputsFormToPromptVariables(user_input_form)
-        if (siteInfo.default_language)
-          changeLanguage(siteInfo.default_language)
-
-        setNewConversationInfo({
-          name: t('share.chat.newChatDefaultName'),
-          introduction,
-        })
-        setOpeningSuggestedQuestions(suggested_questions || [])
-
-        setSiteInfo(siteInfo as SiteInfo)
-        setPromptConfig({
-          prompt_template,
-          prompt_variables,
-        } as PromptConfig)
-        setSuggestedQuestionsAfterAnswerConfig(suggested_questions_after_answer)
-        setSpeechToTextConfig(speech_to_text)
-        setTextToSpeechConfig(text_to_speech)
-        setCitationConfig(retriever_resource)
-
-        // setConversationList(conversations as ConversationItem[])
-
-        if (isNotNewConversation)
-          setCurrConversationId(_conversationId, appId, false)
-
-        setInited(true)
-      }
-      catch (e: any) {
-        if (e.status === 404) {
-          setAppUnavailable(true)
-        }
-        else {
-          setIsUnknwonReason(true)
-          setAppUnavailable(true)
-        }
-      }
-    })()
-  }, [])
-
-  const logError = useCallback((message: string) => {
-    notify({ type: 'error', message })
-  }, [notify])
-
-  const checkCanSend = useCallback(() => {
-    if (currConversationId !== '-1')
-      return true
-
-    const prompt_variables = promptConfig?.prompt_variables
-    const inputs = currInputs
-    if (!inputs || !prompt_variables || prompt_variables?.length === 0)
-      return true
-
-    let hasEmptyInput = ''
-    const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
-      const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
-      return res
-    }) || [] // compatible with old version
-    requiredVars.forEach(({ key, name }) => {
-      if (hasEmptyInput)
-        return
-
-      if (!inputs?.[key])
-        hasEmptyInput = name
-    })
-
-    if (hasEmptyInput) {
-      logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
-      return false
-    }
-    return !hasEmptyInput
-  }, [currConversationId, currInputs, promptConfig, t, logError])
-
-  const [controlFocus, setControlFocus] = useState(0)
-  const doShowSuggestion = isShowSuggestion && !isResponding
-  const [openingSuggestedQuestions, setOpeningSuggestedQuestions] = useState<string[]>([])
-  const [messageTaskId, setMessageTaskId] = useState('')
-  const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
-  const [isRespondingConIsCurrCon, setIsRespondingConCurrCon, getIsRespondingConIsCurrCon] = useGetState(true)
-  const [userQuery, setUserQuery] = useState('')
-  const [visionConfig, setVisionConfig] = useState<VisionSettings>({
-    enabled: false,
-    number_limits: 2,
-    detail: Resolution.low,
-    transfer_methods: [TransferMethod.local_file],
-  })
-
-  const updateCurrentQA = ({
-    responseItem,
-    questionId,
-    placeholderAnswerId,
-    questionItem,
-  }: {
-    responseItem: IChatItem
-    questionId: string
-    placeholderAnswerId: string
-    questionItem: IChatItem
-  }) => {
-    // closesure new list is outdated.
-    const newListWithAnswer = produce(
-      getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
-      (draft) => {
-        if (!draft.find(item => item.id === questionId))
-          draft.push({ ...questionItem })
-
-        draft.push({ ...responseItem })
-      })
-    setChatList(newListWithAnswer)
-  }
-
-  const handleSend = async (message: string, files?: VisionFile[]) => {
-    if (isResponding) {
-      notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
-      return
-    }
-
-    if (files?.find(item => item.transfer_method === TransferMethod.local_file && !item.upload_file_id)) {
-      notify({ type: 'info', message: t('appDebug.errorMessage.waitForImgUpload') })
-      return false
-    }
-
-    const data: Record<string, any> = {
-      inputs: currInputs,
-      query: message,
-      conversation_id: isNewConversation ? null : currConversationId,
-    }
-
-    if (visionConfig?.enabled && files && files?.length > 0) {
-      data.files = files.map((item) => {
-        if (item.transfer_method === TransferMethod.local_file) {
-          return {
-            ...item,
-            url: '',
-          }
-        }
-        return item
-      })
-    }
-
-    // qustion
-    const questionId = `question-${Date.now()}`
-    const questionItem = {
-      id: questionId,
-      content: message,
-      isAnswer: false,
-      message_files: files,
-
-    }
-
-    const placeholderAnswerId = `answer-placeholder-${Date.now()}`
-    const placeholderAnswerItem = {
-      id: placeholderAnswerId,
-      content: '',
-      isAnswer: true,
-    }
-
-    const newList = [...getChatList(), questionItem, placeholderAnswerItem]
-    setChatList(newList)
-
-    let isAgentMode = false
-
-    // answer
-    const responseItem: IChatItem = {
-      id: `${Date.now()}`,
-      content: '',
-      agent_thoughts: [],
-      message_files: [],
-      isAnswer: true,
-    }
-    let hasSetResponseId = false
-
-    const prevTempNewConversationId = getCurrConversationId() || '-1'
-    let tempNewConversationId = prevTempNewConversationId
-
-    setHasStopResponded(false)
-    setRespondingTrue()
-    setIsShowSuggestion(false)
-    setIsRespondingConCurrCon(true)
-    sendChatMessage(data, {
-      getAbortController: (abortController) => {
-        setAbortController(abortController)
-      },
-      onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
-        if (!isAgentMode) {
-          responseItem.content = responseItem.content + message
-        }
-        else {
-          const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
-          if (lastThought)
-            lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
-        }
-        if (messageId && !hasSetResponseId) {
-          responseItem.id = messageId
-          hasSetResponseId = true
-        }
-
-        if (isFirstMessage && newConversationId)
-          tempNewConversationId = newConversationId
-
-        setMessageTaskId(taskId)
-        // has switched to other conversation
-        if (prevTempNewConversationId !== getCurrConversationId()) {
-          setIsRespondingConCurrCon(false)
-          return
-        }
-        updateCurrentQA({
-          responseItem,
-          questionId,
-          placeholderAnswerId,
-          questionItem,
-        })
-      },
-      async onCompleted(hasError?: boolean) {
-        if (hasError)
-          return
-
-        if (getConversationIdChangeBecauseOfNew()) {
-          const { data: allConversations }: any = await fetchAllConversations()
-          const newItem: any = await generationConversationName(isInstalledApp, installedAppInfo?.id, allConversations[0].id)
-
-          const newAllConversations = produce(allConversations, (draft: any) => {
-            draft[0].name = newItem.name
-          })
-          setAllConversationList(newAllConversations as any)
-          noticeUpdateList()
-        }
-        setConversationIdChangeBecauseOfNew(false)
-        resetNewConversationInputs()
-        setChatNotStarted()
-        setCurrConversationId(tempNewConversationId, appId, true)
-        if (getIsRespondingConIsCurrCon() && suggestedQuestionsAfterAnswerConfig?.enabled && !getHasStopResponded()) {
-          const { data }: any = await fetchSuggestedQuestions(responseItem.id, isInstalledApp, installedAppInfo?.id)
-          setSuggestQuestions(data)
-          setIsShowSuggestion(true)
-        }
-        setRespondingFalse()
-      },
-      onFile(file) {
-        const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
-        if (lastThought)
-          lastThought.message_files = [...(lastThought as any).message_files, { ...file }]
-
-        updateCurrentQA({
-          responseItem,
-          questionId,
-          placeholderAnswerId,
-          questionItem,
-        })
-      },
-      onThought(thought) {
-        isAgentMode = true
-        const response = responseItem as any
-        if (thought.message_id && !hasSetResponseId) {
-          response.id = thought.message_id
-          hasSetResponseId = true
-        }
-        // responseItem.id = thought.message_id;
-        if (response.agent_thoughts.length === 0) {
-          response.agent_thoughts.push(thought)
-        }
-        else {
-          const lastThought = response.agent_thoughts[response.agent_thoughts.length - 1]
-          // thought changed but still the same thought, so update.
-          if (lastThought.id === thought.id) {
-            thought.thought = lastThought.thought
-            thought.message_files = lastThought.message_files
-            responseItem.agent_thoughts![response.agent_thoughts.length - 1] = thought
-          }
-          else {
-            responseItem.agent_thoughts!.push(thought)
-          }
-        }
-        // has switched to other conversation
-        if (prevTempNewConversationId !== getCurrConversationId()) {
-          setIsRespondingConCurrCon(false)
-          return false
-        }
-
-        updateCurrentQA({
-          responseItem,
-          questionId,
-          placeholderAnswerId,
-          questionItem,
-        })
-      },
-      onMessageEnd: (messageEnd) => {
-        if (messageEnd.metadata?.annotation_reply) {
-          responseItem.id = messageEnd.id
-          responseItem.annotation = ({
-            id: messageEnd.metadata.annotation_reply.id,
-            authorName: messageEnd.metadata.annotation_reply.account.name,
-          } as AnnotationType)
-          const newListWithAnswer = produce(
-            getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
-            (draft) => {
-              if (!draft.find(item => item.id === questionId))
-                draft.push({ ...questionItem })
-
-              draft.push({
-                ...responseItem,
-              })
-            })
-          setChatList(newListWithAnswer)
-          return
-        }
-        // not support show citation
-        // responseItem.citation = messageEnd.retriever_resources
-        if (!isInstalledApp)
-          return
-        const newListWithAnswer = produce(
-          getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
-          (draft) => {
-            if (!draft.find(item => item.id === questionId))
-              draft.push({ ...questionItem })
-
-            draft.push({ ...responseItem })
-          })
-        setChatList(newListWithAnswer)
-      },
-      onMessageReplace: (messageReplace) => {
-        if (isInstalledApp) {
-          responseItem.content = messageReplace.answer
-        }
-        else {
-          setChatList(produce(
-            getChatList(),
-            (draft) => {
-              const current = draft.find(item => item.id === messageReplace.id)
-
-              if (current)
-                current.content = messageReplace.answer
-            },
-          ))
-        }
-      },
-      onError() {
-        setRespondingFalse()
-        // role back placeholder answer
-        setChatList(produce(getChatList(), (draft) => {
-          draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1)
-        }))
-      },
-    }, isInstalledApp, installedAppInfo?.id)
-  }
-
-  const handleFeedback = useCallback(async (messageId: string, feedback: Feedbacktype) => {
-    await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, installedAppInfo?.id)
-    const newChatList = chatList.map((item) => {
-      if (item.id === messageId) {
-        return {
-          ...item,
-          feedback,
-        }
-      }
-      return item
-    })
-    setChatList(newChatList)
-    notify({ type: 'success', message: t('common.api.success') })
-  }, [isInstalledApp, installedAppInfo?.id, chatList, t, notify, setChatList])
-
-  const handleListChanged = useCallback((list: ConversationItem[]) => {
-    setConversationList(list)
-    setControlChatUpdateAllConversation(Date.now())
-  }, [setConversationList, setControlChatUpdateAllConversation])
-  const handlePinnedListChanged = useCallback((list: ConversationItem[]) => {
-    setPinnedConversationList(list)
-    setControlChatUpdateAllConversation(Date.now())
-  }, [setPinnedConversationList, setControlChatUpdateAllConversation])
-  const handleStartChatOnSidebar = useCallback(() => {
-    handleConversationIdChange('-1')
-  }, [handleConversationIdChange])
-
-  const renderSidebar = () => {
-    if (!appId || !siteInfo || !promptConfig)
-      return null
-    return (
-      <Sidebar
-        list={conversationList}
-        onListChanged={handleListChanged}
-        isClearConversationList={isClearConversationList}
-        pinnedList={pinnedConversationList}
-        onPinnedListChanged={handlePinnedListChanged}
-        isClearPinnedConversationList={isClearPinnedConversationList}
-        onMoreLoaded={onMoreLoaded}
-        onPinnedMoreLoaded={onPinnedMoreLoaded}
-        isNoMore={!hasMore}
-        isPinnedNoMore={!hasPinnedMore}
-        onCurrentIdChange={handleConversationIdChange}
-        currentId={currConversationId}
-        copyRight={siteInfo.copyright || siteInfo.title}
-        isInstalledApp={isInstalledApp}
-        installedAppId={installedAppInfo?.id}
-        siteInfo={siteInfo}
-        onPin={handlePin}
-        onUnpin={handleUnpin}
-        controlUpdateList={controlUpdateConversationList}
-        onDelete={handleDelete}
-        onStartChat={handleStartChatOnSidebar}
-      />
-    )
-  }
-
-  const handleAbortResponding = useCallback(async () => {
-    await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id)
-    setHasStopResponded(true)
-    setRespondingFalse()
-  }, [appId, messageTaskId, isInstalledApp, installedAppInfo?.id])
-
-  if (appUnavailable)
-    return <AppUnavailable isUnknownReason={isUnknownReason} />
-
-  if (!appId || !siteInfo || !promptConfig) {
-    return <div className='flex h-screen w-full'>
-      <Loading type='app' />
-    </div>
-  }
-
-  return (
-    <div className='bg-gray-100 h-full flex flex-col'>
-      {!isInstalledApp && (
-        <Header
-          title={siteInfo.title}
-          icon={siteInfo.icon || ''}
-          icon_background={siteInfo.icon_background || ''}
-          isMobile={isMobile}
-          onShowSideBar={showSidebar}
-          onCreateNewChat={handleStartChatOnSidebar}
-        />
-      )}
-
-      <div
-        className={cn(
-          'flex rounded-t-2xl bg-white overflow-hidden h-full w-full',
-          isInstalledApp && 'rounded-b-2xl',
-        )}
-        style={isInstalledApp
-          ? {
-            boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)',
-          }
-          : {}}
-      >
-        {/* sidebar */}
-        {!isMobile && renderSidebar()}
-        {isMobile && isShowSidebar && (
-          <div className='fixed inset-0 z-50'
-            style={{ backgroundColor: 'rgba(35, 56, 118, 0.2)' }}
-            onClick={hideSidebar}
-          >
-            <div className='inline-block' onClick={e => e.stopPropagation()}>
-              {renderSidebar()}
-            </div>
-          </div>
-        )}
-        {/* main */}
-        <div className={cn(
-          'h-full flex-grow flex flex-col overflow-y-auto',
-        )
-        }>
-          <ConfigSence
-            conversationName={conversationName}
-            hasSetInputs={hasSetInputs}
-            isPublicVersion={isPublicVersion}
-            siteInfo={siteInfo}
-            promptConfig={promptConfig}
-            onStartChat={handleStartChat}
-            canEidtInpus={canEditInpus}
-            savedInputs={currInputs as Record<string, any>}
-            onInputsChange={setCurrInputs}
-            plan={plan}
-            canReplaceLogo={canReplaceLogo}
-            customConfig={customConfig}
-          ></ConfigSence>
-
-          {
-            hasSetInputs && (
-              <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponding ? 'pb-[113px]' : 'pb-[76px]'), 'relative grow h-[200px] pc:w-[794px] max-w-full mobile:w-full mx-auto mb-3.5 overflow-hidden')}>
-                <div className='h-full overflow-y-auto' ref={chatListDomRef}>
-                  <Chat
-                    chatList={chatList}
-                    query={userQuery}
-                    onQueryChange={setUserQuery}
-                    onSend={handleSend}
-                    isHideFeedbackEdit
-                    onFeedback={handleFeedback}
-                    isResponding={isResponding}
-                    canStopResponding={!!messageTaskId && isRespondingConIsCurrCon}
-                    abortResponding={handleAbortResponding}
-                    checkCanSend={checkCanSend}
-                    controlFocus={controlFocus}
-                    isShowSuggestion={doShowSuggestion}
-                    suggestionList={suggestedQuestions}
-                    isShowSpeechToText={speechToTextConfig?.enabled}
-                    isShowTextToSpeech={textToSpeechConfig?.enabled}
-                    isShowCitation={citationConfig?.enabled}
-                    visionConfig={{
-                      ...visionConfig,
-                      image_file_size_limit: fileUploadConfigResponse ? fileUploadConfigResponse.image_file_size_limit : visionConfig.image_file_size_limit,
-                    }}
-                    allToolIcons={appMeta?.tool_icons || {}}
-                    customDisclaimer={siteInfo.custom_disclaimer}
-                  />
-                </div>
-              </div>)
-          }
-
-          {isShowConfirm && (
-            <Confirm
-              title={t('share.chat.deleteConversation.title')}
-              content={t('share.chat.deleteConversation.content')}
-              isShow={isShowConfirm}
-              onClose={hideConfirm}
-              onConfirm={didDelete}
-              onCancel={hideConfirm}
-            />
-          )}
-        </div>
-      </div>
-    </div>
-  )
-}
-export default React.memo(Main)

+ 0 - 28
web/app/components/share/chat/sidebar/app-info/index.tsx

@@ -1,28 +0,0 @@
-'use client'
-import type { FC } from 'react'
-import React from 'react'
-import cn from 'classnames'
-import { appDefaultIconBackground } from '@/config/index'
-import AppIcon from '@/app/components/base/app-icon'
-
-export type IAppInfoProps = {
-  className?: string
-  icon: string
-  icon_background?: string
-  name: string
-}
-
-const AppInfo: FC<IAppInfoProps> = ({
-  className,
-  icon,
-  icon_background,
-  name,
-}) => {
-  return (
-    <div className={cn(className, 'flex items-center space-x-3')}>
-      <AppIcon size="small" icon={icon} background={icon_background || appDefaultIconBackground} />
-      <div className='w-0 grow text-sm font-semibold text-gray-800 overflow-hidden  text-ellipsis whitespace-nowrap'>{name}</div>
-    </div>
-  )
-}
-export default React.memo(AppInfo)

+ 0 - 3
web/app/components/share/chat/sidebar/card.module.css

@@ -1,3 +0,0 @@
-.card:hover {
-  background: linear-gradient(0deg, rgba(235, 245, 255, 0.4), rgba(235, 245, 255, 0.4)), #FFFFFF;
-}

+ 0 - 19
web/app/components/share/chat/sidebar/card.tsx

@@ -1,19 +0,0 @@
-import React from 'react'
-import { useTranslation } from 'react-i18next'
-import s from './card.module.css'
-
-type PropType = {
-  children: React.ReactNode
-  text?: string
-}
-function Card({ children, text }: PropType) {
-  const { t } = useTranslation()
-  return (
-    <div className={`${s.card} box-border w-full flex flex-col items-start px-4 py-3 rounded-lg border-solid border border-gray-200  cursor-pointer hover:border-primary-300`}>
-      <div className='text-gray-400 font-medium text-xs mb-2'>{text ?? t('share.chat.powerBy')}</div>
-      {children}
-    </div>
-  )
-}
-
-export default Card

+ 0 - 167
web/app/components/share/chat/sidebar/index.tsx

@@ -1,167 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react'
-import type { FC } from 'react'
-import { useTranslation } from 'react-i18next'
-import {
-  PencilSquareIcon,
-} from '@heroicons/react/24/outline'
-import cn from 'classnames'
-import Button from '../../../base/button'
-import List from './list'
-import AppInfo from '@/app/components/share/chat/sidebar/app-info'
-// import Card from './card'
-import type { ConversationItem, SiteInfo } from '@/models/share'
-import { fetchConversations } from '@/service/share'
-
-export type ISidebarProps = {
-  copyRight: string
-  currentId: string
-  onCurrentIdChange: (id: string) => void
-  list: ConversationItem[]
-  onListChanged: (newList: ConversationItem[]) => void
-  isClearConversationList: boolean
-  pinnedList: ConversationItem[]
-  onPinnedListChanged: (newList: ConversationItem[]) => void
-  isClearPinnedConversationList: boolean
-  isInstalledApp: boolean
-  installedAppId?: string
-  siteInfo: SiteInfo
-  onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
-  onPinnedMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
-  isNoMore: boolean
-  isPinnedNoMore: boolean
-  onPin: (id: string) => void
-  onUnpin: (id: string) => void
-  controlUpdateList: number
-  onDelete: (id: string) => void
-  onStartChat: (inputs: Record<string, any>) => void
-}
-
-const Sidebar: FC<ISidebarProps> = ({
-  copyRight,
-  currentId,
-  onCurrentIdChange,
-  list,
-  onListChanged,
-  isClearConversationList,
-  pinnedList,
-  onPinnedListChanged,
-  isClearPinnedConversationList,
-  isInstalledApp,
-  installedAppId,
-  siteInfo,
-  onMoreLoaded,
-  onPinnedMoreLoaded,
-  isNoMore,
-  isPinnedNoMore,
-  onPin,
-  onUnpin,
-  controlUpdateList,
-  onDelete,
-  onStartChat,
-}) => {
-  const { t } = useTranslation()
-  const [hasPinned, setHasPinned] = useState(false)
-
-  const checkHasPinned = async () => {
-    const res = await fetchConversations(isInstalledApp, installedAppId, undefined, true) as any
-    setHasPinned(res.data.length > 0)
-  }
-
-  useEffect(() => {
-    checkHasPinned()
-  }, [])
-
-  useEffect(() => {
-    if (controlUpdateList !== 0)
-      checkHasPinned()
-  }, [controlUpdateList])
-
-  const handleUnpin = useCallback((id: string) => {
-    onUnpin(id)
-  }, [onUnpin])
-  const handlePin = useCallback((id: string) => {
-    onPin(id)
-  }, [onPin])
-
-  const maxListHeight = (isInstalledApp) ? 'max-h-[30vh]' : 'max-h-[40vh]'
-
-  return (
-    <div
-      className={
-        cn(
-          (isInstalledApp) ? 'tablet:h-[calc(100vh_-_74px)]' : '',
-          'shrink-0 flex flex-col bg-white pc:w-[244px] tablet:w-[192px] mobile:w-[240px]  border-r border-gray-200 mobile:h-screen',
-        )
-      }
-    >
-      {isInstalledApp && (
-        <AppInfo
-          className='my-4 px-4'
-          name={siteInfo.title || ''}
-          icon={siteInfo.icon || ''}
-          icon_background={siteInfo.icon_background}
-        />
-      )}
-      <div className="flex flex-shrink-0 p-4 !pb-0">
-        <Button
-          onClick={() => onStartChat({})}
-          variant='secondary-accent'
-          className="group w-full flex-shrink-0 justify-start">
-          <PencilSquareIcon className="mr-2 h-4 w-4" /> {t('share.chat.newChat')}
-        </Button>
-      </div>
-      <div className={'flex-grow flex flex-col h-0 overflow-y-auto overflow-x-hidden'}>
-        {/* pinned list */}
-        {hasPinned && (
-          <div className={cn('mt-4 px-4', list.length === 0 && 'flex flex-col flex-grow')}>
-            <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.pinnedTitle')}</div>
-            <List
-              className={cn(list.length > 0 ? maxListHeight : 'flex-grow')}
-              currentId={currentId}
-              onCurrentIdChange={onCurrentIdChange}
-              list={pinnedList}
-              onListChanged={onPinnedListChanged}
-              isClearConversationList={isClearPinnedConversationList}
-              isInstalledApp={isInstalledApp}
-              installedAppId={installedAppId}
-              onMoreLoaded={onPinnedMoreLoaded}
-              isNoMore={isPinnedNoMore}
-              isPinned={true}
-              onPinChanged={handleUnpin}
-              controlUpdate={controlUpdateList + 1}
-              onDelete={onDelete}
-            />
-          </div>
-        )}
-        {/* unpinned list */}
-        <div className={cn('grow flex flex-col mt-4 px-4', !hasPinned && 'flex flex-col flex-grow')}>
-          {(hasPinned && list.length > 0) && (
-            <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.unpinnedTitle')}</div>
-          )}
-          <List
-            className={cn('flex-grow h-0')}
-            currentId={currentId}
-            onCurrentIdChange={onCurrentIdChange}
-            list={list}
-            onListChanged={onListChanged}
-            isClearConversationList={isClearConversationList}
-            isInstalledApp={isInstalledApp}
-            installedAppId={installedAppId}
-            onMoreLoaded={onMoreLoaded}
-            isNoMore={isNoMore}
-            isPinned={false}
-            onPinChanged={handlePin}
-            controlUpdate={controlUpdateList + 1}
-            onDelete={onDelete}
-          />
-        </div>
-
-      </div>
-      <div className="flex flex-shrink-0 pr-4 pb-4 pl-4">
-        <div className="text-gray-400 font-normal text-xs">© {copyRight} {(new Date()).getFullYear()}</div>
-      </div>
-    </div>
-  )
-}
-
-export default React.memo(Sidebar)

+ 0 - 143
web/app/components/share/chat/sidebar/list/index.tsx

@@ -1,143 +0,0 @@
-'use client'
-import type { FC } from 'react'
-import React, { useRef, useState } from 'react'
-
-import { useBoolean, useInfiniteScroll } from 'ahooks'
-import cn from 'classnames'
-import { useTranslation } from 'react-i18next'
-import RenameModal from '../rename-modal'
-import Item from './item'
-import type { ConversationItem } from '@/models/share'
-import { fetchConversations, renameConversation } from '@/service/share'
-import Toast from '@/app/components/base/toast'
-
-export type IListProps = {
-  className: string
-  currentId: string
-  onCurrentIdChange: (id: string) => void
-  list: ConversationItem[]
-  onListChanged?: (newList: ConversationItem[]) => void
-  isClearConversationList: boolean
-  isInstalledApp: boolean
-  installedAppId?: string
-  onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
-  isNoMore: boolean
-  isPinned: boolean
-  onPinChanged: (id: string) => void
-  controlUpdate: number
-  onDelete: (id: string) => void
-}
-
-const List: FC<IListProps> = ({
-  className,
-  currentId,
-  onCurrentIdChange,
-  list,
-  onListChanged,
-  isClearConversationList,
-  isInstalledApp,
-  installedAppId,
-  onMoreLoaded,
-  isNoMore,
-  isPinned,
-  onPinChanged,
-  controlUpdate,
-  onDelete,
-}) => {
-  const { t } = useTranslation()
-  const listRef = useRef<HTMLDivElement>(null)
-
-  useInfiniteScroll(
-    async () => {
-      if (!isNoMore) {
-        let lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined
-        if (lastId === '-1')
-          lastId = undefined
-        const res = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned) as any
-        const { data: conversations, has_more }: any = res
-        onMoreLoaded({ data: conversations, has_more })
-      }
-      return { list: [] }
-    },
-    {
-      target: listRef,
-      isNoMore: () => {
-        return isNoMore
-      },
-      reloadDeps: [isNoMore, controlUpdate],
-    },
-  )
-  const [isShowRename, { setTrue: setShowRename, setFalse: setHideRename }] = useBoolean(false)
-  const [isSaving, { setTrue: setIsSaving, setFalse: setNotSaving }] = useBoolean(false)
-  const [currentConversation, setCurrentConversation] = useState<ConversationItem | null>(null)
-  const showRename = (item: ConversationItem) => {
-    setCurrentConversation(item)
-    setShowRename()
-  }
-  const handleRename = async (newName: string) => {
-    if (!newName.trim() || !currentConversation) {
-      Toast.notify({
-        type: 'error',
-        message: t('common.chat.conversationNameCanNotEmpty'),
-      })
-      return
-    }
-
-    setIsSaving()
-    const currId = currentConversation.id
-    try {
-      await renameConversation(isInstalledApp, installedAppId, currId, newName)
-
-      Toast.notify({
-        type: 'success',
-        message: t('common.actionMsg.modifiedSuccessfully'),
-      })
-      onListChanged?.(list.map((item) => {
-        if (item.id === currId) {
-          return {
-            ...item,
-            name: newName,
-          }
-        }
-        return item
-      }))
-      setHideRename()
-    }
-    finally {
-      setNotSaving()
-    }
-  }
-  return (
-    <nav
-      ref={listRef}
-      className={cn(className, 'shrink-0 space-y-1 bg-white overflow-y-auto overflow-x-hidden')}
-    >
-      {list.map((item) => {
-        const isCurrent = item.id === currentId
-        return (
-          <Item
-            key={item.id}
-            item={item}
-            isCurrent={isCurrent}
-            onClick={onCurrentIdChange}
-            isPinned={isPinned}
-            togglePin={onPinChanged}
-            onDelete={onDelete}
-            onRenameConversation={showRename}
-          />
-        )
-      })}
-      {isShowRename && (
-        <RenameModal
-          isShow={isShowRename}
-          onClose={setHideRename}
-          saveLoading={isSaving}
-          name={currentConversation?.name || ''}
-          onSave={handleRename}
-        />
-      )}
-    </nav>
-  )
-}
-
-export default React.memo(List)

+ 0 - 77
web/app/components/share/chat/sidebar/list/item.tsx

@@ -1,77 +0,0 @@
-'use client'
-import type { FC } from 'react'
-import React, { useRef } from 'react'
-import cn from 'classnames'
-import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon } from '@heroicons/react/24/solid'
-import {
-  ChatBubbleOvalLeftEllipsisIcon,
-} from '@heroicons/react/24/outline'
-import { useHover } from 'ahooks'
-import ItemOperation from '@/app/components/explore/item-operation'
-import type { ConversationItem } from '@/models/share'
-
-export type IItemProps = {
-  onClick: (id: string) => void
-  item: ConversationItem
-  isCurrent: boolean
-  isPinned: boolean
-  togglePin: (id: string) => void
-  onDelete: (id: string) => void
-  onRenameConversation: (item: ConversationItem) => void
-}
-
-const Item: FC<IItemProps> = ({
-  isCurrent,
-  item,
-  onClick,
-  isPinned,
-  togglePin,
-  onDelete,
-  onRenameConversation,
-}) => {
-  const ItemIcon = isCurrent ? ChatBubbleOvalLeftEllipsisSolidIcon : ChatBubbleOvalLeftEllipsisIcon
-  const ref = useRef(null)
-  const isHovering = useHover(ref)
-
-  return (
-    <div
-      ref={ref}
-      onClick={() => onClick(item.id)}
-      key={item.id}
-      className={cn(
-        isCurrent
-          ? 'bg-primary-50 text-primary-600'
-          : 'text-gray-700 hover:bg-gray-200 hover:text-gray-700',
-        'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer',
-      )}
-    >
-      <div className='flex items-center w-0 grow'>
-        <ItemIcon
-          className={cn(
-            isCurrent
-              ? 'text-primary-600'
-              : 'text-gray-400 group-hover:text-gray-500',
-            'mr-3 h-5 w-5 flex-shrink-0',
-          )}
-          aria-hidden="true"
-        />
-        <span>{item.name}</span>
-      </div>
-
-      {item.id !== '-1' && (
-        <div className='shrink-0 h-6' onClick={e => e.stopPropagation()}>
-          <ItemOperation
-            isPinned={isPinned}
-            isItemHovering={isHovering}
-            togglePin={() => togglePin(item.id)}
-            isShowDelete
-            isShowRenameConversation
-            onRenameConversation={() => onRenameConversation(item)}
-            onDelete={() => onDelete(item.id)}
-          />
-        </div>
-      )}
-    </div>
-  )
-}
-export default React.memo(Item)

+ 0 - 77
web/app/components/share/chat/value-panel/index.tsx

@@ -1,77 +0,0 @@
-'use client'
-import type { FC, ReactNode } from 'react'
-import React from 'react'
-import cn from 'classnames'
-import { useTranslation } from 'react-i18next'
-import s from './style.module.css'
-import { StarIcon } from '@/app/components/share/chat/welcome/massive-component'
-import Button from '@/app/components/base/button'
-
-export type ITemplateVarPanelProps = {
-  className?: string
-  header: ReactNode
-  children?: ReactNode | null
-  isFold: boolean
-}
-
-const TemplateVarPanel: FC<ITemplateVarPanelProps> = ({
-  className,
-  header,
-  children,
-  isFold,
-}) => {
-  return (
-    <div className={cn(isFold ? 'border border-indigo-100' : s.boxShodow, className, 'rounded-xl ')}>
-      {/* header */}
-      <div
-        className={cn(isFold && 'rounded-b-xl', 'rounded-t-xl px-6 py-4 bg-indigo-25 text-xs')}
-      >
-        {header}
-      </div>
-      {/* body */}
-      {!isFold && children && (
-        <div className='rounded-b-xl p-6'>
-          {children}
-        </div>
-      )}
-    </div>
-  )
-}
-
-export const PanelTitle: FC<{ title: string; className?: string }> = ({
-  title,
-  className,
-}) => {
-  return (
-    <div className={cn(className, 'flex items-center space-x-1 text-indigo-600')}>
-      <StarIcon />
-      <span className='text-xs'>{title}</span>
-    </div>
-  )
-}
-
-export const VarOpBtnGroup: FC<{ className?: string; onConfirm: () => void; onCancel: () => void }> = ({
-  className,
-  onConfirm,
-  onCancel,
-}) => {
-  const { t } = useTranslation()
-
-  return (
-    <div className={cn(className, 'flex mt-3 space-x-2 mobile:ml-0 tablet:ml-[128px] text-sm')}>
-      <Button
-        variant='primary'
-        onClick={onConfirm}
-      >
-        {t('common.operation.save')}
-      </Button>
-      <Button
-        onClick={onCancel}
-      >
-        {t('common.operation.cancel')}
-      </Button>
-    </div >
-  )
-}
-
-export default React.memo(TemplateVarPanel)

+ 0 - 3
web/app/components/share/chat/value-panel/style.module.css

@@ -1,3 +0,0 @@
-.boxShodow {
-  box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
-}

+ 0 - 388
web/app/components/share/chat/welcome/index.tsx

@@ -1,388 +0,0 @@
-'use client'
-import type { FC } from 'react'
-import React, { useEffect, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { useContext } from 'use-context-selector'
-import TemplateVarPanel, { PanelTitle, VarOpBtnGroup } from '../value-panel'
-import s from './style.module.css'
-import { AppInfo, ChatBtn, EditBtn, FootLogo, PromptTemplate } from './massive-component'
-import type { SiteInfo } from '@/models/share'
-import type { PromptConfig } from '@/models/debug'
-import { ToastContext } from '@/app/components/base/toast'
-import Select from '@/app/components/base/select'
-import { DEFAULT_VALUE_MAX_LEN } from '@/config'
-
-// regex to match the {{}} and replace it with a span
-const regex = /\{\{([^}]+)\}\}/g
-
-export type IWelcomeProps = {
-  conversationName: string
-  hasSetInputs: boolean
-  isPublicVersion: boolean
-  siteInfo: SiteInfo
-  promptConfig: PromptConfig
-  onStartChat: (inputs: Record<string, any>) => void
-  canEidtInpus: boolean
-  savedInputs: Record<string, any>
-  onInputsChange: (inputs: Record<string, any>) => void
-  plan?: string
-  canReplaceLogo?: boolean
-  customConfig?: {
-    remove_webapp_brand?: boolean
-    replace_webapp_logo?: string
-  }
-}
-
-const Welcome: FC<IWelcomeProps> = ({
-  conversationName,
-  hasSetInputs,
-  isPublicVersion,
-  siteInfo,
-  promptConfig,
-  onStartChat,
-  canEidtInpus,
-  savedInputs,
-  onInputsChange,
-  customConfig,
-}) => {
-  const { t } = useTranslation()
-  const hasVar = promptConfig.prompt_variables.length > 0
-  const [isFold, setIsFold] = useState<boolean>(true)
-  const [inputs, setInputs] = useState<Record<string, any>>((() => {
-    if (hasSetInputs)
-      return savedInputs
-
-    const res: Record<string, any> = {}
-    if (promptConfig) {
-      promptConfig.prompt_variables.forEach((item) => {
-        res[item.key] = ''
-      })
-    }
-    // debugger
-    return res
-  })())
-  useEffect(() => {
-    if (!savedInputs) {
-      const res: Record<string, any> = {}
-      if (promptConfig) {
-        promptConfig.prompt_variables.forEach((item) => {
-          res[item.key] = ''
-        })
-      }
-      setInputs(res)
-    }
-    else {
-      setInputs(savedInputs)
-    }
-  }, [savedInputs])
-
-  const highLightPromoptTemplate = (() => {
-    if (!promptConfig)
-      return ''
-    const res = promptConfig.prompt_template.replace(regex, (match, p1) => {
-      return `<span class='text-gray-800 font-bold'>${inputs?.[p1] ? inputs?.[p1] : match}</span>`
-    })
-    return res
-  })()
-
-  const { notify } = useContext(ToastContext)
-  const logError = (message: string) => {
-    notify({ type: 'error', message, duration: 3000 })
-  }
-
-  const renderHeader = () => {
-    return (
-      <div className='absolute top-0 left-0 right-0 flex items-center justify-between border-b border-gray-100 mobile:h-12 tablet:h-16 px-8 bg-white'>
-        <div className='text-gray-900'>{conversationName}</div>
-      </div>
-    )
-  }
-
-  const renderInputs = () => {
-    return (
-      <div className='space-y-3'>
-        {promptConfig.prompt_variables.map(item => (
-          <div className='tablet:flex items-start mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}>
-            <label className={`flex-shrink-0 flex items-center tablet:leading-9 mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label>
-            {item.type === 'select'
-              && (
-                <Select
-                  className='w-full'
-                  defaultValue={inputs?.[item.key]}
-                  onSelect={(i) => { setInputs({ ...inputs, [item.key]: i.value }) }}
-                  items={(item.options || []).map(i => ({ name: i, value: i }))}
-                  allowSearch={false}
-                  bgClassName='bg-gray-50'
-                />
-              )}
-            {item.type === 'string' && (
-              <input
-                placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
-                value={inputs?.[item.key] || ''}
-                onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
-                className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'}
-                maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN}
-              />
-            )}
-            {item.type === 'paragraph' && (
-              <textarea
-                className="w-full h-[104px] flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50"
-                placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
-                value={inputs?.[item.key] || ''}
-                onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
-              />
-            )}
-            {item.type === 'number' && (
-              <input
-                type='number'
-                placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
-                value={inputs?.[item.key] || ''}
-                onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
-                className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'}
-              />
-            )}
-          </div>
-        ))}
-      </div>
-    )
-  }
-
-  const canChat = () => {
-    const prompt_variables = promptConfig?.prompt_variables
-    if (!inputs || !prompt_variables || prompt_variables?.length === 0)
-      return true
-
-    let hasEmptyInput = ''
-    const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
-      const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
-      return res
-    }) || [] // compatible with old version
-    requiredVars.forEach(({ key, name }) => {
-      if (hasEmptyInput)
-        return
-
-      if (!inputs?.[key])
-        hasEmptyInput = name
-    })
-
-    if (hasEmptyInput) {
-      logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
-      return false
-    }
-    return !hasEmptyInput
-  }
-
-  const handleChat = () => {
-    if (!canChat())
-      return
-
-    onStartChat(inputs)
-  }
-
-  const renderNoVarPanel = () => {
-    if (isPublicVersion) {
-      return (
-        <div>
-          <AppInfo siteInfo={siteInfo} />
-          <TemplateVarPanel
-            isFold={false}
-            header={
-              <>
-                <PanelTitle
-                  title={t('share.chat.publicPromptConfigTitle')}
-                  className='mb-1'
-                />
-                <PromptTemplate html={highLightPromoptTemplate} />
-              </>
-            }
-          >
-            <ChatBtn onClick={handleChat} />
-          </TemplateVarPanel>
-        </div>
-      )
-    }
-    // private version
-    return (
-      <TemplateVarPanel
-        isFold={false}
-        header={
-          <AppInfo siteInfo={siteInfo} />
-        }
-      >
-        <ChatBtn onClick={handleChat} />
-      </TemplateVarPanel>
-    )
-  }
-
-  const renderVarPanel = () => {
-    return (
-      <TemplateVarPanel
-        isFold={false}
-        header={
-          <AppInfo siteInfo={siteInfo} />
-        }
-      >
-        {renderInputs()}
-        <ChatBtn
-          className='mt-3 mobile:ml-0 tablet:ml-[128px]'
-          onClick={handleChat}
-        />
-      </TemplateVarPanel>
-    )
-  }
-
-  const renderVarOpBtnGroup = () => {
-    return (
-      <VarOpBtnGroup
-        onConfirm={() => {
-          if (!canChat())
-            return
-
-          onInputsChange(inputs)
-          setIsFold(true)
-        }}
-        onCancel={() => {
-          setInputs(savedInputs)
-          setIsFold(true)
-        }}
-      />
-    )
-  }
-
-  const renderHasSetInputsPublic = () => {
-    if (!canEidtInpus) {
-      return (
-        <TemplateVarPanel
-          isFold={false}
-          header={
-            <>
-              <PanelTitle
-                title={t('share.chat.publicPromptConfigTitle')}
-                className='mb-1'
-              />
-              <PromptTemplate html={highLightPromoptTemplate} />
-            </>
-          }
-        />
-      )
-    }
-
-    return (
-      <TemplateVarPanel
-        isFold={isFold}
-        header={
-          <>
-            <PanelTitle
-              title={t('share.chat.publicPromptConfigTitle')}
-              className='mb-1'
-            />
-            <PromptTemplate html={highLightPromoptTemplate} />
-            {isFold && (
-              <div className='flex items-center justify-between mt-3 border-t border-indigo-100 pt-4 text-xs text-indigo-600'>
-                <span className='text-gray-700'>{t('share.chat.configStatusDes')}</span>
-                <EditBtn onClick={() => setIsFold(false)} />
-              </div>
-            )}
-          </>
-        }
-      >
-        {renderInputs()}
-        {renderVarOpBtnGroup()}
-      </TemplateVarPanel>
-    )
-  }
-
-  const renderHasSetInputsPrivate = () => {
-    if (!canEidtInpus || !hasVar)
-      return null
-
-    return (
-      <TemplateVarPanel
-        isFold={isFold}
-        header={
-          <div className='flex items-center justify-between text-indigo-600'>
-            <PanelTitle
-              title={!isFold ? t('share.chat.privatePromptConfigTitle') : t('share.chat.configStatusDes')}
-            />
-            {isFold && (
-              <EditBtn onClick={() => setIsFold(false)} />
-            )}
-          </div>
-        }
-      >
-        {renderInputs()}
-        {renderVarOpBtnGroup()}
-      </TemplateVarPanel>
-    )
-  }
-
-  const renderHasSetInputs = () => {
-    if ((!isPublicVersion && !canEidtInpus) || !hasVar)
-      return null
-
-    return (
-      <div
-        className='pt-[88px] mb-5'
-      >
-        {isPublicVersion ? renderHasSetInputsPublic() : renderHasSetInputsPrivate()}
-      </div>)
-  }
-
-  return (
-    <div className='relative mobile:min-h-[48px] tablet:min-h-[64px]'>
-      {hasSetInputs && renderHeader()}
-      <div className='mx-auto pc:w-[794px] max-w-full mobile:w-full px-3.5'>
-        {/*  Has't set inputs  */}
-        {
-          !hasSetInputs && (
-            <div className='mobile:pt-[72px] tablet:pt-[128px] pc:pt-[200px]'>
-              {hasVar
-                ? (
-                  renderVarPanel()
-                )
-                : (
-                  renderNoVarPanel()
-                )}
-            </div>
-          )
-        }
-
-        {/* Has set inputs */}
-        {hasSetInputs && renderHasSetInputs()}
-
-        {/* foot */}
-        {!hasSetInputs && (
-          <div className='mt-4 flex justify-between items-center h-8 text-xs text-gray-400'>
-
-            {siteInfo.privacy_policy
-              ? <div>{t('share.chat.privacyPolicyLeft')}
-                <a
-                  className='text-gray-500 px-1'
-                  href={siteInfo.privacy_policy}
-                  target='_blank' rel='noopener noreferrer'>{t('share.chat.privacyPolicyMiddle')}</a>
-                {t('share.chat.privacyPolicyRight')}
-              </div>
-              : <div>
-              </div>}
-            {
-              customConfig?.remove_webapp_brand
-                ? null
-                : (
-                  <a className='flex items-center pr-3 space-x-3' href="https://dify.ai/" target="_blank">
-                    <span className='uppercase'>{t('share.chat.powerBy')}</span>
-                    {
-                      customConfig?.replace_webapp_logo
-                        ? <img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
-                        : <FootLogo />
-                    }
-                  </a>
-                )
-            }
-          </div>
-        )}
-      </div>
-    </div >
-  )
-}
-
-export default React.memo(Welcome)

File diff suppressed because it is too large
+ 0 - 74
web/app/components/share/chat/welcome/massive-component.tsx


+ 0 - 22
web/app/components/share/chat/welcome/style.module.css

@@ -1,22 +0,0 @@
-.boxShodow {
-  box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
-}
-
-.bgGrayColor {
-  background-color: #F9FAFB;
-}
-
-.headerBg {
-  height: 3.5rem;
-  padding-left: 1.5rem;
-  padding-right: 1.5rem;
-}
-
-.formLabel {
-  width: 120px;
-  margin-right: 8px;
-}
-
-.customBtn {
-  width: auto;
-}

+ 0 - 13
web/app/components/share/chatbot/config-scence/index.tsx

@@ -1,13 +0,0 @@
-import type { FC } from 'react'
-import React from 'react'
-import type { IWelcomeProps } from '../welcome'
-import Welcome from '../welcome'
-
-const ConfigScene: FC<IWelcomeProps> = (props) => {
-  return (
-    <div className='mb-5 antialiased font-sans shrink-0'>
-      <Welcome {...props} />
-    </div>
-  )
-}
-export default React.memo(ConfigScene)

+ 0 - 72
web/app/components/share/chatbot/hooks/use-conversation.ts

@@ -1,72 +0,0 @@
-import { useState } from 'react'
-import produce from 'immer'
-import { useGetState } from 'ahooks'
-import type { ConversationItem } from '@/models/share'
-
-const storageConversationIdKey = 'conversationIdInfo'
-
-type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'>
-function useConversation() {
-  const [conversationList, setConversationList] = useState<ConversationItem[]>([])
-  const [pinnedConversationList, setPinnedConversationList] = useState<ConversationItem[]>([])
-  const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState<string>('-1')
-  // when set conversation id, we do not have set appId
-  const setCurrConversationId = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => {
-    doSetCurrConversationId(id)
-    if (isSetToLocalStroge && id !== '-1') {
-      // conversationIdInfo: {[appId1]: conversationId1, [appId2]: conversationId2}
-      const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {}
-      conversationIdInfo[appId] = id
-      globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo))
-    }
-  }
-
-  const getConversationIdFromStorage = (appId: string) => {
-    const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {}
-    const id = conversationIdInfo[appId]
-    return id
-  }
-
-  const isNewConversation = currConversationId === '-1'
-  // input can be updated by user
-  const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any> | null>(null)
-  const resetNewConversationInputs = () => {
-    if (!newConversationInputs)
-      return
-    setNewConversationInputs(produce(newConversationInputs, (draft) => {
-      Object.keys(draft).forEach((key) => {
-        draft[key] = ''
-      })
-    }))
-  }
-  const [existConversationInputs, setExistConversationInputs] = useState<Record<string, any> | null>(null)
-  const currInputs = isNewConversation ? newConversationInputs : existConversationInputs
-  const setCurrInputs = isNewConversation ? setNewConversationInputs : setExistConversationInputs
-
-  // info is muted
-  const [newConversationInfo, setNewConversationInfo] = useState<ConversationInfoType | null>(null)
-  const [existConversationInfo, setExistConversationInfo] = useState<ConversationInfoType | null>(null)
-  const currConversationInfo = isNewConversation ? newConversationInfo : existConversationInfo
-
-  return {
-    conversationList,
-    setConversationList,
-    pinnedConversationList,
-    setPinnedConversationList,
-    currConversationId,
-    getCurrConversationId,
-    setCurrConversationId,
-    getConversationIdFromStorage,
-    isNewConversation,
-    currInputs,
-    newConversationInputs,
-    existConversationInputs,
-    resetNewConversationInputs,
-    setCurrInputs,
-    currConversationInfo,
-    setNewConversationInfo,
-    setExistConversationInfo,
-  }
-}
-
-export default useConversation

+ 0 - 824
web/app/components/share/chatbot/index.tsx

@@ -1,824 +0,0 @@
-/* eslint-disable @typescript-eslint/no-use-before-define */
-'use client'
-import type { FC } from 'react'
-import React, { useEffect, useRef, useState } from 'react'
-import cn from 'classnames'
-import { useTranslation } from 'react-i18next'
-import { useContext } from 'use-context-selector'
-import produce, { setAutoFreeze } from 'immer'
-import { useBoolean, useGetState } from 'ahooks'
-import { checkOrSetAccessToken } from '../utils'
-import AppUnavailable from '../../base/app-unavailable'
-import useConversation from './hooks/use-conversation'
-import { ToastContext } from '@/app/components/base/toast'
-import ConfigScene from '@/app/components/share/chatbot/config-scence'
-import Header from '@/app/components/share/header'
-import {
-  fetchAppInfo,
-  fetchAppMeta,
-  fetchAppParams,
-  fetchChatList,
-  fetchConversations,
-  fetchSuggestedQuestions,
-  generationConversationName,
-  sendChatMessage,
-  stopChatMessageResponding,
-  updateFeedback,
-} from '@/service/share'
-import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
-import type { AppMeta, ConversationItem, SiteInfo } from '@/models/share'
-import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
-import type { Feedbacktype, IChatItem } from '@/app/components/app/chat/type'
-import Chat from '@/app/components/app/chat'
-import { changeLanguage } from '@/i18n/i18next-config'
-import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
-import Loading from '@/app/components/base/loading'
-import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
-import { userInputsFormToPromptVariables } from '@/utils/model-config'
-import type { InstalledApp } from '@/models/explore'
-import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
-import LogoHeader from '@/app/components/base/logo/logo-embeded-chat-header'
-import LogoAvatar from '@/app/components/base/logo/logo-embeded-chat-avatar'
-import type { VisionFile, VisionSettings } from '@/types/app'
-import { Resolution, TransferMethod } from '@/types/app'
-import type { Annotation as AnnotationType } from '@/models/log'
-
-export type IMainProps = {
-  isInstalledApp?: boolean
-  installedAppInfo?: InstalledApp
-}
-
-const Main: FC<IMainProps> = ({
-  isInstalledApp = false,
-  installedAppInfo,
-}) => {
-  const { t } = useTranslation()
-  const media = useBreakpoints()
-  const isMobile = media === MediaType.mobile
-
-  /*
-  * app info
-  */
-  const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
-  const [isUnknownReason, setIsUnknwonReason] = useState<boolean>(false)
-  const [appId, setAppId] = useState<string>('')
-  const [isPublicVersion, setIsPublicVersion] = useState<boolean>(true)
-  const [siteInfo, setSiteInfo] = useState<SiteInfo | null>()
-  const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null)
-  const [inited, setInited] = useState<boolean>(false)
-  const [plan, setPlan] = useState<string>('basic') // basic/plus/pro
-  const [canReplaceLogo, setCanReplaceLogo] = useState<boolean>(false)
-  const [customConfig, setCustomConfig] = useState<any>(null)
-  const [appMeta, setAppMeta] = useState<AppMeta | null>(null)
-
-  // Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
-  useEffect(() => {
-    if (siteInfo?.title) {
-      if (canReplaceLogo)
-        document.title = `${siteInfo.title}`
-      else
-        document.title = `${siteInfo.title} - Powered by Dify`
-    }
-  }, [siteInfo?.title, canReplaceLogo])
-
-  // onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
-  useEffect(() => {
-    setAutoFreeze(false)
-    return () => {
-      setAutoFreeze(true)
-    }
-  }, [])
-
-  /*
-  * conversation info
-  */
-  const [allConversationList, setAllConversationList] = useState<ConversationItem[]>([])
-  const [isClearConversationList, { setTrue: clearConversationListTrue, setFalse: clearConversationListFalse }] = useBoolean(false)
-  const [isClearPinnedConversationList, { setTrue: clearPinnedConversationListTrue, setFalse: clearPinnedConversationListFalse }] = useBoolean(false)
-  const {
-    conversationList,
-    setConversationList,
-    pinnedConversationList,
-    setPinnedConversationList,
-    currConversationId,
-    getCurrConversationId,
-    setCurrConversationId,
-    getConversationIdFromStorage,
-    isNewConversation,
-    currConversationInfo,
-    currInputs,
-    newConversationInputs,
-    // existConversationInputs,
-    resetNewConversationInputs,
-    setCurrInputs,
-    setNewConversationInfo,
-    setExistConversationInfo,
-  } = useConversation()
-  const [hasMore, setHasMore] = useState<boolean>(true)
-  const [hasPinnedMore, setHasPinnedMore] = useState<boolean>(true)
-
-  const onMoreLoaded = ({ data: conversations, has_more }: any) => {
-    setHasMore(has_more)
-    if (isClearConversationList) {
-      setConversationList(conversations)
-      clearConversationListFalse()
-    }
-    else {
-      setConversationList([...conversationList, ...conversations])
-    }
-  }
-
-  const onPinnedMoreLoaded = ({ data: conversations, has_more }: any) => {
-    setHasPinnedMore(has_more)
-    if (isClearPinnedConversationList) {
-      setPinnedConversationList(conversations)
-      clearPinnedConversationListFalse()
-    }
-    else {
-      setPinnedConversationList([...pinnedConversationList, ...conversations])
-    }
-  }
-
-  const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0)
-
-  const noticeUpdateList = () => {
-    setHasMore(true)
-    clearConversationListTrue()
-
-    setHasPinnedMore(true)
-    clearPinnedConversationListTrue()
-
-    setControlUpdateConversationList(Date.now())
-  }
-  const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
-  const [speechToTextConfig, setSpeechToTextConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
-  const [textToSpeechConfig, setTextToSpeechConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
-  const [citationConfig, setCitationConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
-
-  const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false)
-  const [isChatStarted, { setTrue: setChatStarted, setFalse: setChatNotStarted }] = useBoolean(false)
-  const handleStartChat = (inputs: Record<string, any>) => {
-    createNewChat()
-    setConversationIdChangeBecauseOfNew(true)
-    setCurrInputs(inputs)
-    setChatStarted()
-    // parse variables in introduction
-    setChatList(generateNewChatListWithOpenstatement('', inputs))
-  }
-  const hasSetInputs = (() => {
-    if (!isNewConversation)
-      return true
-
-    return isChatStarted
-  })()
-
-  // const conversationName = currConversationInfo?.name || t('share.chat.newChatDefaultName') as string
-  const conversationIntroduction = currConversationInfo?.introduction || ''
-
-  const handleConversationSwitch = () => {
-    if (!inited)
-      return
-    if (!appId) {
-      // wait for appId
-      setTimeout(handleConversationSwitch, 100)
-      return
-    }
-
-    // update inputs of current conversation
-    let notSyncToStateIntroduction = ''
-    let notSyncToStateInputs: Record<string, any> | undefined | null = {}
-    if (!isNewConversation) {
-      const item = allConversationList.find(item => item.id === currConversationId)
-      notSyncToStateInputs = item?.inputs || {}
-      setCurrInputs(notSyncToStateInputs)
-      notSyncToStateIntroduction = item?.introduction || ''
-      setExistConversationInfo({
-        name: item?.name || '',
-        introduction: notSyncToStateIntroduction,
-      })
-    }
-    else {
-      notSyncToStateInputs = newConversationInputs
-      setCurrInputs(notSyncToStateInputs)
-    }
-
-    // update chat list of current conversation
-    if (!isNewConversation && !conversationIdChangeBecauseOfNew && !isResponding) {
-      fetchChatList(currConversationId, isInstalledApp, installedAppInfo?.id).then((res: any) => {
-        const { data } = res
-        const newChatList: IChatItem[] = generateNewChatListWithOpenstatement(notSyncToStateIntroduction, notSyncToStateInputs)
-
-        data.forEach((item: any) => {
-          newChatList.push({
-            id: `question-${item.id}`,
-            content: item.query,
-            isAnswer: false,
-            message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [],
-          })
-          newChatList.push({
-            id: item.id,
-            content: item.answer,
-            agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files),
-            feedback: item.feedback,
-            isAnswer: true,
-            citation: item.retriever_resources,
-            message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
-          })
-        })
-        setChatList(newChatList)
-      })
-    }
-
-    if (isNewConversation && isChatStarted)
-      setChatList(generateNewChatListWithOpenstatement())
-
-    setControlFocus(Date.now())
-  }
-  useEffect(handleConversationSwitch, [currConversationId, inited])
-
-  /*
-  * chat info. chat is under conversation.
-  */
-  const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
-  const chatListDomRef = useRef<HTMLDivElement>(null)
-
-  useEffect(() => {
-    // scroll to bottom
-    if (chatListDomRef.current)
-      chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight
-  }, [chatList, currConversationId])
-  // user can not edit inputs if user had send message
-  const canEditInputs = !chatList.some(item => item.isAnswer === false) && isNewConversation
-  const createNewChat = async () => {
-    // if new chat is already exist, do not create new chat
-    abortController?.abort()
-    setRespondingFalse()
-    if (conversationList.some(item => item.id === '-1'))
-      return
-
-    setConversationList(produce(conversationList, (draft) => {
-      draft.unshift({
-        id: '-1',
-        name: t('share.chat.newChatDefaultName'),
-        inputs: newConversationInputs,
-        introduction: conversationIntroduction,
-      })
-    }))
-  }
-
-  // sometime introduction is not applied to state
-  const generateNewChatListWithOpenstatement = (introduction?: string, inputs?: Record<string, any> | null) => {
-    let caculatedIntroduction = introduction || conversationIntroduction || ''
-    const caculatedPromptVariables = inputs || currInputs || null
-    if (caculatedIntroduction && caculatedPromptVariables)
-      caculatedIntroduction = replaceStringWithValues(caculatedIntroduction, promptConfig?.prompt_variables || [], caculatedPromptVariables)
-
-    const openstatement = {
-      id: `${Date.now()}`,
-      content: caculatedIntroduction,
-      isAnswer: true,
-      feedbackDisabled: true,
-      isOpeningStatement: isPublicVersion,
-    }
-    if (caculatedIntroduction)
-      return [openstatement]
-
-    return []
-  }
-
-  const fetchAllConversations = () => {
-    return fetchConversations(isInstalledApp, installedAppInfo?.id, undefined, undefined, 100)
-  }
-
-  const fetchInitData = async () => {
-    if (!isInstalledApp)
-      await checkOrSetAccessToken()
-
-    return Promise.all([isInstalledApp
-      ? {
-        app_id: installedAppInfo?.id,
-        site: {
-          title: installedAppInfo?.app.name,
-          prompt_public: false,
-          copyright: '',
-        },
-        plan: 'basic',
-      }
-      : fetchAppInfo(), fetchAllConversations(), fetchAppParams(isInstalledApp, installedAppInfo?.id), fetchAppMeta(isInstalledApp, installedAppInfo?.id)])
-  }
-
-  // init
-  useEffect(() => {
-    (async () => {
-      try {
-        const [appData, conversationData, appParams, appMeta]: any = await fetchInitData()
-        setAppMeta(appMeta)
-        const { app_id: appId, site: siteInfo, plan, can_replace_logo, custom_config }: any = appData
-        setAppId(appId)
-        setPlan(plan)
-        setCanReplaceLogo(can_replace_logo)
-        setCustomConfig(custom_config)
-        const tempIsPublicVersion = siteInfo.prompt_public
-        setIsPublicVersion(tempIsPublicVersion)
-        const prompt_template = ''
-        // handle current conversation id
-        const { data: allConversations } = conversationData as { data: ConversationItem[]; has_more: boolean }
-        const _conversationId = getConversationIdFromStorage(appId)
-        const isNotNewConversation = allConversations.some(item => item.id === _conversationId)
-        setAllConversationList(allConversations)
-        // fetch new conversation info
-        const { user_input_form, opening_statement: introduction, suggested_questions_after_answer, speech_to_text, text_to_speech, retriever_resource, file_upload, sensitive_word_avoidance }: any = appParams
-        setVisionConfig({
-          ...file_upload.image,
-          image_file_size_limit: appParams?.system_parameters?.image_file_size_limit,
-        })
-        const prompt_variables = userInputsFormToPromptVariables(user_input_form)
-        if (siteInfo.default_language)
-          changeLanguage(siteInfo.default_language)
-
-        setNewConversationInfo({
-          name: t('share.chat.newChatDefaultName'),
-          introduction,
-        })
-        setSiteInfo(siteInfo as SiteInfo)
-        setPromptConfig({
-          prompt_template,
-          prompt_variables,
-        } as PromptConfig)
-        setSuggestedQuestionsAfterAnswerConfig(suggested_questions_after_answer)
-        setSpeechToTextConfig(speech_to_text)
-        setTextToSpeechConfig(text_to_speech)
-        setCitationConfig(retriever_resource)
-
-        // setConversationList(conversations as ConversationItem[])
-
-        if (isNotNewConversation)
-          setCurrConversationId(_conversationId, appId, false)
-
-        setInited(true)
-      }
-      catch (e: any) {
-        if (e.status === 404) {
-          setAppUnavailable(true)
-        }
-        else {
-          setIsUnknwonReason(true)
-          setAppUnavailable(true)
-        }
-      }
-    })()
-  }, [])
-
-  const [isResponding, { setTrue: setRespondingTrue, setFalse: setRespondingFalse }] = useBoolean(false)
-  const [abortController, setAbortController] = useState<AbortController | null>(null)
-  const { notify } = useContext(ToastContext)
-  const logError = (message: string) => {
-    notify({ type: 'error', message })
-  }
-
-  const checkCanSend = () => {
-    if (currConversationId !== '-1')
-      return true
-
-    const prompt_variables = promptConfig?.prompt_variables
-    const inputs = currInputs
-    if (!inputs || !prompt_variables || prompt_variables?.length === 0)
-      return true
-
-    let hasEmptyInput = ''
-    const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
-      const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
-      return res
-    }) || [] // compatible with old version
-    requiredVars.forEach(({ key, name }) => {
-      if (hasEmptyInput)
-        return
-
-      if (!inputs?.[key])
-        hasEmptyInput = name
-    })
-
-    if (hasEmptyInput) {
-      logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
-      return false
-    }
-    return !hasEmptyInput
-  }
-
-  const [controlFocus, setControlFocus] = useState(0)
-  const [isShowSuggestion, setIsShowSuggestion] = useState(false)
-  const doShowSuggestion = isShowSuggestion && !isResponding
-  const [suggestQuestions, setSuggestQuestions] = useState<string[]>([])
-  const [messageTaskId, setMessageTaskId] = useState('')
-  const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
-  const [isRespondingConIsCurrCon, setIsRespondingConCurrCon, getIsRespondingConIsCurrCon] = useGetState(true)
-  const [shouldReload, setShouldReload] = useState(false)
-  const [userQuery, setUserQuery] = useState('')
-  const [visionConfig, setVisionConfig] = useState<VisionSettings>({
-    enabled: false,
-    number_limits: 2,
-    detail: Resolution.low,
-    transfer_methods: [TransferMethod.local_file],
-  })
-
-  const updateCurrentQA = ({
-    responseItem,
-    questionId,
-    placeholderAnswerId,
-    questionItem,
-  }: {
-    responseItem: IChatItem
-    questionId: string
-    placeholderAnswerId: string
-    questionItem: IChatItem
-  }) => {
-    // closesure new list is outdated.
-    const newListWithAnswer = produce(
-      getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
-      (draft) => {
-        if (!draft.find(item => item.id === questionId))
-          draft.push({ ...questionItem })
-
-        draft.push({ ...responseItem })
-      })
-    setChatList(newListWithAnswer)
-  }
-
-  const handleSend = async (message: string, files?: VisionFile[]) => {
-    if (isResponding) {
-      notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
-      return
-    }
-
-    if (files?.find(item => item.transfer_method === TransferMethod.local_file && !item.upload_file_id)) {
-      notify({ type: 'info', message: t('appDebug.errorMessage.waitForImgUpload') })
-      return false
-    }
-    const data: Record<string, any> = {
-      inputs: currInputs,
-      query: message,
-      conversation_id: isNewConversation ? null : currConversationId,
-    }
-
-    if (visionConfig.enabled && files && files?.length > 0) {
-      data.files = files.map((item) => {
-        if (item.transfer_method === TransferMethod.local_file) {
-          return {
-            ...item,
-            url: '',
-          }
-        }
-        return item
-      })
-    }
-
-    // qustion
-    const questionId = `question-${Date.now()}`
-    const questionItem = {
-      id: questionId,
-      content: message,
-      isAnswer: false,
-      message_files: files,
-    }
-
-    const placeholderAnswerId = `answer-placeholder-${Date.now()}`
-    const placeholderAnswerItem = {
-      id: placeholderAnswerId,
-      content: '',
-      isAnswer: true,
-    }
-
-    const newList = [...getChatList(), questionItem, placeholderAnswerItem]
-    setChatList(newList)
-
-    let isAgentMode = false
-
-    // answer
-    const responseItem: IChatItem = {
-      id: `${Date.now()}`,
-      content: '',
-      agent_thoughts: [],
-      message_files: [],
-      isAnswer: true,
-    }
-    let hasSetResponseId = false
-
-    const prevTempNewConversationId = getCurrConversationId() || '-1'
-    let tempNewConversationId = prevTempNewConversationId
-
-    setHasStopResponded(false)
-    setRespondingTrue()
-    setIsShowSuggestion(false)
-    sendChatMessage(data, {
-      getAbortController: (abortController) => {
-        setAbortController(abortController)
-      },
-      onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
-        if (!isAgentMode) {
-          responseItem.content = responseItem.content + message
-        }
-        else {
-          const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
-          if (lastThought)
-            lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
-        }
-        if (messageId && !hasSetResponseId) {
-          responseItem.id = messageId
-          hasSetResponseId = true
-        }
-
-        if (isFirstMessage && newConversationId)
-          tempNewConversationId = newConversationId
-
-        setMessageTaskId(taskId)
-        // has switched to other conversation
-        if (prevTempNewConversationId !== getCurrConversationId()) {
-          setIsRespondingConCurrCon(false)
-          return
-        }
-        updateCurrentQA({
-          responseItem,
-          questionId,
-          placeholderAnswerId,
-          questionItem,
-        })
-      },
-      async onCompleted(hasError?: boolean) {
-        if (hasError)
-          return
-
-        if (getConversationIdChangeBecauseOfNew()) {
-          const { data: allConversations }: any = await fetchAllConversations()
-          const newItem: any = await generationConversationName(isInstalledApp, installedAppInfo?.id, allConversations[0].id)
-          const newAllConversations = produce(allConversations, (draft: any) => {
-            draft[0].name = newItem.name
-          })
-          setAllConversationList(newAllConversations as any)
-          noticeUpdateList()
-        }
-        setConversationIdChangeBecauseOfNew(false)
-        resetNewConversationInputs()
-        setChatNotStarted()
-        setCurrConversationId(tempNewConversationId, appId, true)
-        if (suggestedQuestionsAfterAnswerConfig?.enabled && !getHasStopResponded()) {
-          const { data }: any = await fetchSuggestedQuestions(responseItem.id, isInstalledApp, installedAppInfo?.id)
-          setSuggestQuestions(data)
-          setIsShowSuggestion(true)
-        }
-        setRespondingFalse()
-      },
-      onFile(file) {
-        const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
-        if (lastThought)
-          lastThought.message_files = [...(lastThought as any).message_files, { ...file }]
-
-        updateCurrentQA({
-          responseItem,
-          questionId,
-          placeholderAnswerId,
-          questionItem,
-        })
-      },
-      onThought(thought) {
-        isAgentMode = true
-        const response = responseItem as any
-        if (thought.message_id && !hasSetResponseId) {
-          response.id = thought.message_id
-          hasSetResponseId = true
-        }
-        // responseItem.id = thought.message_id;
-        if (response.agent_thoughts.length === 0) {
-          response.agent_thoughts.push(thought)
-        }
-        else {
-          const lastThought = response.agent_thoughts[response.agent_thoughts.length - 1]
-          // thought changed but still the same thought, so update.
-          if (lastThought.id === thought.id) {
-            thought.thought = lastThought.thought
-            thought.message_files = lastThought.message_files
-            responseItem.agent_thoughts![response.agent_thoughts.length - 1] = thought
-          }
-          else {
-            responseItem.agent_thoughts!.push(thought)
-          }
-        }
-        // has switched to other conversation
-        if (prevTempNewConversationId !== getCurrConversationId()) {
-          setIsRespondingConCurrCon(false)
-          return false
-        }
-
-        updateCurrentQA({
-          responseItem,
-          questionId,
-          placeholderAnswerId,
-          questionItem,
-        })
-      },
-      onMessageEnd: (messageEnd) => {
-        if (messageEnd.metadata?.annotation_reply) {
-          responseItem.id = messageEnd.id
-          responseItem.annotation = ({
-            id: messageEnd.metadata.annotation_reply.id,
-            authorName: messageEnd.metadata.annotation_reply.account.name,
-          } as AnnotationType)
-          const newListWithAnswer = produce(
-            getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
-            (draft) => {
-              if (!draft.find(item => item.id === questionId))
-                draft.push({ ...questionItem })
-
-              draft.push({
-                ...responseItem,
-              })
-            })
-          setChatList(newListWithAnswer)
-          return
-        }
-        // not support show citation
-        // responseItem.citation = messageEnd.retriever_resources
-        if (!isInstalledApp)
-          return
-        const newListWithAnswer = produce(
-          getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
-          (draft) => {
-            if (!draft.find(item => item.id === questionId))
-              draft.push({ ...questionItem })
-
-            draft.push({ ...responseItem })
-          })
-        setChatList(newListWithAnswer)
-      },
-      onMessageReplace: (messageReplace) => {
-        if (isInstalledApp) {
-          responseItem.content = messageReplace.answer
-        }
-        else {
-          setChatList(produce(
-            getChatList(),
-            (draft) => {
-              const current = draft.find(item => item.id === messageReplace.id)
-
-              if (current)
-                current.content = messageReplace.answer
-            },
-          ))
-        }
-      },
-      onError() {
-        setRespondingFalse()
-        // role back placeholder answer
-        setChatList(produce(getChatList(), (draft) => {
-          draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1)
-        }))
-      },
-    }, isInstalledApp, installedAppInfo?.id)
-  }
-
-  const handleFeedback = async (messageId: string, feedback: Feedbacktype) => {
-    await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, installedAppInfo?.id)
-    const newChatList = chatList.map((item) => {
-      if (item.id === messageId) {
-        return {
-          ...item,
-          feedback,
-        }
-      }
-      return item
-    })
-    setChatList(newChatList)
-    notify({ type: 'success', message: t('common.api.success') })
-  }
-
-  const handleReload = () => {
-    setCurrConversationId('-1', appId, false)
-    setChatNotStarted()
-    setShouldReload(false)
-    createNewChat()
-  }
-
-  const handleConversationIdChange = (id: string) => {
-    if (id === '-1') {
-      createNewChat()
-      setConversationIdChangeBecauseOfNew(true)
-    }
-    else {
-      setConversationIdChangeBecauseOfNew(false)
-    }
-    // trigger handleConversationSwitch
-    setCurrConversationId(id, appId)
-    setIsShowSuggestion(false)
-  }
-
-  const difyIcon = (
-    <LogoHeader />
-  )
-
-  if (appUnavailable)
-    return <AppUnavailable isUnknownReason={isUnknownReason} />
-
-  if (!appId || !siteInfo || !promptConfig) {
-    return <div className='flex h-screen w-full'>
-      <Loading type='app' />
-    </div>
-  }
-
-  return (
-    <div>
-      <Header
-        title={siteInfo.title}
-        icon=''
-        customerIcon={difyIcon}
-        icon_background={siteInfo.icon_background || ''}
-        isEmbedScene={true}
-        isMobile={isMobile}
-        onCreateNewChat={() => handleConversationIdChange('-1')}
-      />
-
-      <div className={'flex bg-white overflow-hidden'}>
-        <div className={cn(
-          isInstalledApp ? 'h-full' : 'h-[calc(100vh_-_3rem)]',
-          'flex-grow flex flex-col overflow-y-auto',
-        )
-        }>
-          <ConfigScene
-            // conversationName={conversationName}
-            hasSetInputs={hasSetInputs}
-            isPublicVersion={isPublicVersion}
-            siteInfo={siteInfo}
-            promptConfig={promptConfig}
-            onStartChat={handleStartChat}
-            canEditInputs={canEditInputs}
-            savedInputs={currInputs as Record<string, any>}
-            onInputsChange={setCurrInputs}
-            plan={plan}
-            canReplaceLogo={canReplaceLogo}
-            customConfig={customConfig}
-          ></ConfigScene>
-          {
-            shouldReload && (
-              <div className='flex items-center justify-between mb-5 px-4 py-2 bg-[#FEF0C7]'>
-                <div className='flex items-center text-xs font-medium text-[#DC6803]'>
-                  <AlertTriangle className='mr-2 w-4 h-4' />
-                  {t('share.chat.temporarySystemIssue')}
-                </div>
-                <div
-                  className='flex items-center px-3 h-7 bg-white shadow-xs rounded-md text-xs font-medium text-gray-700 cursor-pointer'
-                  onClick={handleReload}
-                >
-                  {t('share.chat.tryToSolve')}
-                </div>
-              </div>
-            )
-          }
-          {
-            hasSetInputs && (
-              <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponding ? 'pb-[113px]' : 'pb-[76px]'), 'relative grow h-[200px] pc:w-[794px] max-w-full mobile:w-full mx-auto mb-3.5 overflow-hidden')}>
-                <div className='h-full overflow-y-auto' ref={chatListDomRef}>
-                  <Chat
-                    chatList={chatList}
-                    query={userQuery}
-                    onQueryChange={setUserQuery}
-                    onSend={handleSend}
-                    isHideFeedbackEdit
-                    onFeedback={handleFeedback}
-                    isResponding={isResponding}
-                    canStopResponding={!!messageTaskId && isRespondingConIsCurrCon}
-                    abortResponding={async () => {
-                      await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id)
-                      setHasStopResponded(true)
-                      setRespondingFalse()
-                    }}
-                    checkCanSend={checkCanSend}
-                    controlFocus={controlFocus}
-                    isShowSuggestion={doShowSuggestion}
-                    suggestionList={suggestQuestions}
-                    displayScene='web'
-                    isShowSpeechToText={speechToTextConfig?.enabled}
-                    isShowTextToSpeech={textToSpeechConfig?.enabled}
-                    isShowCitation={citationConfig?.enabled && isInstalledApp}
-                    answerIcon={<LogoAvatar className='relative shrink-0' />}
-                    visionConfig={visionConfig}
-                    allToolIcons={appMeta?.tool_icons || {}}
-                    customDisclaimer={siteInfo.custom_disclaimer}
-                  />
-                </div>
-              </div>)
-          }
-
-          {/* {isShowConfirm && (
-            <Confirm
-              title={t('share.chat.deleteConversation.title')}
-              content={t('share.chat.deleteConversation.content')}
-              isShow={isShowConfirm}
-              onClose={hideConfirm}
-              onConfirm={didDelete}
-              onCancel={hideConfirm}
-            />
-          )} */}
-        </div>
-      </div>
-    </div>
-  )
-}
-export default React.memo(Main)

+ 0 - 28
web/app/components/share/chatbot/sidebar/app-info/index.tsx

@@ -1,28 +0,0 @@
-'use client'
-import type { FC } from 'react'
-import React from 'react'
-import cn from 'classnames'
-import { appDefaultIconBackground } from '@/config/index'
-import AppIcon from '@/app/components/base/app-icon'
-
-export type IAppInfoProps = {
-  className?: string
-  icon: string
-  icon_background?: string
-  name: string
-}
-
-const AppInfo: FC<IAppInfoProps> = ({
-  className,
-  icon,
-  icon_background,
-  name,
-}) => {
-  return (
-    <div className={cn(className, 'flex items-center space-x-3')}>
-      <AppIcon size="small" icon={icon} background={icon_background || appDefaultIconBackground} />
-      <div className='w-0 grow text-sm font-semibold text-gray-800 overflow-hidden  text-ellipsis whitespace-nowrap'>{name}</div>
-    </div>
-  )
-}
-export default React.memo(AppInfo)

+ 0 - 3
web/app/components/share/chatbot/sidebar/card.module.css

@@ -1,3 +0,0 @@
-.card:hover {
-  background: linear-gradient(0deg, rgba(235, 245, 255, 0.4), rgba(235, 245, 255, 0.4)), #FFFFFF;
-}

+ 0 - 19
web/app/components/share/chatbot/sidebar/card.tsx

@@ -1,19 +0,0 @@
-import React from 'react'
-import { useTranslation } from 'react-i18next'
-import s from './card.module.css'
-
-type PropType = {
-  children: React.ReactNode
-  text?: string
-}
-function Card({ children, text }: PropType) {
-  const { t } = useTranslation()
-  return (
-    <div className={`${s.card} box-border w-full flex flex-col items-start px-4 py-3 rounded-lg border-solid border border-gray-200  cursor-pointer hover:border-primary-300`}>
-      <div className='text-gray-400 font-medium text-xs mb-2'>{text ?? t('share.chat.powerBy')}</div>
-      {children}
-    </div>
-  )
-}
-
-export default Card

+ 0 - 152
web/app/components/share/chatbot/sidebar/index.tsx

@@ -1,152 +0,0 @@
-import React, { useEffect, useState } from 'react'
-import type { FC } from 'react'
-import { useTranslation } from 'react-i18next'
-import {
-  PencilSquareIcon,
-} from '@heroicons/react/24/outline'
-import cn from 'classnames'
-import Button from '../../../base/button'
-import List from './list'
-import AppInfo from '@/app/components/share/chat/sidebar/app-info'
-// import Card from './card'
-import type { ConversationItem, SiteInfo } from '@/models/share'
-import { fetchConversations } from '@/service/share'
-
-export type ISidebarProps = {
-  copyRight: string
-  currentId: string
-  onCurrentIdChange: (id: string) => void
-  list: ConversationItem[]
-  isClearConversationList: boolean
-  pinnedList: ConversationItem[]
-  isClearPinnedConversationList: boolean
-  isInstalledApp: boolean
-  installedAppId?: string
-  siteInfo: SiteInfo
-  onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
-  onPinnedMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
-  isNoMore: boolean
-  isPinnedNoMore: boolean
-  onPin: (id: string) => void
-  onUnpin: (id: string) => void
-  controlUpdateList: number
-  onDelete: (id: string) => void
-}
-
-const Sidebar: FC<ISidebarProps> = ({
-  copyRight,
-  currentId,
-  onCurrentIdChange,
-  list,
-  isClearConversationList,
-  pinnedList,
-  isClearPinnedConversationList,
-  isInstalledApp,
-  installedAppId,
-  siteInfo,
-  onMoreLoaded,
-  onPinnedMoreLoaded,
-  isNoMore,
-  isPinnedNoMore,
-  onPin,
-  onUnpin,
-  controlUpdateList,
-  onDelete,
-}) => {
-  const { t } = useTranslation()
-  const [hasPinned, setHasPinned] = useState(false)
-
-  const checkHasPinned = async () => {
-    const { data }: any = await fetchConversations(isInstalledApp, installedAppId, undefined, true)
-    setHasPinned(data.length > 0)
-  }
-
-  useEffect(() => {
-    checkHasPinned()
-  }, [])
-
-  useEffect(() => {
-    if (controlUpdateList !== 0)
-      checkHasPinned()
-  }, [controlUpdateList])
-
-  const maxListHeight = isInstalledApp ? 'max-h-[30vh]' : 'max-h-[40vh]'
-
-  return (
-    <div
-      className={
-        cn(
-          isInstalledApp ? 'tablet:h-[calc(100vh_-_74px)]' : 'tablet:h-[calc(100vh_-_3rem)]',
-          'shrink-0 flex flex-col bg-white pc:w-[244px] tablet:w-[192px] mobile:w-[240px]  border-r border-gray-200 mobile:h-screen',
-        )
-      }
-    >
-      {isInstalledApp && (
-        <AppInfo
-          className='my-4 px-4'
-          name={siteInfo.title || ''}
-          icon={siteInfo.icon || ''}
-          icon_background={siteInfo.icon_background}
-        />
-      )}
-      <div className="flex flex-shrink-0 p-4 !pb-0">
-        <Button
-          onClick={() => { onCurrentIdChange('-1') }}
-          variant='secondary-accent'
-          className="group w-full flex-shrink-0">
-          <PencilSquareIcon className="mr-2 h-4 w-4" /> {t('share.chat.newChat')}
-        </Button>
-      </div>
-      <div className={'flex-grow flex flex-col h-0 overflow-y-auto overflow-x-hidden'}>
-        {/* pinned list */}
-        {hasPinned && (
-          <div className={cn('mt-4 px-4', list.length === 0 && 'flex flex-col flex-grow')}>
-            <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.pinnedTitle')}</div>
-            <List
-              className={cn(list.length > 0 ? maxListHeight : 'flex-grow')}
-              currentId={currentId}
-              onCurrentIdChange={onCurrentIdChange}
-              list={pinnedList}
-              isClearConversationList={isClearPinnedConversationList}
-              isInstalledApp={isInstalledApp}
-              installedAppId={installedAppId}
-              onMoreLoaded={onPinnedMoreLoaded}
-              isNoMore={isPinnedNoMore}
-              isPinned={true}
-              onPinChanged={id => onUnpin(id)}
-              controlUpdate={controlUpdateList + 1}
-              onDelete={onDelete}
-            />
-          </div>
-        )}
-        {/* unpinned list */}
-        <div className={cn('mt-4 px-4', !hasPinned && 'flex flex-col flex-grow')}>
-          {(hasPinned && list.length > 0) && (
-            <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.unpinnedTitle')}</div>
-          )}
-          <List
-            className={cn(hasPinned ? maxListHeight : 'flex-grow')}
-            currentId={currentId}
-            onCurrentIdChange={onCurrentIdChange}
-            list={list}
-            isClearConversationList={isClearConversationList}
-            isInstalledApp={isInstalledApp}
-            installedAppId={installedAppId}
-            onMoreLoaded={onMoreLoaded}
-            isNoMore={isNoMore}
-            isPinned={false}
-            onPinChanged={id => onPin(id)}
-            controlUpdate={controlUpdateList + 1}
-            onDelete={onDelete}
-          />
-        </div>
-
-      </div>
-      <div className="flex flex-shrink-0 pr-4 pb-4 pl-4">
-        <div className="text-gray-400 font-normal text-xs">© {copyRight} {(new Date()).getFullYear()}</div>
-      </div>
-    </div>
-  )
-}
-
-export default React.memo(Sidebar)

+ 0 - 115
web/app/components/share/chatbot/sidebar/list/index.tsx

@@ -1,115 +0,0 @@
-'use client'
-import type { FC } from 'react'
-import React, { useRef } from 'react'
-import {
-  ChatBubbleOvalLeftEllipsisIcon,
-} from '@heroicons/react/24/outline'
-import { useInfiniteScroll } from 'ahooks'
-import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon } from '@heroicons/react/24/solid'
-import cn from 'classnames'
-import s from './style.module.css'
-import type { ConversationItem } from '@/models/share'
-import { fetchConversations } from '@/service/share'
-import ItemOperation from '@/app/components/explore/item-operation'
-
-export type IListProps = {
-  className: string
-  currentId: string
-  onCurrentIdChange: (id: string) => void
-  list: ConversationItem[]
-  isClearConversationList: boolean
-  isInstalledApp: boolean
-  installedAppId?: string
-  onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
-  isNoMore: boolean
-  isPinned: boolean
-  onPinChanged: (id: string) => void
-  controlUpdate: number
-  onDelete: (id: string) => void
-}
-
-const List: FC<IListProps> = ({
-  className,
-  currentId,
-  onCurrentIdChange,
-  list,
-  isClearConversationList,
-  isInstalledApp,
-  installedAppId,
-  onMoreLoaded,
-  isNoMore,
-  isPinned,
-  onPinChanged,
-  controlUpdate,
-  onDelete,
-}) => {
-  const listRef = useRef<HTMLDivElement>(null)
-
-  useInfiniteScroll(
-    async () => {
-      if (!isNoMore) {
-        const lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined
-        const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned)
-        onMoreLoaded({ data: conversations, has_more })
-      }
-      return { list: [] }
-    },
-    {
-      target: listRef,
-      isNoMore: () => {
-        return isNoMore
-      },
-      reloadDeps: [isNoMore, controlUpdate],
-    },
-  )
-  return (
-    <nav
-      ref={listRef}
-      className={cn(className, 'shrink-0 space-y-1 bg-white overflow-y-auto')}
-    >
-      {list.map((item) => {
-        const isCurrent = item.id === currentId
-        const ItemIcon
-            = isCurrent ? ChatBubbleOvalLeftEllipsisSolidIcon : ChatBubbleOvalLeftEllipsisIcon
-        return (
-          <div
-            onClick={() => onCurrentIdChange(item.id)}
-            key={item.id}
-            className={cn(s.item,
-              isCurrent
-                ? 'bg-primary-50 text-primary-600'
-                : 'text-gray-700 hover:bg-gray-200 hover:text-gray-700',
-              'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer',
-            )}
-          >
-            <div className='flex items-center w-0 grow'>
-              <ItemIcon
-                className={cn(
-                  isCurrent
-                    ? 'text-primary-600'
-                    : 'text-gray-400 group-hover:text-gray-500',
-                  'mr-3 h-5 w-5 flex-shrink-0',
-                )}
-                aria-hidden="true"
-              />
-              <span>{item.name}</span>
-            </div>
-
-            {item.id !== '-1' && (
-              <div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}>
-                <ItemOperation
-                  isPinned={isPinned}
-                  togglePin={() => onPinChanged(item.id)}
-                  isShowDelete
-                  onDelete={() => onDelete(item.id)}
-                />
-              </div>
-            )}
-          </div>
-        )
-      })}
-    </nav>
-  )
-}
-
-export default React.memo(List)

+ 0 - 7
web/app/components/share/chatbot/sidebar/list/style.module.css

@@ -1,7 +0,0 @@
-.opBtn {
-  visibility: hidden;
-}
-
-.item:hover .opBtn {
-  visibility: visible;
-}

+ 0 - 77
web/app/components/share/chatbot/value-panel/index.tsx

@@ -1,77 +0,0 @@
-'use client'
-import type { FC, ReactNode } from 'react'
-import React from 'react'
-import cn from 'classnames'
-import { useTranslation } from 'react-i18next'
-import s from './style.module.css'
-import { StarIcon } from '@/app/components/share/chatbot/welcome/massive-component'
-import Button from '@/app/components/base/button'
-
-export type ITemplateVarPanelProps = {
-  className?: string
-  header: ReactNode
-  children?: ReactNode | null
-  isFold: boolean
-}
-
-const TemplateVarPanel: FC<ITemplateVarPanelProps> = ({
-  className,
-  header,
-  children,
-  isFold,
-}) => {
-  return (
-    <div className={cn(isFold ? 'border border-indigo-100' : s.boxShodow, className, 'rounded-xl ')}>
-      {/* header */}
-      <div
-        className={cn(isFold && 'rounded-b-xl', 'rounded-t-xl px-6 py-4 bg-indigo-25 text-xs')}
-      >
-        {header}
-      </div>
-      {/* body */}
-      {!isFold && children && (
-        <div className='rounded-b-xl p-6'>
-          {children}
-        </div>
-      )}
-    </div>
-  )
-}
-
-export const PanelTitle: FC<{ title: string; className?: string }> = ({
-  title,
-  className,
-}) => {
-  return (
-    <div className={cn(className, 'flex items-center space-x-1 text-indigo-600')}>
-      <StarIcon />
-      <span className='text-xs'>{title}</span>
-    </div>
-  )
-}
-
-export const VarOpBtnGroup: FC<{ className?: string; onConfirm: () => void; onCancel: () => void }> = ({
-  className,
-  onConfirm,
-  onCancel,
-}) => {
-  const { t } = useTranslation()
-
-  return (
-    <div className={cn(className, 'flex mt-3 space-x-2 mobile:ml-0 tablet:ml-[128px] text-sm')}>
-      <Button
-        variant='primary'
-        onClick={onConfirm}
-      >
-        {t('common.operation.save')}
-      </Button>
-      <Button
-        onClick={onCancel}
-      >
-        {t('common.operation.cancel')}
-      </Button>
-    </div >
-  )
-}
-
-export default React.memo(TemplateVarPanel)

+ 0 - 3
web/app/components/share/chatbot/value-panel/style.module.css

@@ -1,3 +0,0 @@
-.boxShodow {
-  box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
-}

+ 0 - 389
web/app/components/share/chatbot/welcome/index.tsx

@@ -1,389 +0,0 @@
-'use client'
-import type { FC } from 'react'
-import React, { useEffect, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { useContext } from 'use-context-selector'
-import TemplateVarPanel, { PanelTitle, VarOpBtnGroup } from '../value-panel'
-import s from './style.module.css'
-import { AppInfo, ChatBtn, EditBtn, FootLogo, PromptTemplate } from './massive-component'
-import type { SiteInfo } from '@/models/share'
-import type { PromptConfig } from '@/models/debug'
-import { ToastContext } from '@/app/components/base/toast'
-import Select from '@/app/components/base/select'
-import { DEFAULT_VALUE_MAX_LEN } from '@/config'
-
-// regex to match the {{}} and replace it with a span
-const regex = /\{\{([^}]+)\}\}/g
-
-export type IWelcomeProps = {
-  // conversationName: string
-  hasSetInputs: boolean
-  isPublicVersion: boolean
-  siteInfo: SiteInfo
-  promptConfig: PromptConfig
-  onStartChat: (inputs: Record<string, any>) => void
-  canEditInputs: boolean
-  savedInputs: Record<string, any>
-  onInputsChange: (inputs: Record<string, any>) => void
-  plan: string
-  canReplaceLogo?: boolean
-  customConfig?: {
-    remove_webapp_brand?: boolean
-    replace_webapp_logo?: string
-  }
-}
-
-const Welcome: FC<IWelcomeProps> = ({
-  // conversationName,
-  hasSetInputs,
-  isPublicVersion,
-  siteInfo,
-  promptConfig,
-  onStartChat,
-  canEditInputs,
-  savedInputs,
-  onInputsChange,
-  customConfig,
-}) => {
-  const { t } = useTranslation()
-  const hasVar = promptConfig.prompt_variables.length > 0
-  const [isFold, setIsFold] = useState<boolean>(true)
-  const [inputs, setInputs] = useState<Record<string, any>>((() => {
-    if (hasSetInputs)
-      return savedInputs
-
-    const res: Record<string, any> = {}
-    if (promptConfig) {
-      promptConfig.prompt_variables.forEach((item) => {
-        res[item.key] = ''
-      })
-    }
-    // debugger
-    return res
-  })())
-  useEffect(() => {
-    if (!savedInputs) {
-      const res: Record<string, any> = {}
-      if (promptConfig) {
-        promptConfig.prompt_variables.forEach((item) => {
-          res[item.key] = ''
-        })
-      }
-      setInputs(res)
-    }
-    else {
-      setInputs(savedInputs)
-    }
-  }, [savedInputs])
-
-  const highLightPromoptTemplate = (() => {
-    if (!promptConfig)
-      return ''
-    const res = promptConfig.prompt_template.replace(regex, (match, p1) => {
-      return `<span class='text-gray-800 font-bold'>${inputs?.[p1] ? inputs?.[p1] : match}</span>`
-    })
-    return res
-  })()
-
-  const { notify } = useContext(ToastContext)
-  const logError = (message: string) => {
-    notify({ type: 'error', message, duration: 3000 })
-  }
-
-  // const renderHeader = () => {
-  //   return (
-  //     <div className='absolute top-0 left-0 right-0 flex items-center justify-between border-b border-gray-100 mobile:h-12 tablet:h-16 px-8 bg-white'>
-  //       <div className='text-gray-900'>{conversationName}</div>
-  //     </div>
-  //   )
-  // }
-
-  const renderInputs = () => {
-    return (
-      <div className='space-y-3'>
-        {promptConfig.prompt_variables.map(item => (
-          <div className='tablet:flex items-start  mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}>
-            <label className={`flex-shrink-0 flex items-center tablet:leading-9 mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label>
-            {item.type === 'select'
-              && (
-                <Select
-                  className='w-full'
-                  defaultValue={inputs?.[item.key]}
-                  onSelect={(i) => { setInputs({ ...inputs, [item.key]: i.value }) }}
-                  items={(item.options || []).map(i => ({ name: i, value: i }))}
-                  allowSearch={false}
-                  bgClassName='bg-gray-50'
-                />
-              )
-            }
-            {item.type === 'string' && (
-              <input
-                placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
-                value={inputs?.[item.key] || ''}
-                onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
-                className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'}
-                maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN}
-              />
-            )}
-            {item.type === 'paragraph' && (
-              <textarea
-                className="w-full h-[104px] flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50"
-                placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
-                value={inputs?.[item.key] || ''}
-                onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
-              />
-            )}
-            {item.type === 'number' && (
-              <input
-                type='number'
-                placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
-                value={inputs?.[item.key] || ''}
-                onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
-                className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'}
-              />
-            )}
-          </div>
-        ))}
-      </div>
-    )
-  }
-
-  const canChat = () => {
-    const prompt_variables = promptConfig?.prompt_variables
-    if (!inputs || !prompt_variables || prompt_variables?.length === 0)
-      return true
-
-    let hasEmptyInput = ''
-    const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
-      const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
-      return res
-    }) || [] // compatible with old version
-    requiredVars.forEach(({ key, name }) => {
-      if (hasEmptyInput)
-        return
-
-      if (!inputs?.[key])
-        hasEmptyInput = name
-    })
-
-    if (hasEmptyInput) {
-      logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
-      return false
-    }
-    return !hasEmptyInput
-  }
-
-  const handleChat = () => {
-    if (!canChat())
-      return
-
-    onStartChat(inputs)
-  }
-
-  const renderNoVarPanel = () => {
-    if (isPublicVersion) {
-      return (
-        <div>
-          <AppInfo siteInfo={siteInfo} />
-          <TemplateVarPanel
-            isFold={false}
-            header={
-              <>
-                <PanelTitle
-                  title={t('share.chat.publicPromptConfigTitle')}
-                  className='mb-1'
-                />
-                <PromptTemplate html={highLightPromoptTemplate} />
-              </>
-            }
-          >
-            <ChatBtn onClick={handleChat} />
-          </TemplateVarPanel>
-        </div>
-      )
-    }
-    // private version
-    return (
-      <TemplateVarPanel
-        isFold={false}
-        header={
-          <AppInfo siteInfo={siteInfo} />
-        }
-      >
-        <ChatBtn onClick={handleChat} />
-      </TemplateVarPanel>
-    )
-  }
-
-  const renderVarPanel = () => {
-    return (
-      <TemplateVarPanel
-        isFold={false}
-        header={
-          <AppInfo siteInfo={siteInfo} />
-        }
-      >
-        {renderInputs()}
-        <ChatBtn
-          className='mt-3 mobile:ml-0 tablet:ml-[128px]'
-          onClick={handleChat}
-        />
-      </TemplateVarPanel>
-    )
-  }
-
-  const renderVarOpBtnGroup = () => {
-    return (
-      <VarOpBtnGroup
-        onConfirm={() => {
-          if (!canChat())
-            return
-
-          onInputsChange(inputs)
-          setIsFold(true)
-        }}
-        onCancel={() => {
-          setInputs(savedInputs)
-          setIsFold(true)
-        }}
-      />
-    )
-  }
-
-  const renderHasSetInputsPublic = () => {
-    if (!canEditInputs) {
-      return (
-        <TemplateVarPanel
-          isFold={false}
-          header={
-            <>
-              <PanelTitle
-                title={t('share.chat.publicPromptConfigTitle')}
-                className='mb-1'
-              />
-              <PromptTemplate html={highLightPromoptTemplate} />
-            </>
-          }
-        />
-      )
-    }
-
-    return (
-      <TemplateVarPanel
-        isFold={isFold}
-        header={
-          <>
-            <PanelTitle
-              title={t('share.chat.publicPromptConfigTitle')}
-              className='mb-1'
-            />
-            <PromptTemplate html={highLightPromoptTemplate} />
-            {isFold && (
-              <div className='flex items-center justify-between mt-3 border-t border-indigo-100 pt-4 text-xs text-indigo-600'>
-                <span className='text-gray-700'>{t('share.chat.configStatusDes')}</span>
-                <EditBtn onClick={() => setIsFold(false)} />
-              </div>
-            )}
-          </>
-        }
-      >
-        {renderInputs()}
-        {renderVarOpBtnGroup()}
-      </TemplateVarPanel>
-    )
-  }
-
-  const renderHasSetInputsPrivate = () => {
-    if (!canEditInputs || !hasVar)
-      return null
-
-    return (
-      <TemplateVarPanel
-        isFold={isFold}
-        header={
-          <div className='flex items-center justify-between text-indigo-600'>
-            <PanelTitle
-              title={!isFold ? t('share.chat.privatePromptConfigTitle') : t('share.chat.configStatusDes')}
-            />
-            {isFold && (
-              <EditBtn onClick={() => setIsFold(false)} />
-            )}
-          </div>
-        }
-      >
-        {renderInputs()}
-        {renderVarOpBtnGroup()}
-      </TemplateVarPanel>
-    )
-  }
-
-  const renderHasSetInputs = () => {
-    if ((!isPublicVersion && !canEditInputs) || !hasVar)
-      return null
-
-    return (
-      <div
-        className='pt-[88px] mb-5'
-      >
-        {isPublicVersion ? renderHasSetInputsPublic() : renderHasSetInputsPrivate()}
-      </div>)
-  }
-
-  return (
-    <div className='relative tablet:min-h-[64px]'>
-      {/* {hasSetInputs && renderHeader()} */}
-      <div className='mx-auto pc:w-[794px] max-w-full mobile:w-full px-3.5'>
-        {/*  Has't set inputs  */}
-        {
-          !hasSetInputs && (
-            <div className='mobile:pt-[72px] tablet:pt-[128px] pc:pt-[200px]'>
-              {hasVar
-                ? (
-                  renderVarPanel()
-                )
-                : (
-                  renderNoVarPanel()
-                )}
-            </div>
-          )
-        }
-
-        {/* Has set inputs */}
-        {hasSetInputs && renderHasSetInputs()}
-
-        {/* foot */}
-        {!hasSetInputs && (
-          <div className='mt-4 flex justify-between items-center h-8 text-xs text-gray-400'>
-
-            {siteInfo.privacy_policy
-              ? <div>{t('share.chat.privacyPolicyLeft')}
-                <a
-                  className='text-gray-500 px-1'
-                  href={siteInfo.privacy_policy}
-                  target='_blank' rel='noopener noreferrer'>{t('share.chat.privacyPolicyMiddle')}</a>
-                {t('share.chat.privacyPolicyRight')}
-              </div>
-              : <div>
-              </div>}
-            {
-              customConfig?.remove_webapp_brand
-                ? null
-                : (
-                  <a className='flex items-center pr-3 space-x-3' href="https://dify.ai/" target="_blank">
-                    <span className='uppercase'>{t('share.chat.powerBy')}</span>
-                    {
-                      customConfig?.replace_webapp_logo
-                        ? <img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
-                        : <FootLogo />
-                    }
-                  </a>
-                )
-            }
-          </div>
-        )}
-      </div>
-    </div >
-  )
-}
-
-export default React.memo(Welcome)

File diff suppressed because it is too large
+ 0 - 75
web/app/components/share/chatbot/welcome/massive-component.tsx


+ 0 - 22
web/app/components/share/chatbot/welcome/style.module.css

@@ -1,22 +0,0 @@
-.boxShodow {
-  box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
-}
-
-.bgGrayColor {
-  background-color: #F9FAFB;
-}
-
-.headerBg {
-  height: 3.5rem;
-  padding-left: 1.5rem;
-  padding-right: 1.5rem;
-}
-
-.formLabel {
-  width: 120px;
-  margin-right: 8px;
-}
-
-.customBtn {
-  width: 136px;
-}

+ 0 - 88
web/app/components/share/header.tsx

@@ -1,88 +0,0 @@
-import type { FC } from 'react'
-import React from 'react'
-import {
-  Bars3Icon,
-  PencilSquareIcon,
-} from '@heroicons/react/24/solid'
-import { useTranslation } from 'react-i18next'
-import AppIcon from '@/app/components/base/app-icon'
-import { ReplayIcon } from '@/app/components/app/chat/icon-component'
-import Tooltip from '@/app/components/base/tooltip'
-
-export type IHeaderProps = {
-  title: string
-  customerIcon?: React.ReactNode
-  icon: string
-  icon_background: string
-  isMobile?: boolean
-  isEmbedScene?: boolean
-  onShowSideBar?: () => void
-  onCreateNewChat?: () => void
-}
-const Header: FC<IHeaderProps> = ({
-  title,
-  isMobile,
-  customerIcon,
-  icon,
-  icon_background,
-  isEmbedScene = false,
-  onShowSideBar,
-  onCreateNewChat,
-}) => {
-  const { t } = useTranslation()
-  if (!isMobile)
-    return null
-
-  if (isEmbedScene) {
-    return (
-      <div
-        className={`
-          shrink-0 flex items-center justify-between h-14 px-4 bg-gray-100 
-          bg-gradient-to-r from-blue-600 to-sky-500
-        `}
-      >
-        <div className="flex items-center space-x-2">
-          {customerIcon || <AppIcon size="small" icon={icon} background={icon_background} />}
-          <div
-            className={'text-sm font-bold text-white'}
-          >
-            {title}
-          </div>
-        </div>
-        <Tooltip
-          selector={'embed-scene-restart-button'}
-          htmlContent={t('share.chat.resetChat')}
-          position='top'
-        >
-          <div className='flex cursor-pointer hover:rounded-lg hover:bg-black/5 w-8 h-8 items-center justify-center' onClick={() => {
-            onCreateNewChat?.()
-          }}>
-            <ReplayIcon className="h-4 w-4 text-sm font-bold text-white" />
-          </div>
-        </Tooltip>
-      </div>
-    )
-  }
-
-  return (
-    <div className="shrink-0 flex items-center justify-between h-14 px-4 bg-gray-100">
-      <div
-        className='flex items-center justify-center h-8 w-8 cursor-pointer'
-        onClick={() => onShowSideBar?.()}
-      >
-        <Bars3Icon className="h-4 w-4 text-gray-500" />
-      </div>
-      <div className='flex items-center space-x-2'>
-        <AppIcon size="small" icon={icon} background={icon_background} />
-        <div className=" text-sm text-gray-800 font-bold">{title}</div>
-      </div>
-      <div className='flex items-center justify-center h-8 w-8 cursor-pointer'
-        onClick={() => onCreateNewChat?.()}
-      >
-        <PencilSquareIcon className="h-4 w-4 text-gray-500" />
-      </div>
-    </div>
-  )
-}
-
-export default React.memo(Header)

+ 1 - 1
web/app/components/share/text-generation/result/content.tsx

@@ -1,7 +1,7 @@
 import type { FC } from 'react'
 import React from 'react'
 import Header from './header'
-import type { Feedbacktype } from '@/app/components/app/chat/type'
+import type { Feedbacktype } from '@/app/components/base/chat/chat/type'
 import { format } from '@/service/base'
 
 export type IResultProps = {

+ 1 - 1
web/app/components/share/text-generation/result/header.tsx

@@ -4,7 +4,7 @@ import React from 'react'
 import { useTranslation } from 'react-i18next'
 import { ClipboardDocumentIcon, HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline'
 import copy from 'copy-to-clipboard'
-import type { Feedbacktype } from '@/app/components/app/chat/type'
+import type { Feedbacktype } from '@/app/components/base/chat/chat/type'
 import Button from '@/app/components/base/button'
 import Toast from '@/app/components/base/toast'
 import Tooltip from '@/app/components/base/tooltip'

+ 1 - 1
web/app/components/share/text-generation/result/index.tsx

@@ -9,7 +9,7 @@ import TextGenerationRes from '@/app/components/app/text-generate/item'
 import NoData from '@/app/components/share/text-generation/no-data'
 import Toast from '@/app/components/base/toast'
 import { sendCompletionMessage, sendWorkflowMessage, updateFeedback } from '@/service/share'
-import type { Feedbacktype } from '@/app/components/app/chat/type'
+import type { Feedbacktype } from '@/app/components/base/chat/chat/type'
 import Loading from '@/app/components/base/loading'
 import type { PromptConfig } from '@/models/debug'
 import type { InstalledApp } from '@/models/explore'

+ 1 - 1
web/app/components/tools/utils/index.ts

@@ -1,4 +1,4 @@
-import type { ThoughtItem } from '../../app/chat/type'
+import type { ThoughtItem } from '@/app/components/base/chat/chat/type'
 import type { VisionFile } from '@/types/app'
 
 export const sortAgentSorts = (list: ThoughtItem[]) => {

+ 5 - 5
web/app/components/workflow/panel/chat-record/index.tsx

@@ -14,7 +14,7 @@ import { useWorkflowRun } from '../../hooks'
 import UserInput from './user-input'
 import Chat from '@/app/components/base/chat/chat'
 import type { ChatItem } from '@/app/components/base/chat/types'
-import { fetchConvesationMessages } from '@/service/debug'
+import { fetchConversationMessages } from '@/service/debug'
 import { useStore as useAppStore } from '@/app/components/app/store'
 import Loading from '@/app/components/base/loading'
 
@@ -51,11 +51,11 @@ const ChatRecord = () => {
     return res
   }, [chatList])
 
-  const handleFetchConvesationMessages = useCallback(async () => {
+  const handleFetchConversationMessages = useCallback(async () => {
     if (appDetail && currentConversationID) {
       try {
         setFetched(false)
-        const res = await fetchConvesationMessages(appDetail.id, currentConversationID)
+        const res = await fetchConversationMessages(appDetail.id, currentConversationID)
         setFetched(true)
         setChatList((res as any).data)
       }
@@ -65,8 +65,8 @@ const ChatRecord = () => {
     }
   }, [appDetail, currentConversationID])
   useEffect(() => {
-    handleFetchConvesationMessages()
-  }, [currentConversationID, appDetail, handleFetchConvesationMessages])
+    handleFetchConversationMessages()
+  }, [currentConversationID, appDetail, handleFetchConversationMessages])
 
   return (
     <div

+ 1 - 1
web/app/components/workflow/run/output-panel.tsx

@@ -3,7 +3,7 @@ import type { FC } from 'react'
 import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
 import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
 import { Markdown } from '@/app/components/base/markdown'
-import LoadingAnim from '@/app/components/app/chat/loading-anim'
+import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
 
 type OutputPanelProps = {
   isRunning?: boolean

+ 1 - 1
web/app/components/workflow/run/result-text.tsx

@@ -3,7 +3,7 @@ import type { FC } from 'react'
 import { useTranslation } from 'react-i18next'
 import { ImageIndentLeft } from '@/app/components/base/icons/src/vender/line/editor'
 import { Markdown } from '@/app/components/base/markdown'
-import LoadingAnim from '@/app/components/app/chat/loading-anim'
+import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
 
 type ResultTextProps = {
   isRunning?: boolean

+ 1 - 1
web/service/base.ts

@@ -1,6 +1,6 @@
 import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'
 import Toast from '@/app/components/base/toast'
-import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/app/chat/type'
+import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type'
 import type { VisionFile } from '@/types/app'
 import type {
   IterationFinishedResponse,

+ 1 - 1
web/service/debug.ts

@@ -55,7 +55,7 @@ export const fetchSuggestedQuestions = (appId: string, messageId: string, getAbo
   )
 }
 
-export const fetchConvesationMessages = (appId: string, conversation_id: string, getAbortController?: any) => {
+export const fetchConversationMessages = (appId: string, conversation_id: string, getAbortController?: any) => {
   return get(`apps/${appId}/chat-messages`, {
     params: {
       conversation_id,

+ 1 - 1
web/service/share.ts

@@ -3,7 +3,7 @@ import {
   del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost,
   delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost,
 } from './base'
-import type { Feedbacktype } from '@/app/components/app/chat/type'
+import type { Feedbacktype } from '@/app/components/base/chat/chat/type'
 import type {
   AppConversationData,
   AppData,