From a430284aa21e3ae1f0d5654e55b2ad2852519cc2 Mon Sep 17 00:00:00 2001
From: wwf <yearningwang@iqtogether.com>
Date: 星期三, 04 六月 2025 15:17:49 +0800
Subject: [PATCH] 初始化

---
 app/components/share/text-generation/index.tsx |  363 ++++++++++++++++++++++++++-------------------------
 1 files changed, 183 insertions(+), 180 deletions(-)

diff --git a/app/components/share/text-generation/index.tsx b/app/components/share/text-generation/index.tsx
index 5751092..c54601a 100644
--- a/app/components/share/text-generation/index.tsx
+++ b/app/components/share/text-generation/index.tsx
@@ -3,16 +3,18 @@
 import React, { useCallback, useEffect, useRef, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import {
-  RiBookmark3Line,
   RiErrorWarningFill,
 } from '@remixicon/react'
-import { useBoolean } from 'ahooks'
+import { useBoolean, useClickAway } from 'ahooks'
+import { XMarkIcon } from '@heroicons/react/24/outline'
 import { usePathname, useRouter, useSearchParams } from 'next/navigation'
 import TabHeader from '../../base/tab-header'
+import Button from '../../base/button'
 import { checkOrSetAccessToken } from '../utils'
-import MenuDropdown from './menu-dropdown'
+import s from './style.module.css'
 import RunBatch from './run-batch'
 import ResDownload from './run-batch/res-download'
+import cn from '@/utils/classnames'
 import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
 import RunOnce from '@/app/components/share/text-generation/run-once'
 import { fetchSavedMessage as doFetchSavedMessage, fetchAppInfo, fetchAppParams, removeMessage, saveMessage } from '@/service/share'
@@ -24,7 +26,6 @@
   TextToSpeechConfig,
 } from '@/models/debug'
 import AppIcon from '@/app/components/base/app-icon'
-import Badge from '@/app/components/base/badge'
 import { changeLanguage } from '@/i18n/i18next-config'
 import Loading from '@/app/components/base/loading'
 import { userInputsFormToPromptVariables } from '@/utils/model-config'
@@ -36,8 +37,6 @@
 import type { VisionFile, VisionSettings } from '@/types/app'
 import { Resolution, TransferMethod } from '@/types/app'
 import { useAppFavicon } from '@/hooks/use-app-favicon'
-import DifyLogo from '@/app/components/base/logo/dify-logo'
-import cn from '@/utils/classnames'
 
 const GROUP_SIZE = 5 // to avoid RPM(Request per minute) limit. The group task finished then the next group.
 enum TaskStatus {
@@ -73,6 +72,8 @@
   const { t } = useTranslation()
   const media = useBreakpoints()
   const isPC = media === MediaType.pc
+  const isTablet = media === MediaType.tablet
+  const isMobile = media === MediaType.mobile
 
   const searchParams = useSearchParams()
   const mode = searchParams.get('mode') || 'create'
@@ -101,7 +102,6 @@
   const [appId, setAppId] = useState<string>('')
   const [siteInfo, setSiteInfo] = useState<SiteInfo | null>(null)
   const [canReplaceLogo, setCanReplaceLogo] = useState<boolean>(false)
-  const [customConfig, setCustomConfig] = useState<Record<string, any> | null>(null)
   const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null)
   const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig | null>(null)
   const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig | null>(null)
@@ -137,12 +137,10 @@
   const handleSend = () => {
     setIsCallBatchAPI(false)
     setControlSend(Date.now())
-
-    // eslint-disable-next-line ts/no-use-before-define
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
     setAllTaskList([]) // clear batch task running status
-
-    // eslint-disable-next-line ts/no-use-before-define
-    showResultPanel()
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
+    showResSidebar()
   }
 
   const [controlRetry, setControlRetry] = useState(0)
@@ -302,17 +300,10 @@
     const varLen = promptConfig?.prompt_variables.length || 0
     setIsCallBatchAPI(true)
     const allTaskList: Task[] = payloadData.map((item, i) => {
-      const inputs: Record<string, any> = {}
+      const inputs: Record<string, string> = {}
       if (varLen > 0) {
         item.slice(0, varLen).forEach((input, index) => {
-          const varSchema = promptConfig?.prompt_variables[index]
-          inputs[varSchema?.key as string] = input
-          if (!input) {
-            if (varSchema?.type === 'string' || varSchema?.type === 'paragraph')
-              inputs[varSchema?.key as string] = ''
-            else
-              inputs[varSchema?.key as string] = undefined
-          }
+          inputs[promptConfig?.prompt_variables[index].key as string] = input
         })
       }
       return {
@@ -328,9 +319,8 @@
     setControlSend(Date.now())
     // clear run once task status
     setControlStopResponding(Date.now())
-
-    // eslint-disable-next-line ts/no-use-before-define
-    showResultPanel()
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
+    showResSidebar()
   }
   const handleCompleted = (completionRes: string, taskId?: number, isSuccess?: boolean) => {
     const allTaskListLatest = getLatestTaskList()
@@ -395,11 +385,10 @@
   useEffect(() => {
     (async () => {
       const [appData, appParams]: any = await fetchInitData()
-      const { app_id: appId, site: siteInfo, can_replace_logo, custom_config } = appData
+      const { app_id: appId, site: siteInfo, can_replace_logo } = appData
       setAppId(appId)
       setSiteInfo(siteInfo as SiteInfo)
       setCanReplaceLogo(can_replace_logo)
-      setCustomConfig(custom_config)
       changeLanguage(siteInfo.default_language)
 
       const { user_input_form, more_like_this, file_upload, text_to_speech }: any = appParams
@@ -439,21 +428,24 @@
     icon_url: siteInfo?.icon_url,
   })
 
-  const [isShowResultPanel, { setTrue: doShowResultPanel, setFalse: hideResultPanel }] = useBoolean(false)
-  const showResultPanel = () => {
+  const [isShowResSidebar, { setTrue: doShowResSidebar, setFalse: hideResSidebar }] = useBoolean(false)
+  const showResSidebar = () => {
     // fix: useClickAway hideResSidebar will close sidebar
     setTimeout(() => {
-      doShowResultPanel()
+      doShowResSidebar()
     }, 0)
   }
-  const [resultExisted, setResultExisted] = useState(false)
+  const resRef = useRef<HTMLDivElement>(null)
+  useClickAway(() => {
+    hideResSidebar()
+  }, resRef)
 
   const renderRes = (task?: Task) => (<Res
     key={task?.id}
     isWorkflow={isWorkflow}
     isCallBatchAPI={isCallBatchAPI}
     isPC={isPC}
-    isMobile={!isPC}
+    isMobile={isMobile}
     isInstalledApp={isInstalledApp}
     installedAppInfo={installedAppInfo}
     isError={task?.status === TaskStatus.failed}
@@ -463,7 +455,7 @@
     controlSend={controlSend}
     controlRetry={task?.status === TaskStatus.failed ? controlRetry : 0}
     controlStopResponding={controlStopResponding}
-    onShowRes={showResultPanel}
+    onShowRes={showResSidebar}
     handleSaveMessage={handleSaveMessage}
     taskId={task?.id}
     onCompleted={handleCompleted}
@@ -471,99 +463,128 @@
     completionFiles={completionFiles}
     isShowTextToSpeech={!!textToSpeechConfig?.enabled}
     siteInfo={siteInfo}
-    onRunStart={() => setResultExisted(true)}
   />)
 
   const renderBatchRes = () => {
     return (showTaskList.map(task => renderRes(task)))
   }
 
+  const resWrapClassNames = (() => {
+    if (isPC)
+      return 'grow h-full'
+
+    if (!isShowResSidebar)
+      return 'none'
+
+    return cn('fixed z-50 inset-0', isTablet ? 'pl-[128px]' : 'pl-6')
+  })()
+
   const renderResWrap = (
     <div
-      className={cn(
-        'relative flex h-full flex-col',
-        !isPC && 'h-[calc(100vh_-_36px)] rounded-t-2xl shadow-lg backdrop-blur-sm',
-        !isPC
-          ? isShowResultPanel
-            ? 'bg-background-default-burn'
-            : 'border-t-[0.5px] border-divider-regular bg-components-panel-bg'
-          : 'bg-chatbot-bg',
-      )}
+      ref={resRef}
+      className={
+        cn(
+          'flex flex-col h-full shrink-0',
+          isPC ? 'px-10 py-8' : 'bg-gray-50',
+          isTablet && 'p-6', isMobile && 'p-4')
+      }
     >
-      {isCallBatchAPI && (
-        <div className={cn(
-          'flex shrink-0 items-center justify-between px-14 pb-2 pt-9',
-          !isPC && 'px-4 pb-1 pt-3',
-        )}>
-          <div className='system-md-semibold-uppercase text-text-primary'>{t('share.generation.executions', { num: allTaskList.length })}</div>
-          {allSuccessTaskList.length > 0 && (
-            <ResDownload
-              isMobile={!isPC}
-              values={exportRes}
-            />
+      <>
+        <div className='flex items-center justify-between shrink-0'>
+          <div className='flex items-center space-x-3'>
+            <div className={s.starIcon}></div>
+            <div className='text-lg font-semibold text-gray-800'>{t('share.generation.title')}</div>
+          </div>
+          <div className='flex items-center space-x-2'>
+            {allFailedTaskList.length > 0 && (
+              <div className='flex items-center'>
+                <RiErrorWarningFill className='w-4 h-4 text-[#D92D20]' />
+                <div className='ml-1 text-[#D92D20]'>{t('share.generation.batchFailed.info', { num: allFailedTaskList.length })}</div>
+                <Button
+                  variant='primary'
+                  className='ml-2'
+                  onClick={handleRetryAllFailedTask}
+                >{t('share.generation.batchFailed.retry')}</Button>
+                <div className='mx-3 w-[1px] h-3.5 bg-gray-200'></div>
+              </div>
+            )}
+            {allSuccessTaskList.length > 0 && (
+              <ResDownload
+                isMobile={isMobile}
+                values={exportRes}
+              />
+            )}
+            {!isPC && (
+              <div
+                className='flex items-center justify-center cursor-pointer'
+                onClick={hideResSidebar}
+              >
+                <XMarkIcon className='w-4 h-4 text-gray-800' />
+              </div>
+            )}
+          </div>
+        </div>
+
+        <div className='overflow-y-auto grow'>
+          {!isCallBatchAPI ? renderRes() : renderBatchRes()}
+          {!noPendingTask && (
+            <div className='mt-4'>
+              <Loading type='area' />
+            </div>
           )}
         </div>
-      )}
-      <div className={cn(
-        'flex h-0 grow flex-col overflow-y-auto',
-        isPC && 'px-14 py-8',
-        isPC && isCallBatchAPI && 'pt-0',
-        !isPC && 'p-0 pb-2',
-      )}>
-        {!isCallBatchAPI ? renderRes() : renderBatchRes()}
-        {!noPendingTask && (
-          <div className='mt-4'>
-            <Loading type='area' />
-          </div>
-        )}
-      </div>
-      {isCallBatchAPI && allFailedTaskList.length > 0 && (
-        <div className='absolute bottom-6 left-1/2 z-10 flex -translate-x-1/2 items-center gap-2 rounded-xl border border-components-panel-border bg-components-panel-bg-blur p-3 shadow-lg backdrop-blur-sm'>
-          <RiErrorWarningFill className='h-4 w-4 text-text-destructive' />
-          <div className='system-sm-medium text-text-secondary'>{t('share.generation.batchFailed.info', { num: allFailedTaskList.length })}</div>
-          <div className='h-3.5 w-px bg-divider-regular'></div>
-          <div onClick={handleRetryAllFailedTask} className='system-sm-semibold-uppercase cursor-pointer text-text-accent'>{t('share.generation.batchFailed.retry')}</div>
-        </div>
-      )}
+      </>
     </div>
   )
 
   if (!appId || !siteInfo || !promptConfig) {
     return (
-      <div className='flex h-screen items-center'>
+      <div className='flex items-center h-screen'>
         <Loading type='app' />
       </div>)
   }
 
   return (
-    <div className={cn(
-      'bg-background-default-burn',
-      isPC && 'flex',
-      !isPC && 'flex-col',
-      isInstalledApp ? 'h-full rounded-2xl shadow-md' : 'h-screen',
-    )}>
-      {/* Left */}
+    <>
       <div className={cn(
-        'relative flex h-full shrink-0 flex-col',
-        isPC ? 'w-[600px] max-w-[50%]' : resultExisted ? 'h-[calc(100%_-_64px)]' : '',
-        isInstalledApp && 'rounded-l-2xl',
+        isPC && 'flex',
+        isInstalledApp ? s.installedApp : 'h-screen',
+        'bg-gray-50',
       )}>
-        {/* header */}
-        <div className={cn('shrink-0 space-y-4 border-b border-divider-subtle', isPC ? 'bg-components-panel-bg p-8 pb-0' : 'p-4 pb-0')}>
-          <div className='flex items-center gap-3'>
-            <AppIcon
-              size={isPC ? 'large' : 'small'}
-              iconType={siteInfo.icon_type}
-              icon={siteInfo.icon}
-              background={siteInfo.icon_background || appDefaultIconBackground}
-              imageUrl={siteInfo.icon_url}
-            />
-            <div className='system-md-semibold grow truncate text-text-secondary'>{siteInfo.title}</div>
-            <MenuDropdown data={siteInfo} />
+        {/* Left */}
+        <div className={cn(
+          isPC ? 'w-[600px] max-w-[50%] p-8' : 'p-4',
+          isInstalledApp && 'rounded-l-2xl',
+          'shrink-0 relative flex flex-col pb-10 h-full border-r border-gray-100 bg-white',
+        )}>
+          <div className='mb-6'>
+            <div className='flex items-center justify-between'>
+              <div className='flex items-center space-x-3'>
+                <AppIcon
+                  size="small"
+                  iconType={siteInfo.icon_type}
+                  icon={siteInfo.icon}
+                  background={siteInfo.icon_background || appDefaultIconBackground}
+                  imageUrl={siteInfo.icon_url}
+                />
+                <div className='text-lg font-semibold text-gray-800'>{siteInfo.title}</div>
+              </div>
+              {!isPC && (
+                <Button
+                  className='shrink-0 ml-2'
+                  onClick={showResSidebar}
+                >
+                  <div className='flex items-center space-x-2 text-primary-600 text-[13px] font-medium'>
+                    <div className={s.starIcon}></div>
+                    <span>{t('share.generation.title')}</span>
+                  </div>
+                </Button>
+              )}
+            </div>
+            {siteInfo.description && (
+              <div className='mt-2 text-xs text-gray-500'>{siteInfo.description}</div>
+            )}
           </div>
-          {siteInfo.description && (
-            <div className='system-xs-regular text-text-tertiary'>{siteInfo.description}</div>
-          )}
           <TabHeader
             items={[
               { id: 'create', name: t('share.generation.tabs.create') },
@@ -573,12 +594,11 @@
                   id: 'saved',
                   name: t('share.generation.tabs.saved'),
                   isRight: true,
-                  icon: <RiBookmark3Line className='h-4 w-4' />,
                   extra: savedMessages.length > 0
                     ? (
-                      <Badge className='ml-1'>
+                      <div className='ml-1 flex items-center h-5 px-1.5 rounded-md border border-gray-200 text-gray-500 text-xs font-medium'>
                         {savedMessages.length}
-                      </Badge>
+                      </div>
                     )
                     : null,
                 }]
@@ -587,89 +607,72 @@
             value={currentTab}
             onChange={setCurrentTab}
           />
-        </div>
-        {/* form */}
-        <div className={cn(
-          'h-0 grow overflow-y-auto bg-components-panel-bg',
-          isPC ? 'px-8' : 'px-4',
-          !isPC && resultExisted && customConfig?.remove_webapp_brand && 'rounded-b-2xl border-b-[0.5px] border-divider-regular',
-        )}>
-          <div className={cn(currentTab === 'create' ? 'block' : 'hidden')}>
-            <RunOnce
-              siteInfo={siteInfo}
-              inputs={inputs}
-              inputsRef={inputsRef}
-              onInputsChange={setInputs}
-              promptConfig={promptConfig}
-              onSend={handleSend}
-              visionConfig={visionConfig}
-              onVisionFilesChange={setCompletionFiles}
-            />
+          <div className='h-20 overflow-y-auto grow'>
+            <div className={cn(currentTab === 'create' ? 'block' : 'hidden')}>
+              <RunOnce
+                siteInfo={siteInfo}
+                inputs={inputs}
+                inputsRef={inputsRef}
+                onInputsChange={setInputs}
+                promptConfig={promptConfig}
+                onSend={handleSend}
+                visionConfig={visionConfig}
+                onVisionFilesChange={setCompletionFiles}
+              />
+            </div>
+            <div className={cn(isInBatchTab ? 'block' : 'hidden')}>
+              <RunBatch
+                vars={promptConfig.prompt_variables}
+                onSend={handleRunBatch}
+                isAllFinished={allTasksRun}
+              />
+            </div>
+
+            {currentTab === 'saved' && (
+              <SavedItems
+                className='mt-4'
+                isShowTextToSpeech={textToSpeechConfig?.enabled}
+                list={savedMessages}
+                onRemove={handleRemoveSavedMessage}
+                onStartCreateContent={() => setCurrentTab('create')}
+              />
+            )}
           </div>
-          <div className={cn(isInBatchTab ? 'block' : 'hidden')}>
-            <RunBatch
-              vars={promptConfig.prompt_variables}
-              onSend={handleRunBatch}
-              isAllFinished={allTasksRun}
-            />
-          </div>
-          {currentTab === 'saved' && (
-            <SavedItems
-              className={cn(isPC ? 'mt-6' : 'mt-4')}
-              isShowTextToSpeech={textToSpeechConfig?.enabled}
-              list={savedMessages}
-              onRemove={handleRemoveSavedMessage}
-              onStartCreateContent={() => setCurrentTab('create')}
-            />
-          )}
-        </div>
-        {/* powered by */}
-        {!customConfig?.remove_webapp_brand && (
+
+          {/* copyright */}
           <div className={cn(
-            'flex shrink-0 items-center gap-1.5 bg-components-panel-bg py-3',
-            isPC ? 'px-8' : 'px-4',
-            !isPC && resultExisted && 'rounded-b-2xl border-b-[0.5px] border-divider-regular',
+            isInstalledApp ? 'left-[248px]' : 'left-8',
+            'fixed  bottom-4  flex space-x-2 text-gray-400 font-normal text-xs',
           )}>
-            <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div>
-            {customConfig?.replace_webapp_logo && (
-              <img src={customConfig?.replace_webapp_logo} alt='logo' className='block h-5 w-auto' />
+            {siteInfo.copyright && (
+              <div className="">漏 {(new Date()).getFullYear()} {siteInfo.copyright}</div>
             )}
-            {!customConfig?.replace_webapp_logo && (
-              <DifyLogo size='small' />
+            {siteInfo.privacy_policy && (
+              <>
+                {siteInfo.copyright && <div>路</div>}
+                <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>
+
+        {/* Result */}
+        <div
+          className={resWrapClassNames}
+          style={{
+            background: (!isPC && isShowResSidebar) ? 'rgba(35, 56, 118, 0.2)' : 'none',
+          }}
+        >
+          {renderResWrap}
+        </div>
       </div>
-      {/* Result */}
-      <div className={cn(
-        isPC
-          ? 'h-full w-0 grow'
-          : isShowResultPanel
-            ? 'fixed inset-0 z-50 bg-background-overlay backdrop-blur-sm'
-            : resultExisted
-              ? 'relative h-16 shrink-0 overflow-hidden bg-background-default-burn pt-2.5'
-              : '',
-      )}>
-        {!isPC && (
-          <div
-            className={cn(
-              isShowResultPanel
-                ? 'flex items-center justify-center p-2 pt-6'
-                : 'absolute left-0 top-0 z-10 flex w-full items-center justify-center px-2 pb-[57px] pt-[3px]',
-            )}
-            onClick={() => {
-              if (isShowResultPanel)
-                hideResultPanel()
-              else
-                showResultPanel()
-            }}
-          >
-            <div className='h-1 w-8 cursor-grab rounded bg-divider-solid' />
-          </div>
-        )}
-        {renderResWrap}
-      </div>
-    </div>
+    </>
   )
 }
 

--
Gitblit v1.8.0