Browse Source

Feat/auto rule generate (#300)

Joel 1 year ago
parent
commit
6483beb096

File diff suppressed because it is too large
+ 24 - 17
web/app/components/app/configuration/config-prompt/index.tsx


File diff suppressed because it is too large
+ 95 - 70
web/app/components/app/configuration/config-var/index.tsx


File diff suppressed because it is too large
+ 33 - 0
web/app/components/app/configuration/config/automatic/automatic-btn.tsx


File diff suppressed because it is too large
+ 205 - 0
web/app/components/app/configuration/config/automatic/get-automatic-res.tsx


+ 33 - 15
web/app/components/app/configuration/config/index.tsx

@@ -3,19 +3,22 @@ import type { FC } from 'react'
 import React from 'react'
 import { useContext } from 'use-context-selector'
 import produce from 'immer'
-import AddFeatureBtn from './feature/add-feature-btn'
-import ChooseFeature from './feature/choose-feature'
-import useFeature from './feature/use-feature'
-import ConfigContext from '@/context/debug-configuration'
+import { useBoolean } from 'ahooks'
 import DatasetConfig from '../dataset-config'
 import ChatGroup from '../features/chat-group'
 import ExperienceEnchanceGroup from '../features/experience-enchance-group'
 import Toolbox from '../toolbox'
+import AddFeatureBtn from './feature/add-feature-btn'
+import AutomaticBtn from './automatic/automatic-btn'
+import type { AutomaticRes } from './automatic/get-automatic-res'
+import GetAutomaticResModal from './automatic/get-automatic-res'
+import ChooseFeature from './feature/choose-feature'
+import useFeature from './feature/use-feature'
+import ConfigContext from '@/context/debug-configuration'
 import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
 import ConfigVar from '@/app/components/app/configuration/config-var'
 import type { PromptVariable } from '@/models/debug'
 import { AppType } from '@/types/app'
-import { useBoolean } from 'ahooks'
 
 const Config: FC = () => {
   const {
@@ -29,7 +32,7 @@ const Config: FC = () => {
     moreLikeThisConifg,
     setMoreLikeThisConifg,
     suggestedQuestionsAfterAnswerConfig,
-    setSuggestedQuestionsAfterAnswerConfig
+    setSuggestedQuestionsAfterAnswerConfig,
   } = useContext(ConfigContext)
   const isChatApp = mode === AppType.chat
 
@@ -41,9 +44,8 @@ const Config: FC = () => {
       draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...newVariables]
     })
 
-    if (modelConfig.configs.prompt_template !== newTemplate) {
+    if (modelConfig.configs.prompt_template !== newTemplate)
       setFormattingChanged(true)
-    }
 
     setPrevPromptConfig(modelConfig.configs)
     setModelConfig(newModelConfig)
@@ -59,7 +61,7 @@ const Config: FC = () => {
 
   const [showChooseFeature, {
     setTrue: showChooseFeatureTrue,
-    setFalse: showChooseFeatureFalse
+    setFalse: showChooseFeatureFalse,
   }] = useBoolean(false)
   const { featureConfig, handleFeatureChange } = useFeature({
     introduction,
@@ -81,14 +83,24 @@ const Config: FC = () => {
   const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer)
   const hasToolbox = false
 
+  const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
+  const handleAutomaticRes = (res: AutomaticRes) => {
+    const newModelConfig = produce(modelConfig, (draft) => {
+      draft.configs.prompt_template = res.prompt
+      draft.configs.prompt_variables = res.variables.map(key => ({ key, name: key, type: 'string', required: true }))
+    })
+    setModelConfig(newModelConfig)
+    setPrevPromptConfig(modelConfig.configs)
+    if (mode === AppType.chat)
+      setIntroduction(res.opening_statement)
+    showAutomaticFalse()
+  }
   return (
     <>
       <div className="pb-[20px]">
         <div className='flex justify-between items-center mb-4'>
           <AddFeatureBtn onClick={showChooseFeatureTrue} />
-          <div>
-            {/* AutoMatic */}
-          </div>
+          <AutomaticBtn onClick={showAutomaticTrue}/>
         </div>
 
         {showChooseFeature && (
@@ -100,6 +112,14 @@ const Config: FC = () => {
             onChange={handleFeatureChange}
           />
         )}
+        {showAutomatic && (
+          <GetAutomaticResModal
+            mode={mode as AppType}
+            isShow={showAutomatic}
+            onClose={showAutomaticFalse}
+            onFinished={handleAutomaticRes}
+          />
+        )}
         {/* Template */}
         <ConfigPrompt
           mode={mode as AppType}
@@ -124,9 +144,8 @@ const Config: FC = () => {
               isShowOpeningStatement={featureConfig.openingStatement}
               openingStatementConfig={
                 {
-                  promptTemplate,
                   value: introduction,
-                  onChange: setIntroduction
+                  onChange: setIntroduction,
                 }
               }
               isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer}
@@ -139,7 +158,6 @@ const Config: FC = () => {
           <ExperienceEnchanceGroup />
         )}
 
-
         {/* Toolbox */}
         {
           hasToolbox && (

+ 35 - 34
web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx

@@ -17,9 +17,9 @@ import { getNewVar } from '@/utils/var'
 import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight'
 
 export type IOpeningStatementProps = {
-  promptTemplate: string
   value: string
-  onChange: (value: string) => void
+  readonly?: boolean
+  onChange?: (value: string) => void
 }
 
 // regex to match the {{}} and replace it with a span
@@ -27,6 +27,7 @@ const regex = /\{\{([^}]+)\}\}/g
 
 const OpeningStatement: FC<IOpeningStatementProps> = ({
   value = '',
+  readonly,
   onChange,
 }) => {
   const { t } = useTranslation()
@@ -64,6 +65,8 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
     .replace(/\n/g, '<br />')
 
   const handleEdit = () => {
+    if (readonly)
+      return
     setFocus()
   }
 
@@ -93,11 +96,11 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
       return
     }
     setBlur()
-    onChange(tempValue)
+    onChange?.(tempValue)
   }
 
   const cancelAutoAddVar = () => {
-    onChange(tempValue)
+    onChange?.(tempValue)
     hideConfirmAddVar()
     setBlur()
   }
@@ -106,15 +109,15 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
     const newModelConfig = produce(modelConfig, (draft) => {
       draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key))]
     })
-    onChange(tempValue)
+    onChange?.(tempValue)
     setModelConfig(newModelConfig)
     hideConfirmAddVar()
     setBlur()
   }
 
-  const headerRight = (
+  const headerRight = !readonly ? (
     <OperationBtn type='edit' actionName={hasValue ? '' : t('appDebug.openingStatement.writeOpner') as string} onClick={handleEdit} />
-  )
+  ) : null
 
   return (
     <Panel
@@ -130,30 +133,28 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
       isFocus={isFocus}
     >
       <div className='text-gray-700 text-sm'>
-        {(hasValue || (!hasValue && isFocus))
-          ? (
-            <>
-              {isFocus
-                ? (
-                  <textarea
-                    ref={inputRef}
-                    value={tempValue}
-                    rows={3}
-                    onChange={e => setTempValue(e.target.value)}
-                    className="w-full px-0 text-sm  border-0 bg-transparent  focus:outline-none "
-                    placeholder={t('appDebug.openingStatement.placeholder') as string}
-                  >
-                  </textarea>
-                )
-                : (
-                  <div dangerouslySetInnerHTML={{
-                    __html: coloredContent,
-                  }}></div>
-                )}
-
-              {/* Operation Bar */}
-              {isFocus
-            && (
+        {(hasValue || (!hasValue && isFocus)) ? (
+          <>
+            {isFocus
+              ? (
+                <textarea
+                  ref={inputRef}
+                  value={tempValue}
+                  rows={3}
+                  onChange={e => setTempValue(e.target.value)}
+                  className="w-full px-0 text-sm  border-0 bg-transparent  focus:outline-none "
+                  placeholder={t('appDebug.openingStatement.placeholder') as string}
+                >
+                </textarea>
+              )
+              : (
+                <div dangerouslySetInnerHTML={{
+                  __html: coloredContent,
+                }}></div>
+              )}
+
+            {/* Operation Bar */}
+            {isFocus && (
               <div className='mt-2 flex items-center justify-between'>
                 <div className='text-xs text-gray-500'>{t('appDebug.openingStatement.varTip')}</div>
 
@@ -164,9 +165,9 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
               </div>
             )}
 
-            </>) : (
-            <div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div>
-          )}
+          </>) : (
+          <div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div>
+        )}
 
         {isShowConfirmAddVar && (
           <ConfirmAddVar

+ 33 - 29
web/app/components/base/block-input/index.tsx

@@ -33,12 +33,14 @@ export type IBlockInputProps = {
   value: string
   className?: string // wrapper class
   highLightClassName?: string // class for the highlighted text default is text-blue-500
+  readonly?: boolean
   onConfirm?: (value: string, keys: string[]) => void
 }
 
 const BlockInput: FC<IBlockInputProps> = ({
   value = '',
   className,
+  readonly = false,
   onConfirm,
 }) => {
   const { t } = useTranslation()
@@ -113,7 +115,7 @@ const BlockInput: FC<IBlockInputProps> = ({
   const editAreaClassName = 'focus:outline-none bg-transparent text-sm'
 
   const textAreaContent = (
-    <div className='h-[180px] overflow-y-auto' onClick={() => setIsEditing(true)}>
+    <div className={classNames(readonly ? 'max-h-[180px] pb-5' : 'h-[180px]', ' overflow-y-auto')} onClick={() => !readonly && setIsEditing(true)}>
       {isEditing
         ? <div className='h-full px-4 py-1'>
           <textarea
@@ -141,35 +143,37 @@ const BlockInput: FC<IBlockInputProps> = ({
     <div className={classNames('block-input w-full overflow-y-auto border-none rounded-lg')}>
       {textAreaContent}
       {/* footer */}
-      <div className='flex item-center h-14 px-4'>
-        {isContentChanged
-          ? (
-            <div className='flex items-center justify-between w-full'>
-              <div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{currentValue.length}</div>
-              <div className='flex space-x-2'>
-                <Button
-                  onClick={handleCancel}
-                  className='w-20 !h-8 !text-[13px]'
-                >
-                  {t('common.operation.cancel')}
-                </Button>
-                <Button
-                  onClick={handleSubmit}
-                  type="primary"
-                  className='w-20 !h-8 !text-[13px]'
-                >
-                  {t('common.operation.confirm')}
-                </Button>
-              </div>
+      {!readonly && (
+        <div className='flex item-center h-14 px-4'>
+          {isContentChanged
+            ? (
+              <div className='flex items-center justify-between w-full'>
+                <div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{currentValue?.length}</div>
+                <div className='flex space-x-2'>
+                  <Button
+                    onClick={handleCancel}
+                    className='w-20 !h-8 !text-[13px]'
+                  >
+                    {t('common.operation.cancel')}
+                  </Button>
+                  <Button
+                    onClick={handleSubmit}
+                    type="primary"
+                    className='w-20 !h-8 !text-[13px]'
+                  >
+                    {t('common.operation.confirm')}
+                  </Button>
+                </div>
 
-            </div>
-          )
-          : (
-            <p className="leading-5 text-xs text-gray-500">
-              {t('appDebug.promptTip')}
-            </p>
-          )}
-      </div>
+              </div>
+            )
+            : (
+              <p className="leading-5 text-xs text-gray-500">
+                {t('appDebug.promptTip')}
+              </p>
+            )}
+        </div>
+      )}
 
     </div>
   )

+ 119 - 108
web/app/components/datasets/create/step-two/index.tsx

@@ -1,36 +1,36 @@
+/* eslint-disable no-mixed-operators */
 'use client'
-import React, { useState, useRef, useEffect, useLayoutEffect } from 'react'
+import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import { useBoolean } from 'ahooks'
-import type { File, PreProcessingRule, Rules, FileIndexingEstimateResponse as IndexingEstimateResponse } from '@/models/datasets'
+import { XMarkIcon } from '@heroicons/react/20/solid'
+import cn from 'classnames'
+import Link from 'next/link'
+import PreviewItem from './preview-item'
+import s from './index.module.css'
+import type { CreateDocumentReq, File, FullDocumentDetail, FileIndexingEstimateResponse as IndexingEstimateResponse, PreProcessingRule, Rules, createDocumentResponse } from '@/models/datasets'
 import {
-  fetchDefaultProcessRule,
-  createFirstDocument,
   createDocument,
+  createFirstDocument,
   fetchFileIndexingEstimate as didFetchFileIndexingEstimate,
+  fetchDefaultProcessRule,
 } from '@/service/datasets'
-import type { CreateDocumentReq, createDocumentResponse, FullDocumentDetail } from '@/models/datasets'
 import Button from '@/app/components/base/button'
-import PreviewItem from './preview-item'
 import Loading from '@/app/components/base/loading'
-import { XMarkIcon } from '@heroicons/react/20/solid'
 
-import cn from 'classnames'
-import s from './index.module.css'
-import Link from 'next/link'
 import Toast from '@/app/components/base/toast'
 import { formatNumber } from '@/utils/format'
 
 type StepTwoProps = {
-  isSetting?: boolean,
+  isSetting?: boolean
   documentDetail?: FullDocumentDetail
-  hasSetAPIKEY: boolean,
-  onSetting: () => void,
-  datasetId?: string,
-  indexingType?: string,
-  file?: File,
-  onStepChange?: (delta: number) => void,
-  updateIndexingTypeCache?: (type: string) => void,
+  hasSetAPIKEY: boolean
+  onSetting: () => void
+  datasetId?: string
+  indexingType?: string
+  file?: File
+  onStepChange?: (delta: number) => void
+  updateIndexingTypeCache?: (type: string) => void
   updateResultCache?: (res: createDocumentResponse) => void
   onSave?: () => void
   onCancel?: () => void
@@ -71,8 +71,10 @@ const StepTwo = ({
   const [defaultConfig, setDefaultConfig] = useState<Rules>()
   const hasSetIndexType = !!indexingType
   const [indexType, setIndexType] = useState<IndexingType>(
-    indexingType ||
-      hasSetAPIKEY ? IndexingType.QUALIFIED : IndexingType.ECONOMICAL
+    indexingType
+      || hasSetAPIKEY
+      ? IndexingType.QUALIFIED
+      : IndexingType.ECONOMICAL,
   )
   const [showPreview, { setTrue: setShowPreview, setFalse: hidePreview }] = useBoolean()
   const [customFileIndexingEstimate, setCustomFileIndexingEstimate] = useState<IndexingEstimateResponse | null>(null)
@@ -82,19 +84,19 @@ const StepTwo = ({
   })()
 
   const scrollHandle = (e: any) => {
-    if (e.target.scrollTop > 0) {
+    if (e.target.scrollTop > 0)
       setScrolled(true)
-    } else {
+
+    else
       setScrolled(false)
-    }
   }
 
   const previewScrollHandle = (e: any) => {
-    if (e.target.scrollTop > 0) {
+    if (e.target.scrollTop > 0)
       setPreviewScrolled(true)
-    } else {
+
+    else
       setPreviewScrolled(false)
-    }
   }
   const getFileName = (name: string) => {
     const arr = name.split('.')
@@ -102,18 +104,17 @@ const StepTwo = ({
   }
 
   const getRuleName = (key: string) => {
-    if (key === 'remove_extra_spaces') {
+    if (key === 'remove_extra_spaces')
       return t('datasetCreation.stepTwo.removeExtraSpaces')
-    }
-    if (key === 'remove_urls_emails') {
+
+    if (key === 'remove_urls_emails')
       return t('datasetCreation.stepTwo.removeUrlEmails')
-    }
-    if (key === 'remove_stopwords') {
+
+    if (key === 'remove_stopwords')
       return t('datasetCreation.stepTwo.removeStopwords')
-    }
   }
   const ruleChangeHandle = (id: string) => {
-    const newRules = rules.map(rule => {
+    const newRules = rules.map((rule) => {
       if (rule.id === id) {
         return {
           id: rule.id,
@@ -132,13 +133,23 @@ const StepTwo = ({
     }
   }
 
+  const fetchFileIndexingEstimate = async () => {
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
+    const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams())
+    if (segmentationType === SegmentType.CUSTOM)
+      setCustomFileIndexingEstimate(res)
+
+    else
+      setAutomaticFileIndexingEstimate(res)
+  }
+
   const confirmChangeCustomConfig = async () => {
     setCustomFileIndexingEstimate(null)
     setShowPreview()
     await fetchFileIndexingEstimate()
   }
 
-  const getIndexing_technique = () => indexingType ? indexingType : indexType
+  const getIndexing_technique = () => indexingType || indexType
 
   const getProcessRule = () => {
     const processRule: any = {
@@ -168,16 +179,6 @@ const StepTwo = ({
     return params
   }
 
-  const fetchFileIndexingEstimate = async () => {
-    const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams())
-    if (segmentationType === SegmentType.CUSTOM) {
-      setCustomFileIndexingEstimate(res)
-    }
-    else {
-      setAutomaticFileIndexingEstimate(res)
-    }
-  }
-
   const getCreationParams = () => {
     let params
     if (isSetting) {
@@ -185,7 +186,8 @@ const StepTwo = ({
         original_document_id: documentDetail?.id,
         process_rule: getProcessRule(),
       } as CreateDocumentReq
-    } else {
+    }
+    else {
       params = {
         data_source: {
           type: 'upload_file',
@@ -226,25 +228,25 @@ const StepTwo = ({
   }
 
   const getDefaultMode = () => {
-    if (documentDetail) {
+    if (documentDetail)
       setSegmentationType(documentDetail.dataset_process_rule.mode)
-    }
   }
 
   const createHandle = async () => {
     try {
-      let res;
+      let res
       const params = getCreationParams()
       if (!datasetId) {
         res = await createFirstDocument({
-          body: params
+          body: params,
         })
         updateIndexingTypeCache && updateIndexingTypeCache(indexType)
         updateResultCache && updateResultCache(res)
-      } else {
+      }
+      else {
         res = await createDocument({
           datasetId,
-          body: params
+          body: params,
         })
         updateIndexingTypeCache && updateIndexingTypeCache(indexType)
         updateResultCache && updateResultCache({
@@ -257,7 +259,7 @@ const StepTwo = ({
     catch (err) {
       Toast.notify({
         type: 'error',
-        message: err + '',
+        message: `${err}`,
       })
     }
   }
@@ -266,35 +268,36 @@ const StepTwo = ({
     // fetch rules
     if (!isSetting) {
       getRules()
-    } else {
+    }
+    else {
       getRulesFromDetail()
       getDefaultMode()
     }
   }, [])
 
   useEffect(() => {
-    scrollRef.current?.addEventListener('scroll', scrollHandle);
+    scrollRef.current?.addEventListener('scroll', scrollHandle)
     return () => {
-      scrollRef.current?.removeEventListener('scroll', scrollHandle);
+      scrollRef.current?.removeEventListener('scroll', scrollHandle)
     }
   }, [])
 
   useLayoutEffect(() => {
     if (showPreview) {
-      previewScrollRef.current?.addEventListener('scroll', previewScrollHandle);
+      previewScrollRef.current?.addEventListener('scroll', previewScrollHandle)
       return () => {
-        previewScrollRef.current?.removeEventListener('scroll', previewScrollHandle);
+        previewScrollRef.current?.removeEventListener('scroll', previewScrollHandle)
       }
     }
   }, [showPreview])
 
   useEffect(() => {
     // get indexing type by props
-    if (indexingType) {
+    if (indexingType)
       setIndexType(indexingType as IndexingType)
-    } else {
+
+    else
       setIndexType(hasSetAPIKEY ? IndexingType.QUALIFIED : IndexingType.ECONOMICAL)
-    }
   }, [hasSetAPIKEY, indexingType, datasetId])
 
   useEffect(() => {
@@ -302,7 +305,8 @@ const StepTwo = ({
       setAutomaticFileIndexingEstimate(null)
       setShowPreview()
       fetchFileIndexingEstimate()
-    } else {
+    }
+    else {
       hidePreview()
       setCustomFileIndexingEstimate(null)
     }
@@ -320,7 +324,7 @@ const StepTwo = ({
               className={cn(
                 s.radioItem,
                 s.segmentationItem,
-                segmentationType === SegmentType.AUTO && s.active
+                segmentationType === SegmentType.AUTO && s.active,
               )}
               onClick={() => setSegmentationType(SegmentType.AUTO)}
             >
@@ -355,7 +359,7 @@ const StepTwo = ({
                         type="text"
                         className={s.input}
                         placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={segmentIdentifier}
-                        onChange={(e) => setSegmentIdentifier(e.target.value)}
+                        onChange={e => setSegmentIdentifier(e.target.value)}
                       />
                     </div>
                   </div>
@@ -366,7 +370,7 @@ const StepTwo = ({
                         type="number"
                         className={s.input}
                         placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={max}
-                        onChange={(e) => setMax(Number(e.target.value))}
+                        onChange={e => setMax(Number(e.target.value))}
                       />
                     </div>
                   </div>
@@ -403,9 +407,8 @@ const StepTwo = ({
                     hasSetIndexType && '!w-full',
                   )}
                   onClick={() => {
-                    if (hasSetAPIKEY) {
+                    if (hasSetAPIKEY)
                       setIndexType(IndexingType.QUALIFIED)
-                    }
                   }}
                 >
                   <span className={cn(s.typeIcon, s.qualified)} />
@@ -418,11 +421,13 @@ const StepTwo = ({
                     <div className={s.tip}>{t('datasetCreation.stepTwo.qualifiedTip')}</div>
                     <div className='pb-0.5 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.emstimateCost')}</div>
                     {
-                      !!fileIndexingEstimate ? (
-                        <div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.tokens)} tokens(<span className='text-yellow-500'>${formatNumber(fileIndexingEstimate.total_price)}</span>)</div>
-                      ) : (
-                        <div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div>
-                      )
+                      fileIndexingEstimate
+                        ? (
+                          <div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.tokens)} tokens(<span className='text-yellow-500'>${formatNumber(fileIndexingEstimate.total_price)}</span>)</div>
+                        )
+                        : (
+                          <div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div>
+                        )
                     }
                   </div>
                   {!hasSetAPIKEY && (
@@ -434,7 +439,6 @@ const StepTwo = ({
                 </div>
               )}
 
-
               {(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.ECONOMICAL)) && (
                 <div
                   className={cn(
@@ -476,51 +480,58 @@ const StepTwo = ({
                 <div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.emstimateSegment')}</div>
                 <div className='flex items-center text-sm leading-6 font-medium text-gray-800'>
                   {
-                    !!fileIndexingEstimate ? (
-                      <div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.total_segments)} </div>
-                    ) : (
-                      <div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div>
-                    )
+                    fileIndexingEstimate
+                      ? (
+                        <div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.total_segments)} </div>
+                      )
+                      : (
+                        <div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div>
+                      )
                   }
                 </div>
               </div>
             </div>
-            {!isSetting ? (
-              <div className='flex items-center mt-8 py-2'>
-                <Button onClick={() => onStepChange && onStepChange(-1)}>{t('datasetCreation.stepTwo.lastStep')}</Button>
-                <div className={s.divider} />
-                <Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.nextStep')}</Button>
-              </div>
-            ) : (
-              <div className='flex items-center mt-8 py-2'>
-                <Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.save')}</Button>
-                <Button className='ml-2' onClick={onCancel}>{t('datasetCreation.stepTwo.cancel')}</Button>
-              </div>
-            )}
+            {!isSetting
+              ? (
+                <div className='flex items-center mt-8 py-2'>
+                  <Button onClick={() => onStepChange && onStepChange(-1)}>{t('datasetCreation.stepTwo.lastStep')}</Button>
+                  <div className={s.divider} />
+                  <Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.nextStep')}</Button>
+                </div>
+              )
+              : (
+                <div className='flex items-center mt-8 py-2'>
+                  <Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.save')}</Button>
+                  <Button className='ml-2' onClick={onCancel}>{t('datasetCreation.stepTwo.cancel')}</Button>
+                </div>
+              )}
           </div>
         </div>
       </div>
-      {(showPreview) ? (
-        <div ref={previewScrollRef} className={cn(s.previewWrap, 'relativeh-full overflow-y-scroll border-l border-[#F2F4F7]')}>
-          <div className={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`, ' flex items-center justify-between px-8')}>
-            <span>{t('datasetCreation.stepTwo.previewTitle')}</span>
-            <div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
-              <XMarkIcon className='h-4 w-4'></XMarkIcon>
+      {(showPreview)
+        ? (
+          <div ref={previewScrollRef} className={cn(s.previewWrap, 'relativeh-full overflow-y-scroll border-l border-[#F2F4F7]')}>
+            <div className={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`, ' flex items-center justify-between px-8')}>
+              <span>{t('datasetCreation.stepTwo.previewTitle')}</span>
+              <div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
+                <XMarkIcon className='h-4 w-4'></XMarkIcon>
+              </div>
+            </div>
+            <div className='my-4 px-8 space-y-4'>
+              {fileIndexingEstimate?.preview
+                ? (
+                  <>
+                    {fileIndexingEstimate?.preview.map((item, index) => (
+                      <PreviewItem key={item} content={item} index={index + 1} />
+                    ))}
+                  </>
+                )
+                : <div className='flex items-center justify-center h-[200px]'><Loading type='area'></Loading></div>
+              }
             </div>
           </div>
-          <div className='my-4 px-8 space-y-4'>
-            {fileIndexingEstimate?.preview ? (
-              <>
-                {fileIndexingEstimate?.preview.map((item, index) => (
-                  <PreviewItem key={item} content={item} index={index + 1} />
-                ))}
-              </>
-            ) : <div className='flex items-center justify-center h-[200px]'><Loading type='area'></Loading></div>
-            }
-          </div>
-        </div>
-      ) :
-        (<div className={cn(s.sideTip)}>
+        )
+        : (<div className={cn(s.sideTip)}>
           <div className={s.tipCard}>
             <span className={s.icon} />
             <div className={s.title}>{t('datasetCreation.stepTwo.sideTipTitle')}</div>

+ 9 - 8
web/app/components/datasets/documents/detail/settings/index.tsx

@@ -1,5 +1,5 @@
 'use client'
-import React, { useState, useCallback, useEffect } from 'react'
+import React, { useEffect, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import { useBoolean } from 'ahooks'
 import { useContext } from 'use-context-selector'
@@ -7,7 +7,8 @@ import { useRouter } from 'next/navigation'
 import DatasetDetailContext from '@/context/dataset-detail'
 import type { FullDocumentDetail } from '@/models/datasets'
 import { fetchTenantInfo } from '@/service/common'
-import { fetchDocumentDetail, MetadataType } from '@/service/datasets'
+import type { MetadataType } from '@/service/datasets'
+import { fetchDocumentDetail } from '@/service/datasets'
 
 import Loading from '@/app/components/base/loading'
 import StepTwo from '@/app/components/datasets/create/step-two'
@@ -15,8 +16,8 @@ import AccountSetting from '@/app/components/header/account-setting'
 import AppUnavailable from '@/app/components/base/app-unavailable'
 
 type DocumentSettingsProps = {
-  datasetId: string;
-  documentId: string;
+  datasetId: string
+  documentId: string
 }
 
 const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
@@ -48,18 +49,18 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
         const detail = await fetchDocumentDetail({
           datasetId,
           documentId,
-          params: { metadata: 'without' as MetadataType }
+          params: { metadata: 'without' as MetadataType },
         })
         setDocumentDetail(detail)
-      } catch (e) {
+      }
+      catch (e) {
         setHasError(true)
       }
     })()
   }, [datasetId, documentId])
 
-  if (hasError) {
+  if (hasError)
     return <AppUnavailable code={500} unknownReason={t('datasetCreation.error.unavailable') as string} />
-  }
 
   return (
     <div className='flex' style={{ height: 'calc(100vh - 56px)' }}>

+ 99 - 81
web/i18n/lang/app-debug.en.ts

@@ -1,16 +1,17 @@
 const translation = {
-  pageTitle: "Prompt Engineering",
+  pageTitle: 'Prompt Engineering',
   operation: {
-    applyConfig: "Publish",
-    resetConfig: "Reset",
-    addFeature: "Add Feature",
-    stopResponding: "Stop responding",
+    applyConfig: 'Publish',
+    resetConfig: 'Reset',
+    addFeature: 'Add Feature',
+    automatic: 'Automatic',
+    stopResponding: 'Stop responding',
   },
   notSetAPIKey: {
-    title: "LLM provider key has not been set",
-    trailFinished: "Trail finished",
-    description: "The LLM provider key has not been set, and it needs to be set before debugging.",
-    settingBtn: "Go to settings",
+    title: 'LLM provider key has not been set',
+    trailFinished: 'Trail finished',
+    description: 'The LLM provider key has not been set, and it needs to be set before debugging.',
+    settingBtn: 'Go to settings',
   },
   trailUseGPT4Info: {
     title: 'Does not support gpt-4 now',
@@ -19,14 +20,14 @@ const translation = {
   feature: {
     groupChat: {
       title: 'Chat enhance',
-      description: 'Add pre-conversation settings for apps can enhance user experience.'
+      description: 'Add pre-conversation settings for apps can enhance user experience.',
     },
     groupExperience: {
       title: 'Experience enhance',
     },
     conversationOpener: {
-      title: "Conversation remakers",
-      description: "In a chat app, the first sentence that the AI actively speaks to the user is usually used as a welcome."
+      title: 'Conversation remakers',
+      description: 'In a chat app, the first sentence that the AI actively speaks to the user is usually used as a welcome.',
     },
     suggestedQuestionsAfterAnswer: {
       title: 'Follow-up',
@@ -35,105 +36,122 @@ const translation = {
       tryToAsk: 'Try to ask',
     },
     moreLikeThis: {
-      title: "More like this",
-      description: "Generate multiple texts at once, and then edit and continue to generate",
-      generateNumTip: "Number of each generated times",
-      tip: "Using this feature will incur additional tokens overhead"
+      title: 'More like this',
+      description: 'Generate multiple texts at once, and then edit and continue to generate',
+      generateNumTip: 'Number of each generated times',
+      tip: 'Using this feature will incur additional tokens overhead',
     },
     dataSet: {
-      title: "Context",
-      noData: "You can import datasets as context",
-      words: "Words",
-      textBlocks: "Text Blocks",
-      selectTitle: "Select reference dataset",
-      selected: "Datasets selected",
-      noDataSet: "No dataset found",
-      toCreate: "Go to create",
-      notSupportSelectMulti: 'Currently only support one dataset'
-    }
+      title: 'Context',
+      noData: 'You can import datasets as context',
+      words: 'Words',
+      textBlocks: 'Text Blocks',
+      selectTitle: 'Select reference dataset',
+      selected: 'Datasets selected',
+      noDataSet: 'No dataset found',
+      toCreate: 'Go to create',
+      notSupportSelectMulti: 'Currently only support one dataset',
+    },
+  },
+  automatic: {
+    title: 'Automated application orchestration',
+    description: 'Describe your scenario, Dify will orchestrate an application for you.',
+    intendedAudience: 'Who is the intended audience?',
+    intendedAudiencePlaceHolder: 'e.g. Student',
+    solveProblem: 'What problems do they hope AI can solve for them?',
+    solveProblemPlaceHolder: 'e.g. Assessing academic performance',
+    generate: 'Generate',
+    audiencesRequired: 'Audiences required',
+    problemRequired: 'Problem required',
+    resTitle: 'We have orchestrated the following application for you.',
+    apply: 'Apply this orchestration',
+    noData: 'Describe your use case on the left, the orchestration preview will show here.',
+    loading: 'Orchestrating the application for you...',
+    overwriteTitle: 'Override existing configuration?',
+    overwriteMessage: 'Applying this orchestration will override existing configuration.',
   },
   resetConfig: {
-    title: "Confirm reset?",
+    title: 'Confirm reset?',
     message:
-      "Reset discards changes, restoring the last published configuration.",
+      'Reset discards changes, restoring the last published configuration.',
   },
   errorMessage: {
-    nameOfKeyRequired: "name of the key: {{key}} required",
-    valueOfVarRequired: "Variables value can not be empty",
-    queryRequired: "Request text is required.",
+    nameOfKeyRequired: 'name of the key: {{key}} required',
+    valueOfVarRequired: 'Variables value can not be empty',
+    queryRequired: 'Request text is required.',
     waitForResponse:
-      "Please wait for the response to the previous message to complete.",
+      'Please wait for the response to the previous message to complete.',
   },
-  chatSubTitle: "Pre Prompt",
-  completionSubTitle: "Prefix Prompt",
+  chatSubTitle: 'Pre Prompt',
+  completionSubTitle: 'Prefix Prompt',
   promptTip:
-    "Prompts guide AI responses with instructions and constraints. Insert variables like {{input}}. This prompt won't be visible to users.",
-  formattingChangedTitle: "Formatting changed",
+    'Prompts guide AI responses with instructions and constraints. Insert variables like {{input}}. This prompt won\'t be visible to users.',
+  formattingChangedTitle: 'Formatting changed',
   formattingChangedText:
-    "Modifying the formatting will reset the debug area, are you sure?",
-  variableTitle: "Variables",
+    'Modifying the formatting will reset the debug area, are you sure?',
+  variableTitle: 'Variables',
   variableTip:
-    "Users fill variables in a form, automatically replacing variables in the prompt.",
-  notSetVar: "Variables allow users to introduce prompt words or opening remarks when filling out forms. You can try entering \"{{input}}\" in the prompt words.",
-  autoAddVar: "Undefined variables referenced in pre-prompt, are you want to add them in user input form?",
+    'Users fill variables in a form, automatically replacing variables in the prompt.',
+  notSetVar: 'Variables allow users to introduce prompt words or opening remarks when filling out forms. You can try entering "{{input}}" in the prompt words.',
+  autoAddVar: 'Undefined variables referenced in pre-prompt, are you want to add them in user input form?',
   variableTable: {
-    key: "Variable Key",
-    name: "User Input Field Name",
-    optional: "Optional",
-    type: "Input Type",
-    action: "Actions",
-    typeString: "String",
-    typeSelect: "Select",
+    key: 'Variable Key',
+    name: 'User Input Field Name',
+    optional: 'Optional',
+    type: 'Input Type',
+    action: 'Actions',
+    typeString: 'String',
+    typeSelect: 'Select',
   },
   varKeyError: {
-    canNoBeEmpty: "Variable key can not be empty",
-    tooLong: "Variable key: {{key}} too length. Can not be longer then 16 characters",
-    notValid: "Variable key: {{key}} is invalid. Can only contain letters, numbers, and underscores",
-    notStartWithNumber: "Variable key: {{key}} can not start with a number",
+    canNoBeEmpty: 'Variable key can not be empty',
+    tooLong: 'Variable key: {{key}} too length. Can not be longer then 16 characters',
+    notValid: 'Variable key: {{key}} is invalid. Can only contain letters, numbers, and underscores',
+    notStartWithNumber: 'Variable key: {{key}} can not start with a number',
   },
   variableConig: {
-    modalTitle: "Field settings",
-    description: "Setting for variable {{varName}}",
+    modalTitle: 'Field settings',
+    description: 'Setting for variable {{varName}}',
     fieldType: 'Field type',
     string: 'Text',
     select: 'Select',
     notSet: 'Not set, try typing {{input}} in the prefix prompt',
-    stringTitle: "Form text box options",
-    maxLength: "Max length",
-    options: "Options",
-    addOption: "Add option",
+    stringTitle: 'Form text box options',
+    maxLength: 'Max length',
+    options: 'Options',
+    addOption: 'Add option',
   },
   openingStatement: {
-    title: "Opening remarks",
-    add: "Add",
-    writeOpner: "Write remarks",
-    placeholder: "Write your remarks message here",
+    title: 'Opening remarks',
+    add: 'Add',
+    writeOpner: 'Write remarks',
+    placeholder: 'Write your remarks message here',
     noDataPlaceHolder:
-      "Starting the conversation with the user can help AI establish a closer connection with them in conversational applications.",
+      'Starting the conversation with the user can help AI establish a closer connection with them in conversational applications.',
     varTip: 'You can use variables, try type {{variable}}',
-    tooShort: "At least 20 words of initial prompt are required to generate an opening remarks for the conversation.",
-    notIncludeKey: "The initial prompt does not include the variable: {{key}}. Please add it to the initial prompt.",
+    tooShort: 'At least 20 words of initial prompt are required to generate an opening remarks for the conversation.',
+    notIncludeKey: 'The initial prompt does not include the variable: {{key}}. Please add it to the initial prompt.',
   },
   modelConfig: {
-    model: "Model",
-    setTone: "Set tone of responses",
-    title: "Model and Parameters",
+    model: 'Model',
+    setTone: 'Set tone of responses',
+    title: 'Model and Parameters',
   },
   inputs: {
-    title: "Debugging and Previewing",
-    noPrompt: "Try write some prompt in pre-prompt input",
-    userInputField: "User Input Field",
-    noVar: "Fill in the value of the variable, which will be automatically replaced in the prompt word every time a new session is started.",
+    title: 'Debugging and Previewing',
+    noPrompt: 'Try write some prompt in pre-prompt input',
+    userInputField: 'User Input Field',
+    noVar: 'Fill in the value of the variable, which will be automatically replaced in the prompt word every time a new session is started.',
     chatVarTip:
-      "Fill in the value of the variable, which will be automatically replaced in the prompt word every time a new session is started",
+      'Fill in the value of the variable, which will be automatically replaced in the prompt word every time a new session is started',
     completionVarTip:
-      "Fill in the value of the variable, which will be automatically replaced in the prompt words every time a question is submitted.",
-    previewTitle: "Prompt preview",
-    queryTitle: "Query content",
-    queryPlaceholder: "Please enter the request text.",
-    run: "RUN",
+      'Fill in the value of the variable, which will be automatically replaced in the prompt words every time a question is submitted.',
+    previewTitle: 'Prompt preview',
+    queryTitle: 'Query content',
+    queryPlaceholder: 'Please enter the request text.',
+    run: 'RUN',
   },
-  result: "Output Text",
-};
+  result: 'Output Text',
+}
 
-export default translation;
+export default translation

+ 98 - 80
web/i18n/lang/app-debug.zh.ts

@@ -1,16 +1,17 @@
 const translation = {
-  pageTitle: "提示词编排",
+  pageTitle: '提示词编排',
   operation: {
-    applyConfig: "发布",
-    resetConfig: "重置",
-    addFeature: "添加功能",
-    stopResponding: "停止响应",
+    applyConfig: '发布',
+    resetConfig: '重置',
+    addFeature: '添加功能',
+    automatic: '自动编排',
+    stopResponding: '停止响应',
   },
   notSetAPIKey: {
-    title: "LLM 提供者的密钥未设置",
-    trailFinished: "试用已结束",
-    description: "在调试之前需要设置 LLM 提供者的密钥。",
-    settingBtn: "去设置",
+    title: 'LLM 提供者的密钥未设置',
+    trailFinished: '试用已结束',
+    description: '在调试之前需要设置 LLM 提供者的密钥。',
+    settingBtn: '去设置',
   },
   trailUseGPT4Info: {
     title: '当前不支持使用 gpt-4',
@@ -19,14 +20,14 @@ const translation = {
   feature: {
     groupChat: {
       title: '聊天增强',
-      description: '为聊天型应用添加预对话设置,可以提升用户体验。'
+      description: '为聊天型应用添加预对话设置,可以提升用户体验。',
     },
     groupExperience: {
       title: '体验增强',
     },
     conversationOpener: {
-      title: "对话开场白",
-      description: "在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。"
+      title: '对话开场白',
+      description: '在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。',
     },
     suggestedQuestionsAfterAnswer: {
       title: '下一步问题建议',
@@ -35,100 +36,117 @@ const translation = {
       tryToAsk: '试着问问',
     },
     moreLikeThis: {
-      title: "更多类似的",
+      title: '更多类似的',
       description: '一次生成多条文本,可在此基础上编辑并继续生成',
-      generateNumTip: "每次生成数",
-      tip: "使用此功能将会额外消耗 tokens"
+      generateNumTip: '每次生成数',
+      tip: '使用此功能将会额外消耗 tokens',
     },
     dataSet: {
-      title: "上下文",
-      noData: "您可以导入数据集作为上下文",
-      words: "词",
-      textBlocks: "文本块",
-      selectTitle: "选择引用数据集",
-      selected: "个数据集被选中",
-      noDataSet: "未找到数据集",
-      toCreate: "去创建",
-      notSupportSelectMulti: '目前只支持引用一个数据集'
-    }
+      title: '上下文',
+      noData: '您可以导入数据集作为上下文',
+      words: '词',
+      textBlocks: '文本块',
+      selectTitle: '选择引用数据集',
+      selected: '个数据集被选中',
+      noDataSet: '未找到数据集',
+      toCreate: '去创建',
+      notSupportSelectMulti: '目前只支持引用一个数据集',
+    },
+  },
+  automatic: {
+    title: '自动编排',
+    description: '描述您的场景,Dify 将为您编排一个应用。',
+    intendedAudience: '目标用户是谁?',
+    intendedAudiencePlaceHolder: '例如:学生',
+    solveProblem: '希望 AI 为他们解决什么问题?',
+    solveProblemPlaceHolder: '例如:评估学业水平',
+    generate: '生成',
+    audiencesRequired: '目标用户必填',
+    problemRequired: '解决问题必填',
+    resTitle: '我们为您编排了以下应用程序',
+    apply: '应用',
+    noData: '在左侧描述您的用例,编排预览将在此处显示。',
+    loading: '为您编排应用程序中…',
+    overwriteTitle: '覆盖现有配置?',
+    overwriteMessage: '应用此编排将覆盖现有配置。',
   },
   resetConfig: {
-    title: "确认重置?",
-    message: "重置将丢失当前页面所有修改,恢复至上次发布时的配置",
+    title: '确认重置?',
+    message: '重置将丢失当前页面所有修改,恢复至上次发布时的配置',
   },
   errorMessage: {
-    nameOfKeyRequired: "变量 {{key}} 对应的名称必填",
-    valueOfVarRequired: "变量值必填",
-    queryRequired: "主要文本必填",
-    waitForResponse: "请等待上条信息响应完成",
+    nameOfKeyRequired: '变量 {{key}} 对应的名称必填',
+    valueOfVarRequired: '变量值必填',
+    queryRequired: '主要文本必填',
+    waitForResponse: '请等待上条信息响应完成',
   },
-  chatSubTitle: "对话前提示词",
-  completionSubTitle: "前缀提示词",
+  chatSubTitle: '对话前提示词',
+  completionSubTitle: '前缀提示词',
   promptTip:
-    "提示词用于对 AI 的回复做出一系列指令和约束。可插入表单变量,例如 {{input}}。这段提示词不会被最终用户所看到。",
-  formattingChangedTitle: "编排已改变",
-  formattingChangedText: "修改编排将重置调试区域,确定吗?",
-  variableTitle: "变量",
-  notSetVar: "变量能使用户输入表单引入提示词或开场白,你可以试试在提示词中输入输入 {{input}}",
+    '提示词用于对 AI 的回复做出一系列指令和约束。可插入表单变量,例如 {{input}}。这段提示词不会被最终用户所看到。',
+  formattingChangedTitle: '编排已改变',
+  formattingChangedText: '修改编排将重置调试区域,确定吗?',
+  variableTitle: '变量',
+  notSetVar: '变量能使用户输入表单引入提示词或开场白,你可以试试在提示词中输入输入 {{input}}',
   variableTip:
-    "变量将以表单形式让用户在对话前填写,用户填写的表单内容将自动替换提示词中的变量。",
-  autoAddVar: "提示词中引用了未定义的变量,是否自动添加到用户输入表单中?",
+    '变量将以表单形式让用户在对话前填写,用户填写的表单内容将自动替换提示词中的变量。',
+  autoAddVar: '提示词中引用了未定义的变量,是否自动添加到用户输入表单中?',
   variableTable: {
-    key: "变量 Key",
-    name: "字段名称",
-    optional: "可选",
-    type: "类型",
-    action: "操作",
-    typeString: "文本",
-    typeSelect: "下拉选项",
+    key: '变量 Key',
+    name: '字段名称',
+    optional: '可选',
+    type: '类型',
+    action: '操作',
+    typeString: '文本',
+    typeSelect: '下拉选项',
   },
   varKeyError: {
-    canNoBeEmpty: "变量不能为空",
-    tooLong: "变量: {{key}} 长度太长。不能超过 16 个字符",
-    notValid: "变量: {{key}} 非法。只能包含英文字符,数字和下划线",
-    notStartWithNumber: "变量: {{key}} 不能以数字开头",
+    canNoBeEmpty: '变量不能为空',
+    tooLong: '变量: {{key}} 长度太长。不能超过 16 个字符',
+    notValid: '变量: {{key}} 非法。只能包含英文字符,数字和下划线',
+    notStartWithNumber: '变量: {{key}} 不能以数字开头',
   },
   variableConig: {
-    modalTitle: "变量设置",
-    description: "设置变量 {{varName}}",
+    modalTitle: '变量设置',
+    description: '设置变量 {{varName}}',
     fieldType: '字段类型',
     string: '文本',
     select: '下拉选项',
     notSet: '未设置,在 Prompt 中输入 {{input}} 试试',
-    stringTitle: "文本框设置",
-    maxLength: "最大长度",
-    options: "选项",
-    addOption: "添加选项",
+    stringTitle: '文本框设置',
+    maxLength: '最大长度',
+    options: '选项',
+    addOption: '添加选项',
   },
   openingStatement: {
-    title: "对话开场白",
-    add: "添加开场白",
-    writeOpner: "编写开场白",
-    placeholder: "请在这里输入开场白",
+    title: '对话开场白',
+    add: '添加开场白',
+    writeOpner: '编写开场白',
+    placeholder: '请在这里输入开场白',
     noDataPlaceHolder:
-      "在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。",
+      '在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。',
     varTip: '你可以使用变量, 试试输入 {{variable}}',
-    tooShort: "对话前提示词至少 20 字才能生成开场白",
-    notIncludeKey: "前缀提示词中不包含变量 {{key}}。请在前缀提示词中添加该变量",
+    tooShort: '对话前提示词至少 20 字才能生成开场白',
+    notIncludeKey: '前缀提示词中不包含变量 {{key}}。请在前缀提示词中添加该变量',
   },
   modelConfig: {
-    model: "语言模型",
-    setTone: "模型设置",
-    title: "模型及参数",
+    model: '语言模型',
+    setTone: '模型设置',
+    title: '模型及参数',
   },
   inputs: {
-    title: "调试与预览",
-    noPrompt: "尝试在对话前提示框中编写一些提示词",
-    userInputField: "用户输入",
-    noVar: "填入变量的值,每次启动新会话时该变量将自动替换提示词中的变量。",
-    chatVarTip: "填入变量的值,该值将在每次开启一个新会话时自动替换到提示词中",
-    completionVarTip: "填入变量的值,该值将在每次提交问题时自动替换到提示词中",
-    previewTitle: "提示词预览",
-    queryTitle: "查询内容",
-    queryPlaceholder: "请输入文本内容",
-    run: "运行",
+    title: '调试与预览',
+    noPrompt: '尝试在对话前提示框中编写一些提示词',
+    userInputField: '用户输入',
+    noVar: '填入变量的值,每次启动新会话时该变量将自动替换提示词中的变量。',
+    chatVarTip: '填入变量的值,该值将在每次开启一个新会话时自动替换到提示词中',
+    completionVarTip: '填入变量的值,该值将在每次提交问题时自动替换到提示词中',
+    previewTitle: '提示词预览',
+    queryTitle: '查询内容',
+    queryPlaceholder: '请输入文本内容',
+    run: '运行',
   },
-  result: "结果",
-};
+  result: '结果',
+}
 
-export default translation;
+export default translation

+ 2 - 2
web/i18n/lang/dataset-creation.en.ts

@@ -102,8 +102,8 @@ const translation = {
     sideTipContent: 'After the document finishes indexing, the dataset can be integrated into the application as context, you can find the context setting in the prompt orchestration page. You can also create it as an independent ChatGPT indexing plugin for release.',
     modelTitle: 'Are you sure to stop embedding?',
     modelContent: 'If you need to resume processing later, you will continue from where you left off.',
-    modelButtonConfirm: "Confirm",
-    modelButtonCancel: 'Cancel'
+    modelButtonConfirm: 'Confirm',
+    modelButtonCancel: 'Cancel',
   },
 }
 

+ 3 - 3
web/i18n/lang/dataset-creation.zh.ts

@@ -101,9 +101,9 @@ const translation = {
     sideTipTitle: '接下来做什么',
     sideTipContent: '当文档完成索引处理后,数据集即可集成至应用内作为上下文使用,你可以在提示词编排页找到上下文设置。你也可以创建成可独立使用的 ChatGPT 索引插件发布。',
     modelTitle: '确认停止索引过程吗?',
-    modelContent:'如果您需要稍后恢复处理,则从停止处继续。',
-    modelButtonConfirm: "确认停止",
-    modelButtonCancel: '取消'
+    modelContent: '如果您需要稍后恢复处理,则从停止处继续。',
+    modelButtonConfirm: '确认停止',
+    modelButtonCancel: '取消',
   },
 }
 

+ 15 - 8
web/service/debug.ts

@@ -1,16 +1,17 @@
-import { ssePost, get, IOnData, IOnCompleted, IOnError } from './base'
+import type { IOnCompleted, IOnData, IOnError } from './base'
+import { get, post, ssePost } from './base'
 
 export const sendChatMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onError, getAbortController }: {
   onData: IOnData
   onCompleted: IOnCompleted
-  onError: IOnError,
+  onError: IOnError
   getAbortController?: (abortController: AbortController) => void
 }) => {
   return ssePost(`apps/${appId}/chat-messages`, {
     body: {
       ...body,
-      response_mode: 'streaming'
-    }
+      response_mode: 'streaming',
+    },
   }, { onData, onCompleted, onError, getAbortController })
 }
 
@@ -22,8 +23,8 @@ export const sendCompletionMessage = async (appId: string, body: Record<string,
   return ssePost(`apps/${appId}/completion-messages`, {
     body: {
       ...body,
-      response_mode: 'streaming'
-    }
+      response_mode: 'streaming',
+    },
   }, { onData, onCompleted, onError })
 }
 
@@ -34,7 +35,13 @@ export const fetchSuggestedQuestions = (appId: string, messageId: string) => {
 export const fetchConvesationMessages = (appId: string, conversation_id: string) => {
   return get(`apps/${appId}/chat-messages`, {
     params: {
-      conversation_id
-    }
+      conversation_id,
+    },
+  })
+}
+
+export const generateRule = (body: Record<string, any>) => {
+  return post('/rule-generate', {
+    body,
   })
 }