Wangbo 4 veckor sedan
förälder
incheckning
9948c55f42
100 ändrade filer med 6744 tillägg och 0 borttagningar
  1. 11 0
      app/components/database/api/index.tsx
  2. 69 0
      app/components/database/common/check-rerank-model.ts
  3. 40 0
      app/components/database/common/economical-retrieval-method-config/index.tsx
  4. 124 0
      app/components/database/common/retrieval-method-config/index.tsx
  5. 64 0
      app/components/database/common/retrieval-method-info/index.tsx
  6. 309 0
      app/components/database/common/retrieval-param-config/index.tsx
  7. 3 0
      app/components/database/create/assets/Icon-3-dots.svg
  8. 16 0
      app/components/database/create/assets/Loading.svg
  9. 3 0
      app/components/database/create/assets/alert-triangle.svg
  10. 3 0
      app/components/database/create/assets/annotation-info.svg
  11. 3 0
      app/components/database/create/assets/arrow-narrow-left.svg
  12. 4 0
      app/components/database/create/assets/book-open-01.svg
  13. 3 0
      app/components/database/create/assets/check.svg
  14. 3 0
      app/components/database/create/assets/close.svg
  15. 22 0
      app/components/database/create/assets/csv.svg
  16. 22 0
      app/components/database/create/assets/doc.svg
  17. 23 0
      app/components/database/create/assets/docx.svg
  18. 4 0
      app/components/database/create/assets/file.svg
  19. 3 0
      app/components/database/create/assets/folder-plus.svg
  20. 23 0
      app/components/database/create/assets/html.svg
  21. BIN
      app/components/database/create/assets/jina.png
  22. 23 0
      app/components/database/create/assets/json.svg
  23. 18 0
      app/components/database/create/assets/md.svg
  24. 2 0
      app/components/database/create/assets/normal.svg
  25. 12 0
      app/components/database/create/assets/notion.svg
  26. 22 0
      app/components/database/create/assets/pdf.svg
  27. 2 0
      app/components/database/create/assets/piggy-bank-01.svg
  28. 8 0
      app/components/database/create/assets/sliders-02.svg
  29. 11 0
      app/components/database/create/assets/star-07.svg
  30. 11 0
      app/components/database/create/assets/star.svg
  31. 3 0
      app/components/database/create/assets/trash.svg
  32. 23 0
      app/components/database/create/assets/txt.svg
  33. 23 0
      app/components/database/create/assets/unknown.svg
  34. 4 0
      app/components/database/create/assets/upload-cloud-01.svg
  35. 4 0
      app/components/database/create/assets/web.svg
  36. 18 0
      app/components/database/create/assets/xlsx.svg
  37. 6 0
      app/components/database/create/assets/zap-fast.svg
  38. 115 0
      app/components/database/create/embedding-process/index.module.css
  39. 272 0
      app/components/database/create/embedding-process/index.tsx
  40. 38 0
      app/components/database/create/empty-dataset-creation-modal/index.module.css
  41. 71 0
      app/components/database/create/empty-dataset-creation-modal/index.tsx
  42. 52 0
      app/components/database/create/file-preview/index.module.css
  43. 69 0
      app/components/database/create/file-preview/index.tsx
  44. 196 0
      app/components/database/create/file-uploader/index.module.css
  45. 306 0
      app/components/database/create/file-uploader/index.tsx
  46. 0 0
      app/components/database/create/index.module.css
  47. 175 0
      app/components/database/create/index.tsx
  48. 54 0
      app/components/database/create/notion-page-preview/index.module.css
  49. 74 0
      app/components/database/create/notion-page-preview/index.tsx
  50. 158 0
      app/components/database/create/step-one/index.module.css
  51. 264 0
      app/components/database/create/step-one/index.tsx
  52. 75 0
      app/components/database/create/step-three/index.module.css
  53. 64 0
      app/components/database/create/step-three/index.tsx
  54. 18 0
      app/components/database/create/step-two/escape.ts
  55. 435 0
      app/components/database/create/step-two/index.module.css
  56. 1026 0
      app/components/database/create/step-two/index.tsx
  57. 47 0
      app/components/database/create/step-two/language-select/index.tsx
  58. 78 0
      app/components/database/create/step-two/preview-item/index.tsx
  59. 54 0
      app/components/database/create/step-two/unescape.ts
  60. 107 0
      app/components/database/create/steps-nav-bar/index.module.css
  61. 61 0
      app/components/database/create/steps-nav-bar/index.tsx
  62. 37 0
      app/components/database/create/stop-embedding-modal/index.module.css
  63. 45 0
      app/components/database/create/stop-embedding-modal/index.tsx
  64. 40 0
      app/components/database/create/website/base/checkbox-with-label.tsx
  65. 40 0
      app/components/database/create/website/base/crawled-result-item.tsx
  66. 87 0
      app/components/database/create/website/base/crawled-result.tsx
  67. 37 0
      app/components/database/create/website/base/crawling.tsx
  68. 30 0
      app/components/database/create/website/base/error-message.tsx
  69. 54 0
      app/components/database/create/website/base/field.tsx
  70. 58 0
      app/components/database/create/website/base/input.tsx
  71. 24 0
      app/components/database/create/website/base/mock-crawl-result.ts
  72. 55 0
      app/components/database/create/website/base/options-wrap.tsx
  73. 48 0
      app/components/database/create/website/base/url-input.tsx
  74. 42 0
      app/components/database/create/website/firecrawl/header.tsx
  75. 218 0
      app/components/database/create/website/firecrawl/index.tsx
  76. 83 0
      app/components/database/create/website/firecrawl/options.tsx
  77. 6 0
      app/components/database/create/website/index.module.css
  78. 138 0
      app/components/database/create/website/index.tsx
  79. 40 0
      app/components/database/create/website/jina-reader/base/checkbox-with-label.tsx
  80. 30 0
      app/components/database/create/website/jina-reader/base/error-message.tsx
  81. 54 0
      app/components/database/create/website/jina-reader/base/field.tsx
  82. 58 0
      app/components/database/create/website/jina-reader/base/input.tsx
  83. 55 0
      app/components/database/create/website/jina-reader/base/options-wrap.tsx
  84. 48 0
      app/components/database/create/website/jina-reader/base/url-input.tsx
  85. 40 0
      app/components/database/create/website/jina-reader/crawled-result-item.tsx
  86. 87 0
      app/components/database/create/website/jina-reader/crawled-result.tsx
  87. 37 0
      app/components/database/create/website/jina-reader/crawling.tsx
  88. 42 0
      app/components/database/create/website/jina-reader/header.tsx
  89. 232 0
      app/components/database/create/website/jina-reader/index.tsx
  90. 24 0
      app/components/database/create/website/jina-reader/mock-crawl-result.ts
  91. 59 0
      app/components/database/create/website/jina-reader/options.tsx
  92. 57 0
      app/components/database/create/website/no-data.tsx
  93. 41 0
      app/components/database/create/website/preview.tsx
  94. 10 0
      app/components/database/documents/assets/atSign.svg
  95. 3 0
      app/components/database/documents/assets/bezierCurve.svg
  96. 3 0
      app/components/database/documents/assets/bookOpen.svg
  97. 3 0
      app/components/database/documents/assets/briefcase.svg
  98. 15 0
      app/components/database/documents/assets/cardLoading.svg
  99. 3 0
      app/components/database/documents/assets/file.svg
  100. 10 0
      app/components/database/documents/assets/globe.svg

+ 11 - 0
app/components/database/api/index.tsx

@@ -0,0 +1,11 @@
+import React from 'react'
+
+type Props = {}
+
+const index = (props: Props) => {
+  return (
+    <div>index</div>
+  )
+}
+
+export default index

+ 69 - 0
app/components/database/common/check-rerank-model.ts

@@ -0,0 +1,69 @@
+import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'
+import type {
+  DefaultModelResponse,
+  Model,
+} from '@/app/components/header/account-setting/model-provider-page/declarations'
+import { RerankingModeEnum } from '@/models/datasets'
+
+export const isReRankModelSelected = ({
+  rerankDefaultModel,
+  isRerankDefaultModelValid,
+  retrievalConfig,
+  rerankModelList,
+  indexMethod,
+}: {
+  rerankDefaultModel?: DefaultModelResponse
+  isRerankDefaultModelValid: boolean
+  retrievalConfig: RetrievalConfig
+  rerankModelList: Model[]
+  indexMethod?: string
+}) => {
+  const rerankModelSelected = (() => {
+    if (retrievalConfig.reranking_model?.reranking_model_name) {
+      const provider = rerankModelList.find(({ provider }) => provider === retrievalConfig.reranking_model?.reranking_provider_name)
+
+      return provider?.models.find(({ model }) => model === retrievalConfig.reranking_model?.reranking_model_name)
+    }
+
+    if (isRerankDefaultModelValid)
+      return !!rerankDefaultModel
+
+    return false
+  })()
+
+  if (
+    indexMethod === 'high_quality'
+    && (retrievalConfig.search_method === RETRIEVE_METHOD.hybrid && retrievalConfig.reranking_mode !== RerankingModeEnum.WeightedScore)
+    && !rerankModelSelected
+  )
+    return false
+
+  return true
+}
+
+export const ensureRerankModelSelected = ({
+  rerankDefaultModel,
+  indexMethod,
+  retrievalConfig,
+}: {
+  rerankDefaultModel: DefaultModelResponse
+  retrievalConfig: RetrievalConfig
+  indexMethod?: string
+}) => {
+  const rerankModel = retrievalConfig.reranking_model?.reranking_model_name ? retrievalConfig.reranking_model : undefined
+  if (
+    indexMethod === 'high_quality'
+    && (retrievalConfig.reranking_enable || retrievalConfig.search_method === RETRIEVE_METHOD.hybrid)
+    && !rerankModel
+    && rerankDefaultModel
+  ) {
+    return {
+      ...retrievalConfig,
+      reranking_model: {
+        reranking_provider_name: rerankDefaultModel.provider.provider,
+        reranking_model_name: rerankDefaultModel.model,
+      },
+    }
+  }
+  return retrievalConfig
+}

+ 40 - 0
app/components/database/common/economical-retrieval-method-config/index.tsx

@@ -0,0 +1,40 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import RetrievalParamConfig from '../retrieval-param-config'
+import { RETRIEVE_METHOD } from '@/types/app'
+import RadioCard from '@/app/components/base/radio-card'
+import { HighPriority } from '@/app/components/base/icons/src/vender/solid/arrows'
+import type { RetrievalConfig } from '@/types/app'
+
+type Props = {
+  value: RetrievalConfig
+  onChange: (value: RetrievalConfig) => void
+}
+
+const EconomicalRetrievalMethodConfig: FC<Props> = ({
+  value,
+  onChange,
+}) => {
+  const { t } = useTranslation()
+
+  return (
+    <div className='space-y-2'>
+      <RadioCard
+        icon={<HighPriority className='w-4 h-4 text-[#7839EE]' />}
+        title={t('dataset.retrieval.invertedIndex.title')}
+        description={t('dataset.retrieval.invertedIndex.description')}
+        noRadio
+        chosenConfig={
+          <RetrievalParamConfig
+            type={RETRIEVE_METHOD.invertedIndex}
+            value={value}
+            onChange={onChange}
+          />
+        }
+      />
+    </div>
+  )
+}
+export default React.memo(EconomicalRetrievalMethodConfig)

+ 124 - 0
app/components/database/common/retrieval-method-config/index.tsx

@@ -0,0 +1,124 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import RetrievalParamConfig from '../retrieval-param-config'
+import type { RetrievalConfig } from '@/types/app'
+import { RETRIEVE_METHOD } from '@/types/app'
+import RadioCard from '@/app/components/base/radio-card'
+import { PatternRecognition, Semantic } from '@/app/components/base/icons/src/vender/solid/development'
+import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files'
+import { useProviderContext } from '@/context/provider-context'
+import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
+import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
+import {
+  DEFAULT_WEIGHTED_SCORE,
+  RerankingModeEnum,
+  WeightedScoreEnum,
+} from '@/models/datasets'
+
+type Props = {
+  value: RetrievalConfig
+  onChange: (value: RetrievalConfig) => void
+}
+
+const RetrievalMethodConfig: FC<Props> = ({
+  value: passValue,
+  onChange,
+}) => {
+  const { t } = useTranslation()
+  const { supportRetrievalMethods } = useProviderContext()
+  const { data: rerankDefaultModel } = useDefaultModel(ModelTypeEnum.rerank)
+  const value = (() => {
+    if (!passValue.reranking_model.reranking_model_name) {
+      return {
+        ...passValue,
+        reranking_model: {
+          reranking_provider_name: rerankDefaultModel?.provider.provider || '',
+          reranking_model_name: rerankDefaultModel?.model || '',
+        },
+        reranking_mode: passValue.reranking_mode || (rerankDefaultModel ? RerankingModeEnum.RerankingModel : RerankingModeEnum.WeightedScore),
+        weights: passValue.weights || {
+          weight_type: WeightedScoreEnum.Customized,
+          vector_setting: {
+            vector_weight: DEFAULT_WEIGHTED_SCORE.other.semantic,
+            embedding_provider_name: '',
+            embedding_model_name: '',
+          },
+          keyword_setting: {
+            keyword_weight: DEFAULT_WEIGHTED_SCORE.other.keyword,
+          },
+        },
+      }
+    }
+    return passValue
+  })()
+  return (
+    <div className='space-y-2'>
+      {supportRetrievalMethods.includes(RETRIEVE_METHOD.semantic) && (
+        <RadioCard
+          icon={<Semantic className='w-4 h-4 text-[#7839EE]' />}
+          title={t('dataset.retrieval.semantic_search.title')}
+          description={t('dataset.retrieval.semantic_search.description')}
+          isChosen={value.search_method === RETRIEVE_METHOD.semantic}
+          onChosen={() => onChange({
+            ...value,
+            search_method: RETRIEVE_METHOD.semantic,
+          })}
+          chosenConfig={
+            <RetrievalParamConfig
+              type={RETRIEVE_METHOD.semantic}
+              value={value}
+              onChange={onChange}
+            />
+          }
+        />
+      )}
+      {supportRetrievalMethods.includes(RETRIEVE_METHOD.semantic) && (
+        <RadioCard
+          icon={<FileSearch02 className='w-4 h-4 text-[#7839EE]' />}
+          title={t('dataset.retrieval.full_text_search.title')}
+          description={t('dataset.retrieval.full_text_search.description')}
+          isChosen={value.search_method === RETRIEVE_METHOD.fullText}
+          onChosen={() => onChange({
+            ...value,
+            search_method: RETRIEVE_METHOD.fullText,
+          })}
+          chosenConfig={
+            <RetrievalParamConfig
+              type={RETRIEVE_METHOD.fullText}
+              value={value}
+              onChange={onChange}
+            />
+          }
+        />
+      )}
+      {supportRetrievalMethods.includes(RETRIEVE_METHOD.semantic) && (
+        <RadioCard
+          icon={<PatternRecognition className='w-4 h-4 text-[#7839EE]' />}
+          title={
+            <div className='flex items-center space-x-1'>
+              <div>{t('dataset.retrieval.hybrid_search.title')}</div>
+              <div className='flex h-full items-center px-1.5 rounded-md border border-[#E0EAFF] text-xs font-medium text-[#444CE7]'>{t('dataset.retrieval.hybrid_search.recommend')}</div>
+            </div>
+          }
+          description={t('dataset.retrieval.hybrid_search.description')}
+          isChosen={value.search_method === RETRIEVE_METHOD.hybrid}
+          onChosen={() => onChange({
+            ...value,
+            search_method: RETRIEVE_METHOD.hybrid,
+            reranking_enable: true,
+          })}
+          chosenConfig={
+            <RetrievalParamConfig
+              type={RETRIEVE_METHOD.hybrid}
+              value={value}
+              onChange={onChange}
+            />
+          }
+        />
+      )}
+    </div>
+  )
+}
+export default React.memo(RetrievalMethodConfig)

+ 64 - 0
app/components/database/common/retrieval-method-info/index.tsx

@@ -0,0 +1,64 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import type { RetrievalConfig } from '@/types/app'
+import { RETRIEVE_METHOD } from '@/types/app'
+import RadioCard from '@/app/components/base/radio-card'
+import { HighPriority } from '@/app/components/base/icons/src/vender/solid/arrows'
+import { PatternRecognition, Semantic } from '@/app/components/base/icons/src/vender/solid/development'
+import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files'
+
+type Props = {
+  value: RetrievalConfig
+}
+
+export const getIcon = (type: RETRIEVE_METHOD) => {
+  return ({
+    [RETRIEVE_METHOD.semantic]: Semantic,
+    [RETRIEVE_METHOD.fullText]: FileSearch02,
+    [RETRIEVE_METHOD.hybrid]: PatternRecognition,
+    [RETRIEVE_METHOD.invertedIndex]: HighPriority,
+  })[type] || FileSearch02
+}
+
+const EconomicalRetrievalMethodConfig: FC<Props> = ({
+  // type,
+  value,
+}) => {
+  const { t } = useTranslation()
+  const type = value.search_method
+  const Icon = getIcon(type)
+  return (
+    <div className='space-y-2'>
+      <RadioCard
+        icon={<Icon className='w-4 h-4 text-[#7839EE]' />}
+        title={t(`dataset.retrieval.${type}.title`)}
+        description={t(`dataset.retrieval.${type}.description`)}
+        noRadio
+        chosenConfigWrapClassName='!pb-3'
+        chosenConfig={
+          <div className='flex flex-wrap leading-[18px] text-xs font-normal'>
+            {value.reranking_model.reranking_model_name && (
+              <div className='mr-8 flex space-x-1'>
+                <div className='text-gray-500'>{t('common.modelProvider.rerankModel.key')}</div>
+                <div className='font-medium text-gray-800'>{value.reranking_model.reranking_model_name}</div>
+              </div>
+            )}
+
+            <div className='mr-8 flex space-x-1'>
+              <div className='text-gray-500'>{t('appDebug.datasetConfig.top_k')}</div>
+              <div className='font-medium text-gray-800'>{value.top_k}</div>
+            </div>
+
+            <div className='mr-8 flex space-x-1'>
+              <div className='text-gray-500'>{t('appDebug.datasetConfig.score_threshold')}</div>
+              <div className='font-medium text-gray-800'>{value.score_threshold}</div>
+            </div>
+          </div>
+        }
+      />
+    </div>
+  )
+}
+export default React.memo(EconomicalRetrievalMethodConfig)

+ 309 - 0
app/components/database/common/retrieval-param-config/index.tsx

@@ -0,0 +1,309 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+
+import cn from '@/utils/classnames'
+import TopKItem from '@/app/components/base/param-item/top-k-item'
+import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item'
+import { RETRIEVE_METHOD } from '@/types/app'
+import Switch from '@/app/components/base/switch'
+import Tooltip from '@/app/components/base/tooltip'
+import type { RetrievalConfig } from '@/types/app'
+import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
+import { useCurrentProviderAndModel, useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
+import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
+import {
+  DEFAULT_WEIGHTED_SCORE,
+  RerankingModeEnum,
+  WeightedScoreEnum,
+} from '@/models/datasets'
+import WeightedScore from '@/app/components/app/configuration/dataset-config/params-config/weighted-score'
+import Toast from '@/app/components/base/toast'
+
+type Props = {
+  type: RETRIEVE_METHOD
+  value: RetrievalConfig
+  onChange: (value: RetrievalConfig) => void
+}
+
+const RetrievalParamConfig: FC<Props> = ({
+  type,
+  value,
+  onChange,
+}) => {
+  const { t } = useTranslation()
+  const canToggleRerankModalEnable = type !== RETRIEVE_METHOD.hybrid
+  const isEconomical = type === RETRIEVE_METHOD.invertedIndex
+  const {
+    defaultModel: rerankDefaultModel,
+    modelList: rerankModelList,
+  } = useModelListAndDefaultModel(ModelTypeEnum.rerank)
+
+  const {
+    currentModel,
+  } = useCurrentProviderAndModel(
+    rerankModelList,
+    rerankDefaultModel
+      ? {
+        ...rerankDefaultModel,
+        provider: rerankDefaultModel.provider.provider,
+      }
+      : undefined,
+  )
+
+  const handleDisabledSwitchClick = useCallback(() => {
+    if (!currentModel)
+      Toast.notify({ type: 'error', message: t('workflow.errorMsg.rerankModelRequired') })
+  }, [currentModel, rerankDefaultModel, t])
+
+  const isHybridSearch = type === RETRIEVE_METHOD.hybrid
+
+  const rerankModel = (() => {
+    if (value.reranking_model) {
+      return {
+        provider_name: value.reranking_model.reranking_provider_name,
+        model_name: value.reranking_model.reranking_model_name,
+      }
+    }
+    else if (rerankDefaultModel) {
+      return {
+        provider_name: rerankDefaultModel.provider.provider,
+        model_name: rerankDefaultModel.model,
+      }
+    }
+  })()
+
+  const handleChangeRerankMode = (v: RerankingModeEnum) => {
+    if (v === value.reranking_mode)
+      return
+
+    const result = {
+      ...value,
+      reranking_mode: v,
+    }
+
+    if (!result.weights && v === RerankingModeEnum.WeightedScore) {
+      result.weights = {
+        weight_type: WeightedScoreEnum.Customized,
+        vector_setting: {
+          vector_weight: DEFAULT_WEIGHTED_SCORE.other.semantic,
+          embedding_provider_name: '',
+          embedding_model_name: '',
+        },
+        keyword_setting: {
+          keyword_weight: DEFAULT_WEIGHTED_SCORE.other.keyword,
+        },
+      }
+    }
+    onChange(result)
+  }
+
+  const rerankingModeOptions = [
+    {
+      value: RerankingModeEnum.WeightedScore,
+      label: t('dataset.weightedScore.title'),
+      tips: t('dataset.weightedScore.description'),
+    },
+    {
+      value: RerankingModeEnum.RerankingModel,
+      label: t('common.modelProvider.rerankModel.key'),
+      tips: t('common.modelProvider.rerankModel.tip'),
+    },
+  ]
+
+  return (
+    <div>
+      {!isEconomical && !isHybridSearch && (
+        <div>
+          <div className='flex h-8 items-center text-[13px] font-medium text-gray-900 space-x-2'>
+            {canToggleRerankModalEnable && (
+              <div
+                className='flex items-center'
+                onClick={handleDisabledSwitchClick}
+              >
+                <Switch
+                  size='md'
+                  defaultValue={currentModel ? value.reranking_enable : false}
+                  onChange={(v) => {
+                    onChange({
+                      ...value,
+                      reranking_enable: v,
+                    })
+                  }}
+                  disabled={!currentModel}
+                />
+              </div>
+            )}
+            <div className='flex items-center'>
+              <span className='mr-0.5'>{t('common.modelProvider.rerankModel.key')}</span>
+              <Tooltip
+                popupContent={
+                  <div className="w-[200px]">{t('common.modelProvider.rerankModel.tip')}</div>
+                }
+              />
+            </div>
+          </div>
+          <ModelSelector
+            triggerClassName={`${!value.reranking_enable && '!opacity-60 !cursor-not-allowed'}`}
+            defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }}
+            modelList={rerankModelList}
+            readonly={!value.reranking_enable}
+            onSelect={(v) => {
+              onChange({
+                ...value,
+                reranking_model: {
+                  reranking_provider_name: v.provider,
+                  reranking_model_name: v.model,
+                },
+              })
+            }}
+          />
+        </div>
+      )}
+      {
+        !isHybridSearch && (
+          <div className={cn(!isEconomical && 'mt-4', 'flex space-between space-x-6')}>
+            <TopKItem
+              className='grow'
+              value={value.top_k}
+              onChange={(_key, v) => {
+                onChange({
+                  ...value,
+                  top_k: v,
+                })
+              }}
+              enable={true}
+            />
+            {(!isEconomical && !(value.search_method === RETRIEVE_METHOD.fullText && !value.reranking_enable)) && (
+              <ScoreThresholdItem
+                className='grow'
+                value={value.score_threshold}
+                onChange={(_key, v) => {
+                  onChange({
+                    ...value,
+                    score_threshold: v,
+                  })
+                }}
+                enable={value.score_threshold_enabled}
+                hasSwitch={true}
+                onSwitchChange={(_key, v) => {
+                  onChange({
+                    ...value,
+                    score_threshold_enabled: v,
+                  })
+                }}
+              />
+            )}
+          </div>
+        )
+      }
+      {
+        isHybridSearch && (
+          <>
+            <div className='flex items-center justify-between'>
+              {
+                rerankingModeOptions.map(option => (
+                  <div
+                    key={option.value}
+                    className={cn(
+                      'flex items-center justify-center mb-4 w-[calc((100%-8px)/2)] h-8 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer system-sm-medium text-text-secondary',
+                      value.reranking_mode === RerankingModeEnum.WeightedScore && option.value === RerankingModeEnum.WeightedScore && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary',
+                      value.reranking_mode !== RerankingModeEnum.WeightedScore && option.value !== RerankingModeEnum.WeightedScore && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary',
+                    )}
+                    onClick={() => handleChangeRerankMode(option.value)}
+                  >
+                    <div className='truncate'>{option.label}</div>
+                    <Tooltip
+                      popupContent={<div className='w-[200px]'>{option.tips}</div>}
+                      triggerClassName='ml-0.5 w-3.5 h-3.5'
+                    />
+                  </div>
+                ))
+              }
+            </div>
+            {
+              value.reranking_mode === RerankingModeEnum.WeightedScore && (
+                <WeightedScore
+                  value={{
+                    value: [
+                      value.weights!.vector_setting.vector_weight,
+                      value.weights!.keyword_setting.keyword_weight,
+                    ],
+                  }}
+                  onChange={(v) => {
+                    onChange({
+                      ...value,
+                      weights: {
+                        ...value.weights!,
+                        vector_setting: {
+                          ...value.weights!.vector_setting,
+                          vector_weight: v.value[0],
+                        },
+                        keyword_setting: {
+                          ...value.weights!.keyword_setting,
+                          keyword_weight: v.value[1],
+                        },
+                      },
+                    })
+                  }}
+                />
+              )
+            }
+            {
+              value.reranking_mode !== RerankingModeEnum.WeightedScore && (
+                <ModelSelector
+                  triggerClassName={`${!value.reranking_enable && '!opacity-60 !cursor-not-allowed'}`}
+                  defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }}
+                  modelList={rerankModelList}
+                  readonly={!value.reranking_enable}
+                  onSelect={(v) => {
+                    onChange({
+                      ...value,
+                      reranking_model: {
+                        reranking_provider_name: v.provider,
+                        reranking_model_name: v.model,
+                      },
+                    })
+                  }}
+                />
+              )
+            }
+            <div className={cn(!isEconomical && 'mt-4', 'flex space-between space-x-6')}>
+              <TopKItem
+                className='grow'
+                value={value.top_k}
+                onChange={(_key, v) => {
+                  onChange({
+                    ...value,
+                    top_k: v,
+                  })
+                }}
+                enable={true}
+              />
+              <ScoreThresholdItem
+                className='grow'
+                value={value.score_threshold}
+                onChange={(_key, v) => {
+                  onChange({
+                    ...value,
+                    score_threshold: v,
+                  })
+                }}
+                enable={value.score_threshold_enabled}
+                hasSwitch={true}
+                onSwitchChange={(_key, v) => {
+                  onChange({
+                    ...value,
+                    score_threshold_enabled: v,
+                  })
+                }}
+              />
+            </div>
+          </>
+        )
+      }
+    </div>
+  )
+}
+export default React.memo(RetrievalParamConfig)

+ 3 - 0
app/components/database/create/assets/Icon-3-dots.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5 6.5V5M8.93934 7.56066L10 6.5M10.0103 11.5H11.5103" stroke="#374151" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 16 - 0
app/components/database/create/assets/Loading.svg

@@ -0,0 +1,16 @@
+<svg width="464" height="180" viewBox="0 0 464 180" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="464" height="180" rx="12" fill="#F9FAFB"/>
+<rect y="6" width="464" height="8" rx="3" fill="#EAECF0"/>
+<rect y="26" width="464" height="8" rx="3" fill="#EAECF0"/>
+<rect y="46" width="464" height="8" rx="3" fill="#EAECF0"/>
+<rect y="66" width="464" height="8" rx="3" fill="#EAECF0"/>
+<rect y="86" width="464" height="8" rx="3" fill="#EAECF0"/>
+<rect y="106" width="256" height="8" rx="3" fill="#EAECF0"/>
+<path d="M0 60H464V168C464 174.627 458.627 180 452 180H12C5.3726 180 0 174.627 0 168V60Z" fill="url(#paint0_linear_2131_10881)"/>
+<defs>
+<linearGradient id="paint0_linear_2131_10881" x1="232" y1="60" x2="232" y2="180" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FCFCFD" stop-opacity="0"/>
+<stop offset="0.741486" stop-color="#FCFCFD"/>
+</linearGradient>
+</defs>
+</svg>

+ 3 - 0
app/components/database/create/assets/alert-triangle.svg

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6.40616 0.834307C6.14751 0.719294 5.85222 0.719294 5.59356 0.834307C5.3938 0.923133 5.26403 1.07959 5.17373 1.20708C5.08495 1.33242 4.9899 1.49664 4.88536 1.67723L0.751783 8.81705C0.646828 8.9983 0.551451 9.16302 0.486781 9.3028C0.421056 9.44487 0.349754 9.63584 0.372478 9.85381C0.401884 10.1359 0.549654 10.3922 0.779012 10.5589C0.956259 10.6878 1.15726 10.7218 1.31314 10.7361C1.46651 10.7501 1.65684 10.7501 1.86628 10.7501H10.1334C10.3429 10.7501 10.5332 10.7501 10.6866 10.7361C10.8425 10.7218 11.0435 10.6878 11.2207 10.5589C11.4501 10.3922 11.5978 10.1359 11.6272 9.85381C11.65 9.63584 11.5787 9.44487 11.5129 9.3028C11.4483 9.16303 11.3529 8.99833 11.248 8.81709L7.11436 1.67722C7.00983 1.49663 6.91477 1.33242 6.82599 1.20708C6.73569 1.07959 6.60593 0.923133 6.40616 0.834307ZM6.49988 4.50012C6.49988 4.22398 6.27602 4.00012 5.99988 4.00012C5.72374 4.00012 5.49988 4.22398 5.49988 4.50012V6.50012C5.49988 6.77626 5.72374 7.00012 5.99988 7.00012C6.27602 7.00012 6.49988 6.77626 6.49988 6.50012V4.50012ZM5.99988 8.00012C5.72374 8.00012 5.49988 8.22398 5.49988 8.50012C5.49988 8.77626 5.72374 9.00012 5.99988 9.00012H6.00488C6.28102 9.00012 6.50488 8.77626 6.50488 8.50012C6.50488 8.22398 6.28102 8.00012 6.00488 8.00012H5.99988Z" fill="#F79009"/>
+</svg>

+ 3 - 0
app/components/database/create/assets/annotation-info.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16.2413 2H7.7587C6.95374 1.99999 6.28937 1.99998 5.74818 2.04419C5.18608 2.09012 4.66937 2.18868 4.18404 2.43598C3.43139 2.81947 2.81947 3.43139 2.43598 4.18404C2.18868 4.66937 2.09012 5.18608 2.04419 5.74818C1.99998 6.28937 1.99999 6.95372 2 7.75869V13.5343C1.99999 14.2041 1.99999 14.7569 2.03087 15.2095C2.06289 15.6788 2.13142 16.1129 2.30448 16.5307C2.71046 17.5108 3.48916 18.2895 4.46927 18.6955C4.88708 18.8686 5.32118 18.9371 5.79046 18.9691C6.24307 19 6.79594 19 7.46573 19H7.5C8.03656 19 8.14307 19.0063 8.22975 19.0268C8.38085 19.0624 8.52156 19.1328 8.64075 19.2322C8.70913 19.2893 8.77806 19.3708 9.1 19.8L10.5769 21.7692C10.6703 21.8938 10.7758 22.0346 10.8774 22.1476C10.9894 22.2721 11.1756 22.4555 11.4563 22.5647C11.806 22.7007 12.194 22.7007 12.5437 22.5647C12.8244 22.4555 13.0106 22.2721 13.1226 22.1476C13.2242 22.0346 13.3297 21.8938 13.4231 21.7692L14.9 19.8C15.2219 19.3708 15.2909 19.2893 15.3593 19.2322C15.4784 19.1328 15.6192 19.0624 15.7702 19.0268C15.8569 19.0063 15.9634 19 16.5 19H16.5343C17.2041 19 17.7569 19 18.2095 18.9691C18.6788 18.9371 19.1129 18.8686 19.5307 18.6955C20.5108 18.2895 21.2895 17.5108 21.6955 16.5307C21.8686 16.1129 21.9371 15.6788 21.9691 15.2095C22 14.7569 22 14.2041 22 13.5343V7.75868C22 6.95372 22 6.28937 21.9558 5.74818C21.9099 5.18608 21.8113 4.66937 21.564 4.18404C21.1805 3.43139 20.5686 2.81947 19.816 2.43598C19.3306 2.18868 18.8139 2.09012 18.2518 2.04419C17.7106 1.99998 17.0463 1.99999 16.2413 2ZM12 6C11.4477 6 11 6.44772 11 7C11 7.55229 11.4477 8 12 8H12.01C12.5623 8 13.01 7.55229 13.01 7C13.01 6.44772 12.5623 6 12.01 6H12ZM13 10.5C13 9.94772 12.5523 9.5 12 9.5C11.4477 9.5 11 9.94772 11 10.5V14C11 14.5523 11.4477 15 12 15C12.5523 15 13 14.5523 13 14V10.5Z" fill="#155EEF"/>
+</svg>

+ 3 - 0
app/components/database/create/assets/arrow-narrow-left.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M13.3332 8H2.6665M2.6665 8L6.6665 12M2.6665 8L6.6665 4" stroke="#155EEF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 4 - 0
app/components/database/create/assets/book-open-01.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.12" d="M1.33337 4.13333C1.33337 3.3866 1.33337 3.01323 1.4787 2.72801C1.60653 2.47713 1.8105 2.27316 2.06139 2.14532C2.3466 2 2.71997 2 3.46671 2H3.73337C5.22685 2 5.97358 2 6.54401 2.29065C7.04578 2.54631 7.45373 2.95426 7.70939 3.45603C8.00004 4.02646 8.00004 4.77319 8.00004 6.26667V14L7.93334 13.8999C7.47024 13.2053 7.2387 12.858 6.93277 12.6065C6.66195 12.3839 6.34988 12.2169 6.01444 12.1151C5.63554 12 5.21811 12 4.38326 12H3.46671C2.71997 12 2.3466 12 2.06139 11.8547C1.8105 11.7268 1.60653 11.5229 1.4787 11.272C1.33337 10.9868 1.33337 10.6134 1.33337 9.86667V4.13333Z" fill="#155EEF"/>
+<path d="M8.00004 14L7.93334 13.8999C7.47024 13.2053 7.2387 12.858 6.93278 12.6065C6.66195 12.3839 6.34988 12.2169 6.01444 12.1151C5.63554 12 5.21811 12 4.38326 12H3.46671C2.71997 12 2.3466 12 2.06139 11.8547C1.8105 11.7268 1.60653 11.5229 1.4787 11.272C1.33337 10.9868 1.33337 10.6134 1.33337 9.86667V4.13333C1.33337 3.3866 1.33337 3.01323 1.4787 2.72801C1.60653 2.47713 1.8105 2.27316 2.06139 2.14532C2.3466 2 2.71997 2 3.46671 2H3.73337C5.22685 2 5.97358 2 6.54402 2.29065C7.04578 2.54631 7.45373 2.95426 7.70939 3.45603C8.00004 4.02646 8.00004 4.77319 8.00004 6.26667M8.00004 14V6.26667M8.00004 14L8.06674 13.8999C8.52984 13.2053 8.76139 12.858 9.06731 12.6065C9.33814 12.3839 9.6502 12.2169 9.98564 12.1151C10.3645 12 10.782 12 11.6168 12H12.5334C13.2801 12 13.6535 12 13.9387 11.8547C14.1896 11.7268 14.3936 11.5229 14.5214 11.272C14.6667 10.9868 14.6667 10.6134 14.6667 9.86667V4.13333C14.6667 3.3866 14.6667 3.01323 14.5214 2.72801C14.3936 2.47713 14.1896 2.27316 13.9387 2.14532C13.6535 2 13.2801 2 12.5334 2H12.2667C10.7732 2 10.0265 2 9.45607 2.29065C8.9543 2.54631 8.54635 2.95426 8.29069 3.45603C8.00004 4.02646 8.00004 4.77319 8.00004 6.26667" stroke="#155EEF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 3 - 0
app/components/database/create/assets/check.svg

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10 3L4.5 8.5L2 6" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 3 - 0
app/components/database/create/assets/close.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12 4L4 12M4 4L12 12" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 22 - 0
app/components/database/create/assets/csv.svg

@@ -0,0 +1,22 @@
+<svg width="24" height="26" viewBox="0 0 24 26" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_5938_919)">
+<path d="M3 5.8C3 4.11984 3 3.27976 3.32698 2.63803C3.6146 2.07354 4.07354 1.6146 4.63803 1.32698C5.27976 1 6.11984 1 7.8 1H14L21 8V18.2C21 19.8802 21 20.7202 20.673 21.362C20.3854 21.9265 19.9265 22.3854 19.362 22.673C18.7202 23 17.8802 23 16.2 23H7.8C6.11984 23 5.27976 23 4.63803 22.673C4.07354 22.3854 3.6146 21.9265 3.32698 21.362C3 20.7202 3 19.8802 3 18.2V5.8Z" fill="#169951"/>
+</g>
+<g opacity="0.96">
+<path d="M9.81332 16.4181C9.63132 17.5171 8.86832 18.0421 7.92332 18.0421C7.34232 18.0421 6.90132 17.8461 6.53732 17.4821C6.01232 16.9571 6.03332 16.2571 6.03332 15.5081C6.03332 14.7591 6.01232 14.0591 6.53732 13.5341C6.90132 13.1701 7.34232 12.9741 7.92332 12.9741C8.86832 12.9741 9.63132 13.4991 9.81332 14.5981H8.56732C8.49032 14.3181 8.33632 14.0661 7.93032 14.0661C7.70632 14.0661 7.53832 14.1571 7.44732 14.2761C7.33532 14.4231 7.25832 14.5981 7.25832 15.5081C7.25832 16.4181 7.33532 16.5931 7.44732 16.7401C7.53832 16.8591 7.70632 16.9501 7.93032 16.9501C8.33632 16.9501 8.49032 16.6981 8.56732 16.4181H9.81332Z" fill="white"/>
+<path d="M13.8059 16.4741C13.8059 17.4891 12.9309 18.0421 11.8809 18.0421C11.1179 18.0421 10.4949 17.9021 9.99094 17.3841L10.7749 16.6001C11.0339 16.8591 11.4889 16.9501 11.8879 16.9501C12.3709 16.9501 12.6019 16.7891 12.6019 16.5021C12.6019 16.3831 12.5739 16.2851 12.5039 16.2081C12.4409 16.1451 12.3359 16.0961 12.1749 16.0751L11.5729 15.9911C11.1319 15.9281 10.7959 15.7811 10.5719 15.5501C10.3409 15.3121 10.2289 14.9761 10.2289 14.5491C10.2289 13.6391 10.9149 12.9741 12.0489 12.9741C12.7629 12.9741 13.3019 13.1421 13.7289 13.5691L12.9589 14.3391C12.6439 14.0241 12.2309 14.0451 12.0139 14.0451C11.5869 14.0451 11.4119 14.2901 11.4119 14.5071C11.4119 14.5701 11.4329 14.6611 11.5099 14.7381C11.5729 14.8011 11.6779 14.8641 11.8529 14.8851L12.4549 14.9691C12.9029 15.0321 13.2249 15.1721 13.4349 15.3821C13.7009 15.6411 13.8059 16.0121 13.8059 16.4741Z" fill="white"/>
+<path d="M18.3124 13.0161L16.6604 18.0001H15.7504L14.1054 13.0161H15.3724L16.2124 15.8021L17.0384 13.0161H18.3124Z" fill="white"/>
+</g>
+<path opacity="0.5" d="M14 1L21 8H16C14.8954 8 14 7.10457 14 6V1Z" fill="white"/>
+<defs>
+<filter id="filter0_d_5938_919" x="1" y="0" width="22" height="26" 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_5938_919"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5938_919" result="shape"/>
+</filter>
+</defs>
+</svg>

+ 22 - 0
app/components/database/create/assets/doc.svg

@@ -0,0 +1,22 @@
+<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_17194_49206)">
+<path d="M4 7.73301C4 5.4928 4 4.37269 4.43597 3.51705C4.81947 2.7644 5.43139 2.15248 6.18404 1.76898C7.03969 1.33301 8.15979 1.33301 10.4 1.33301H18.6667L28 10.6663V24.2663C28 26.5066 28 27.6267 27.564 28.4823C27.1805 29.2349 26.5686 29.8469 25.816 30.2304C24.9603 30.6663 23.8402 30.6663 21.6 30.6663H10.4C8.15979 30.6663 7.03969 30.6663 6.18404 30.2304C5.43139 29.8469 4.81947 29.2349 4.43597 28.4823C4 27.6267 4 26.5066 4 24.2663V7.73301Z" fill="#2349A9"/>
+</g>
+<path opacity="0.5" d="M18.6665 1.33301L27.9998 10.6663H21.3332C19.8604 10.6663 18.6665 9.47243 18.6665 7.99967V1.33301Z" fill="white"/>
+<g opacity="0.96">
+<path d="M13.6329 21.4112C13.6329 22.2603 13.7059 22.9501 13.0326 23.5793C12.6351 23.9508 12.0754 24.11 11.4751 24.11H9.3335V18.7125H11.4751C12.0754 18.7125 12.6351 18.8717 13.0326 19.2431C13.7059 19.8723 13.6329 20.5622 13.6329 21.4112ZM12.2133 21.4112C12.2133 20.5015 12.1727 20.3499 12.0591 20.1983C11.9293 20.0164 11.7347 19.8951 11.3777 19.8951H10.7531V22.9274H11.3777C11.7347 22.9274 11.9293 22.8061 12.0591 22.6242C12.1727 22.4725 12.2133 22.3285 12.2133 21.4112Z" fill="white"/>
+<path d="M18.8275 21.4112C18.8275 22.2224 18.8519 22.9805 18.2435 23.549C17.8217 23.9432 17.3349 24.1555 16.6292 24.1555C15.9234 24.1555 15.4367 23.9432 15.0149 23.549C14.4065 22.9805 14.4308 22.2224 14.4308 21.4112C14.4308 20.6001 14.4065 19.842 15.0149 19.2735C15.4367 18.8793 15.9234 18.667 16.6292 18.667C17.3349 18.667 17.8217 18.8793 18.2435 19.2735C18.8519 19.842 18.8275 20.6001 18.8275 21.4112ZM17.4079 21.4112C17.4079 20.4257 17.3268 20.2438 17.197 20.0846C17.0916 19.9557 16.8888 19.8496 16.6292 19.8496C16.3696 19.8496 16.1668 19.9557 16.0613 20.0846C15.9316 20.2438 15.8504 20.4257 15.8504 21.4112C15.8504 22.3967 15.9316 22.5711 16.0613 22.7303C16.1668 22.8592 16.3696 22.9729 16.6292 22.9729C16.8888 22.9729 17.0916 22.8592 17.197 22.7303C17.3268 22.5711 17.4079 22.3967 17.4079 21.4112Z" fill="white"/>
+<path d="M24.0002 22.3967C23.7893 23.5869 22.905 24.1555 21.8099 24.1555C21.1366 24.1555 20.6256 23.9432 20.2037 23.549C19.5953 22.9805 19.6197 22.2224 19.6197 21.4112C19.6197 20.6001 19.5953 19.842 20.2037 19.2735C20.6256 18.8793 21.1366 18.667 21.8099 18.667C22.905 18.667 23.7893 19.2356 24.0002 20.4257H22.5562C22.467 20.1225 22.2885 19.8496 21.818 19.8496C21.5584 19.8496 21.3638 19.9481 21.2583 20.077C21.1285 20.2362 21.0393 20.4257 21.0393 21.4112C21.0393 22.3967 21.1285 22.5863 21.2583 22.7455C21.3638 22.8743 21.5584 22.9729 21.818 22.9729C22.2885 22.9729 22.467 22.7 22.5562 22.3967H24.0002Z" fill="white"/>
+</g>
+<defs>
+<filter id="filter0_d_17194_49206" x="2" y="0.333008" width="28" height="33.333" 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_17194_49206"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_17194_49206" result="shape"/>
+</filter>
+</defs>
+</svg>

+ 23 - 0
app/components/database/create/assets/docx.svg

@@ -0,0 +1,23 @@
+<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_10291_62253)">
+<path d="M4 7.73301C4 5.4928 4 4.37269 4.43597 3.51705C4.81947 2.7644 5.43139 2.15248 6.18404 1.76898C7.03969 1.33301 8.15979 1.33301 10.4 1.33301H18.6667L28 10.6663V24.2663C28 26.5065 28 27.6267 27.564 28.4823C27.1805 29.2349 26.5686 29.8469 25.816 30.2304C24.9603 30.6663 23.8402 30.6663 21.6 30.6663H10.4C8.15979 30.6663 7.03969 30.6663 6.18404 30.2304C5.43139 29.8469 4.81947 29.2349 4.43597 28.4823C4 27.6267 4 26.5065 4 24.2663V7.73301Z" fill="#2349A9"/>
+</g>
+<path opacity="0.5" d="M18.6665 1.33301L27.9998 10.6663H21.3332C19.8604 10.6663 18.6665 9.47243 18.6665 7.99967V1.33301Z" fill="white"/>
+<g opacity="0.96">
+<path d="M10.8443 21.3337C10.8443 22.1587 10.9153 22.8291 10.261 23.4405C9.87477 23.8014 9.33086 23.9561 8.74754 23.9561H6.6665V18.7112H8.74754C9.33086 18.7112 9.87477 18.8659 10.261 19.2268C10.9153 19.8383 10.8443 20.5086 10.8443 21.3337ZM9.46487 21.3337C9.46487 20.4497 9.42545 20.3024 9.31509 20.155C9.18897 19.9782 8.99979 19.8604 8.65295 19.8604H8.04598V22.807H8.65295C8.99979 22.807 9.18897 22.6891 9.31509 22.5123C9.42545 22.365 9.46487 22.225 9.46487 21.3337Z" fill="white"/>
+<path d="M15.8922 21.3337C15.8922 22.1219 15.9158 22.8585 15.3246 23.411C14.9147 23.7941 14.4418 24.0003 13.756 24.0003C13.0702 24.0003 12.5972 23.7941 12.1873 23.411C11.5961 22.8585 11.6197 22.1219 11.6197 21.3337C11.6197 20.5454 11.5961 19.8088 12.1873 19.2563C12.5972 18.8733 13.0702 18.667 13.756 18.667C14.4418 18.667 14.9147 18.8733 15.3246 19.2563C15.9158 19.8088 15.8922 20.5454 15.8922 21.3337ZM14.5127 21.3337C14.5127 20.376 14.4339 20.1992 14.3077 20.0445C14.2053 19.9193 14.0082 19.8162 13.756 19.8162C13.5037 19.8162 13.3066 19.9193 13.2042 20.0445C13.078 20.1992 12.9992 20.376 12.9992 21.3337C12.9992 22.2913 13.078 22.4607 13.2042 22.6154C13.3066 22.7407 13.5037 22.8512 13.756 22.8512C14.0082 22.8512 14.2053 22.7407 14.3077 22.6154C14.4339 22.4607 14.5127 22.2913 14.5127 21.3337Z" fill="white"/>
+<path d="M20.9186 22.2913C20.7136 23.4478 19.8544 24.0003 18.7902 24.0003C18.136 24.0003 17.6394 23.7941 17.2295 23.411C16.6383 22.8585 16.6619 22.1219 16.6619 21.3337C16.6619 20.5454 16.6383 19.8088 17.2295 19.2563C17.6394 18.8733 18.136 18.667 18.7902 18.667C19.8544 18.667 20.7136 19.2195 20.9186 20.376H19.5154C19.4287 20.0814 19.2553 19.8162 18.7981 19.8162C18.5459 19.8162 18.3567 19.9119 18.2542 20.0372C18.1281 20.1919 18.0414 20.376 18.0414 21.3337C18.0414 22.2913 18.1281 22.4755 18.2542 22.6302C18.3567 22.7554 18.5459 22.8512 18.7981 22.8512C19.2553 22.8512 19.4287 22.586 19.5154 22.2913H20.9186Z" fill="white"/>
+<path d="M25.9998 23.9561H24.4233L23.501 22.3429L22.5787 23.9561H21.0022L22.7522 21.2674L21.1126 18.7112H22.6812L23.501 20.1919L24.3208 18.7112H25.8895L24.2499 21.2674L25.9998 23.9561Z" fill="white"/>
+</g>
+<defs>
+<filter id="filter0_d_10291_62253" x="2" y="0.333008" width="28" height="33.333" 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_10291_62253"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_10291_62253" result="shape"/>
+</filter>
+</defs>
+</svg>

+ 4 - 0
app/components/database/create/assets/file.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.66667 1.34356C8.66667 1.32602 8.66667 1.31725 8.66591 1.30135C8.65018 0.972168 8.3607 0.682824 8.03151 0.667251C8.01562 0.666499 8.0104 0.666501 8.00001 0.666504H5.8391C5.30248 0.666497 4.85957 0.666491 4.49878 0.695968C4.12405 0.726585 3.77958 0.792295 3.45603 0.957155C2.95426 1.21282 2.54631 1.62077 2.29065 2.12253C2.12579 2.44609 2.06008 2.79056 2.02946 3.16529C1.99999 3.52608 1.99999 3.96899 2 4.50562V11.494C1.99999 12.0307 1.99999 12.4736 2.02946 12.8344C2.06008 13.2091 2.12579 13.5536 2.29065 13.8771C2.54631 14.3789 2.95426 14.7869 3.45603 15.0425C3.77958 15.2074 4.12405 15.2731 4.49878 15.3037C4.85958 15.3332 5.30248 15.3332 5.83912 15.3332H10.1609C10.6975 15.3332 11.1404 15.3332 11.5012 15.3037C11.8759 15.2731 12.2204 15.2074 12.544 15.0425C13.0457 14.7869 13.4537 14.3789 13.7093 13.8771C13.8742 13.5536 13.9399 13.2091 13.9705 12.8344C14 12.4736 14 12.0307 14 11.4941V6.66646C14 6.65611 14 6.65093 13.9993 6.63505C13.9837 6.30583 13.6943 6.01631 13.3651 6.0006C13.3492 5.99985 13.3405 5.99985 13.323 5.99985L10.3787 5.99985C10.2105 5.99987 10.0466 5.99989 9.90785 5.98855C9.75545 5.9761 9.57563 5.94672 9.39468 5.85452C9.1438 5.72669 8.93983 5.52272 8.81199 5.27183C8.7198 5.09088 8.69042 4.91106 8.67797 4.75867C8.66663 4.61989 8.66665 4.45603 8.66667 4.28778L8.66667 1.34356ZM5.33333 8.6665C4.96514 8.6665 4.66667 8.96498 4.66667 9.33317C4.66667 9.70136 4.96514 9.99984 5.33333 9.99984H10.6667C11.0349 9.99984 11.3333 9.70136 11.3333 9.33317C11.3333 8.96498 11.0349 8.6665 10.6667 8.6665H5.33333ZM5.33333 11.3332C4.96514 11.3332 4.66667 11.6316 4.66667 11.9998C4.66667 12.368 4.96514 12.6665 5.33333 12.6665H9.33333C9.70152 12.6665 10 12.368 10 11.9998C10 11.6316 9.70152 11.3332 9.33333 11.3332H5.33333Z" fill="#444CE7"/>
+<path d="M12.6053 4.6665C12.8011 4.6665 12.8989 4.6665 12.9791 4.61735C13.0923 4.54794 13.16 4.3844 13.129 4.25526C13.107 4.16382 13.0432 4.10006 12.9155 3.97253L10.694 1.75098C10.5664 1.62333 10.5027 1.5595 10.4112 1.53752C10.2821 1.50648 10.1186 1.57417 10.0492 1.6874C10 1.76757 10 1.86545 10 2.0612L10 4.13315C10 4.31982 10 4.41316 10.0363 4.48446C10.0683 4.54718 10.1193 4.59818 10.182 4.63014C10.2533 4.66647 10.3466 4.66647 10.5333 4.66647L12.6053 4.6665Z" fill="#444CE7"/>
+</svg>

+ 3 - 0
app/components/database/create/assets/folder-plus.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.66683 4.66667L7.92314 3.17928C7.7091 2.7512 7.60207 2.53715 7.44241 2.38078C7.30122 2.24249 7.13105 2.13732 6.94421 2.07287C6.73294 2 6.49363 2 6.01502 2H3.46683C2.72009 2 2.34672 2 2.06151 2.14532C1.81063 2.27316 1.60665 2.47713 1.47882 2.72801C1.3335 3.01323 1.3335 3.3866 1.3335 4.13333V4.66667M1.3335 4.66667H11.4668C12.5869 4.66667 13.147 4.66667 13.5748 4.88465C13.9511 5.0764 14.2571 5.38236 14.4488 5.75869C14.6668 6.18651 14.6668 6.74656 14.6668 7.86667V10.8C14.6668 11.9201 14.6668 12.4802 14.4488 12.908C14.2571 13.2843 13.9511 13.5903 13.5748 13.782C13.147 14 12.5869 14 11.4668 14H4.5335C3.41339 14 2.85334 14 2.42552 13.782C2.04919 13.5903 1.74323 13.2843 1.55148 12.908C1.3335 12.4802 1.3335 11.9201 1.3335 10.8V4.66667ZM8.00016 11.3333V7.33333M6.00016 9.33333H10.0002" stroke="#155EEF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 23 - 0
app/components/database/create/assets/html.svg

@@ -0,0 +1,23 @@
+<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_3055_14424)">
+<path d="M4 7.73349C4 5.49329 4 4.37318 4.43597 3.51753C4.81947 2.76489 5.43139 2.15296 6.18404 1.76947C7.03969 1.3335 8.15979 1.3335 10.4 1.3335H18.6667L28 10.6668V24.2668C28 26.507 28 27.6271 27.564 28.4828C27.1805 29.2354 26.5686 29.8474 25.816 30.2309C24.9603 30.6668 23.8402 30.6668 21.6 30.6668H10.4C8.15979 30.6668 7.03969 30.6668 6.18404 30.2309C5.43139 29.8474 4.81947 29.2354 4.43597 28.4828C4 27.6271 4 26.507 4 24.2668V7.73349Z" fill="#EC5B27"/>
+</g>
+<g opacity="0.96">
+<path d="M10.2704 24.0002V18.3042H8.87042V20.4962H7.38242V18.3042H5.98242V24.0002H7.38242V21.7442H8.87042V24.0002H10.2704Z" fill="white"/>
+<path d="M15.2839 19.5522V18.3042H11.0839V19.5522H12.4839V24.0002H13.8839V19.5522H15.2839Z" fill="white"/>
+<path d="M21.4116 24.0002V18.3042H20.0356L18.7556 20.8162L17.4756 18.3042H16.0996V24.0002H17.4996V21.2722L18.3076 22.6802H19.2036L20.0116 21.2722V24.0002H21.4116Z" fill="white"/>
+<path d="M26.3525 24.0002V22.7522H23.9605V18.3042H22.5605V24.0002H26.3525Z" fill="white"/>
+</g>
+<path opacity="0.5" d="M18.6665 1.3335L27.9998 10.6668H21.3332C19.8604 10.6668 18.6665 9.47292 18.6665 8.00016V1.3335Z" fill="white"/>
+<defs>
+<filter id="filter0_d_3055_14424" x="2" y="0.333496" width="28" height="33.3335" 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_3055_14424"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3055_14424" result="shape"/>
+</filter>
+</defs>
+</svg>

BIN
app/components/database/create/assets/jina.png


+ 23 - 0
app/components/database/create/assets/json.svg

@@ -0,0 +1,23 @@
+<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_3055_14428)">
+<path d="M4 7.73349C4 5.49329 4 4.37318 4.43597 3.51753C4.81947 2.76489 5.43139 2.15296 6.18404 1.76947C7.03969 1.3335 8.15979 1.3335 10.4 1.3335H18.6667L28 10.6668V24.2668C28 26.507 28 27.6271 27.564 28.4828C27.1805 29.2354 26.5686 29.8474 25.816 30.2309C24.9603 30.6668 23.8402 30.6668 21.6 30.6668H10.4C8.15979 30.6668 7.03969 30.6668 6.18404 30.2309C5.43139 29.8474 4.81947 29.2354 4.43597 28.4828C4 27.6271 4 26.507 4 24.2668V7.73349Z" fill="#2D2D2E"/>
+</g>
+<g opacity="0.96">
+<path d="M9.83907 22.0479V18.3039H8.43907V22.0159C8.43907 22.5599 8.12707 22.7999 7.69507 22.7999C7.38307 22.7999 7.23907 22.6879 7.06307 22.5119L6.14307 23.4239C6.60707 23.8879 7.03107 24.0479 7.69507 24.0479C8.76707 24.0479 9.83907 23.3999 9.83907 22.0479Z" fill="white"/>
+<path d="M14.7321 22.2559C14.7321 21.7279 14.6121 21.3039 14.3081 21.0079C14.0681 20.7679 13.7001 20.6079 13.1881 20.5359L12.5001 20.4399C12.3001 20.4159 12.1801 20.3439 12.1081 20.2719C12.0201 20.1839 11.9961 20.0799 11.9961 20.0079C11.9961 19.7599 12.1961 19.4799 12.6841 19.4799C12.9321 19.4799 13.4041 19.4559 13.7641 19.8159L14.6441 18.9359C14.1561 18.4479 13.5401 18.2559 12.7241 18.2559C11.4281 18.2559 10.6441 19.0159 10.6441 20.0559C10.6441 20.5439 10.7721 20.9279 11.0361 21.1999C11.2921 21.4639 11.6761 21.6319 12.1801 21.7039L12.8681 21.7999C13.0521 21.8239 13.1721 21.8799 13.2441 21.9519C13.3241 22.0399 13.3561 22.1519 13.3561 22.2879C13.3561 22.6159 13.0921 22.7999 12.5401 22.7999C12.0841 22.7999 11.5641 22.6959 11.2681 22.3999L10.3721 23.2959C10.9481 23.8879 11.6601 24.0479 12.5321 24.0479C13.7321 24.0479 14.7321 23.4159 14.7321 22.2559Z" fill="white"/>
+<path d="M19.8023 21.1519C19.8023 20.2959 19.8263 19.4959 19.2263 18.8959C18.8103 18.4799 18.3303 18.2559 17.6343 18.2559C16.9383 18.2559 16.4583 18.4799 16.0423 18.8959C15.4423 19.4959 15.4663 20.2959 15.4663 21.1519C15.4663 22.0079 15.4423 22.8079 16.0423 23.4079C16.4583 23.8239 16.9383 24.0479 17.6343 24.0479C18.3303 24.0479 18.8103 23.8239 19.2263 23.4079C19.8263 22.8079 19.8023 22.0079 19.8023 21.1519ZM18.4023 21.1519C18.4023 22.1919 18.3223 22.3759 18.1943 22.5439C18.0903 22.6799 17.8903 22.7999 17.6343 22.7999C17.3783 22.7999 17.1783 22.6799 17.0743 22.5439C16.9463 22.3759 16.8663 22.1919 16.8663 21.1519C16.8663 20.1119 16.9463 19.9199 17.0743 19.7519C17.1783 19.6159 17.3783 19.5039 17.6343 19.5039C17.8903 19.5039 18.0903 19.6159 18.1943 19.7519C18.3223 19.9199 18.4023 20.1119 18.4023 21.1519Z" fill="white"/>
+<path d="M25.2154 23.9999V18.3039H23.8154V21.1679L21.9914 18.3039H20.7674V23.9999H22.1674V21.1359L23.9914 23.9999H25.2154Z" fill="white"/>
+</g>
+<path opacity="0.5" d="M18.6665 1.3335L27.9998 10.6668H21.3332C19.8604 10.6668 18.6665 9.47292 18.6665 8.00016V1.3335Z" fill="white"/>
+<defs>
+<filter id="filter0_d_3055_14428" x="2" y="0.333496" width="28" height="33.3335" 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_3055_14428"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3055_14428" result="shape"/>
+</filter>
+</defs>
+</svg>

+ 18 - 0
app/components/database/create/assets/md.svg

@@ -0,0 +1,18 @@
+<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_3777_37339)">
+<path d="M4 7.73349C4 5.49329 4 4.37318 4.43597 3.51753C4.81947 2.76489 5.43139 2.15296 6.18404 1.76947C7.03969 1.3335 8.15979 1.3335 10.4 1.3335H18.6667L28 10.6668V24.2668C28 26.507 28 27.6271 27.564 28.4828C27.1805 29.2354 26.5686 29.8474 25.816 30.2309C24.9603 30.6668 23.8402 30.6668 21.6 30.6668H10.4C8.15979 30.6668 7.03969 30.6668 6.18404 30.2309C5.43139 29.8474 4.81947 29.2354 4.43597 28.4828C4 27.6271 4 26.507 4 24.2668V7.73349Z" fill="#309BEC"/>
+</g>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M21.9904 25.3335H10.0096C9.45202 25.3335 9 24.9138 9 24.396V18.271C9 17.7532 9.45202 17.3335 10.0096 17.3335H21.9904C22.548 17.3335 23 17.7532 23 18.271V24.396C23 24.9138 22.548 25.3335 21.9904 25.3335ZM12.3654 23.4585V21.021L13.7115 22.5835L15.0577 21.021V23.4585H16.4038V19.2085H15.0577L13.7115 20.771L12.3654 19.2085H11.0192V23.4585H12.3654ZM20.0385 21.3335H21.3846L19.3654 23.521L17.3462 21.3335H18.6923V19.2085H20.0385V21.3335Z" fill="white"/>
+<path opacity="0.5" d="M18.6665 1.3335L27.9998 10.6668H21.3332C19.8604 10.6668 18.6665 9.47292 18.6665 8.00016V1.3335Z" fill="white"/>
+<defs>
+<filter id="filter0_d_3777_37339" x="2" y="0.333496" width="28" height="33.3335" 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_3777_37339"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3777_37339" result="shape"/>
+</filter>
+</defs>
+</svg>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 2 - 0
app/components/database/create/assets/normal.svg


+ 12 - 0
app/components/database/create/assets/notion.svg

@@ -0,0 +1,12 @@
+<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2942_529)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.90599 18.2611L1.75639 15.5832C1.2392 14.9389 0.958496 14.1466 0.958496 13.3312V3.63437C0.958496 2.4129 1.93574 1.39936 3.19644 1.31328L13.1661 0.632614C13.8904 0.583164 14.6103 0.775682 15.2052 1.17794L18.708 3.5462C19.335 3.97012 19.7085 4.66312 19.7085 5.40266V16.427C19.7085 17.6223 18.7476 18.6121 17.5133 18.688L6.44808 19.3692C5.46308 19.4298 4.51099 19.0148 3.90599 18.2611Z" fill="white"/>
+<path d="M7.36355 8.48663V8.35968C7.36355 8.03787 7.62129 7.77098 7.95347 7.7488L10.3731 7.58726L13.7191 12.5146V8.19003L12.8579 8.07522V8.01492C12.8579 7.68933 13.1215 7.42068 13.4579 7.40344L15.6595 7.29066V7.60749C15.6595 7.75622 15.5489 7.88343 15.3973 7.90907L14.8675 7.99868V15.0022L14.2026 15.2309C13.6471 15.4219 13.0287 15.2174 12.7107 14.7376L9.46228 9.83568V14.5143L10.4622 14.7056L10.4482 14.7984C10.4046 15.0889 10.1538 15.3086 9.85036 15.3221L7.36355 15.4328C7.33068 15.1204 7.56481 14.8409 7.88781 14.807L8.21492 14.7726V8.53447L7.36355 8.48663Z" fill="black"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2553 1.85418L3.28567 2.53485C2.68849 2.57562 2.22559 3.05572 2.22559 3.63431V13.3311C2.22559 13.8748 2.41272 14.4029 2.75752 14.8325L4.90712 17.5104C5.25467 17.9433 5.80161 18.1817 6.36747 18.1469L17.4326 17.4658C17.9998 17.4309 18.4413 16.9761 18.4413 16.4269V5.4026C18.4413 5.06281 18.2697 4.74441 17.9816 4.54963L14.4788 2.18137C14.1218 1.94002 13.6899 1.82451 13.2553 1.85418ZM3.78004 3.78556C3.64138 3.6829 3.70737 3.46903 3.88156 3.45654L13.3224 2.77938C13.6232 2.75781 13.9221 2.84064 14.1653 3.01299L16.0595 4.35502C16.1315 4.40597 16.0977 4.51596 16.0087 4.5208L6.01092 5.06454C5.70835 5.081 5.4097 4.99211 5.16913 4.814L3.78004 3.78556ZM5.54198 6.76913C5.54198 6.44433 5.80438 6.17604 6.13991 6.15777L16.7104 5.5821C17.0374 5.56429 17.3127 5.81577 17.3127 6.13232V15.6782C17.3127 16.0024 17.0512 16.2705 16.7164 16.2895L6.2128 16.8871C5.84887 16.9079 5.54198 16.6282 5.54198 16.2759V6.76913Z" fill="black"/>
+</g>
+<defs>
+<clipPath id="clip0_2942_529">
+<rect width="20" height="20" fill="white" transform="translate(0.333496)"/>
+</clipPath>
+</defs>
+</svg>

+ 22 - 0
app/components/database/create/assets/pdf.svg

@@ -0,0 +1,22 @@
+<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_3055_14420)">
+<path d="M4 7.73349C4 5.49329 4 4.37318 4.43597 3.51753C4.81947 2.76489 5.43139 2.15296 6.18404 1.76947C7.03969 1.3335 8.15979 1.3335 10.4 1.3335H18.6667L28 10.6668V24.2668C28 26.507 28 27.6271 27.564 28.4828C27.1805 29.2354 26.5686 29.8474 25.816 30.2309C24.9603 30.6668 23.8402 30.6668 21.6 30.6668H10.4C8.15979 30.6668 7.03969 30.6668 6.18404 30.2309C5.43139 29.8474 4.81947 29.2354 4.43597 28.4828C4 27.6271 4 26.507 4 24.2668V7.73349Z" fill="#DD3633"/>
+</g>
+<g opacity="0.96">
+<path d="M13.2801 20.1362C13.2801 19.2002 12.6001 18.3042 11.3361 18.3042H9.08008V24.0002H10.4801V21.9682H11.3361C12.6001 21.9682 13.2801 21.0722 13.2801 20.1362ZM11.8801 20.1362C11.8801 20.4322 11.6561 20.7122 11.2721 20.7122H10.4801V19.5602H11.2721C11.6561 19.5602 11.8801 19.8402 11.8801 20.1362Z" fill="white"/>
+<path d="M18.3357 21.1522C18.3357 20.2562 18.4077 19.5282 17.7437 18.8642C17.3517 18.4722 16.7997 18.3042 16.2077 18.3042H14.0957V24.0002H16.2077C16.7997 24.0002 17.3517 23.8322 17.7437 23.4402C18.4077 22.7762 18.3357 22.0482 18.3357 21.1522ZM16.9357 21.1522C16.9357 22.1202 16.8957 22.2722 16.7837 22.4322C16.6557 22.6242 16.4637 22.7522 16.1117 22.7522H15.4957V19.5522H16.1117C16.4637 19.5522 16.6557 19.6802 16.7837 19.8722C16.8957 20.0322 16.9357 20.1922 16.9357 21.1522Z" fill="white"/>
+<path d="M23.1786 19.5522V18.3042H19.3066V24.0002H20.7066V21.8002H22.8186V20.5522H20.7066V19.5522H23.1786Z" fill="white"/>
+</g>
+<path opacity="0.5" d="M18.6665 1.3335L27.9998 10.6668H21.3332C19.8604 10.6668 18.6665 9.47292 18.6665 8.00016V1.3335Z" fill="white"/>
+<defs>
+<filter id="filter0_d_3055_14420" x="2" y="0.333496" width="28" height="33.3335" 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_3055_14420"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3055_14420" result="shape"/>
+</filter>
+</defs>
+</svg>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 2 - 0
app/components/database/create/assets/piggy-bank-01.svg


+ 8 - 0
app/components/database/create/assets/sliders-02.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M1.33325 12.6666C1.33325 12.2984 1.63173 12 1.99992 12H4.66659C5.03478 12 5.33325 12.2984 5.33325 12.6666C5.33325 13.0348 5.03478 13.3333 4.66659 13.3333H1.99992C1.63173 13.3333 1.33325 13.0348 1.33325 12.6666Z" fill="#155EEF"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5528 12C10.2782 11.2232 9.5374 10.6666 8.66659 10.6666C7.56202 10.6666 6.66659 11.5621 6.66659 12.6666C6.66659 13.7712 7.56202 14.6666 8.66659 14.6666C9.5374 14.6666 10.2782 14.1101 10.5528 13.3333L13.9999 13.3333C14.3681 13.3333 14.6666 13.0348 14.6666 12.6666C14.6666 12.2984 14.3681 12 13.9999 12L10.5528 12Z" fill="#155EEF"/>
+<path d="M9.99992 7.33329C9.63173 7.33329 9.33325 7.63177 9.33325 7.99996C9.33325 8.36815 9.63173 8.66663 9.99992 8.66663H13.9999C14.3681 8.66663 14.6666 8.36815 14.6666 7.99996C14.6666 7.63177 14.3681 7.33329 13.9999 7.33329H9.99992Z" fill="#155EEF"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1.33325 7.99996C1.33325 7.63177 1.63173 7.33329 1.99992 7.33329H4.11372C4.38828 6.5565 5.12911 5.99996 5.99992 5.99996C7.10449 5.99996 7.99992 6.89539 7.99992 7.99996C7.99992 9.10453 7.10449 9.99996 5.99992 9.99996C5.12911 9.99996 4.38828 9.44342 4.11372 8.66663H1.99992C1.63173 8.66663 1.33325 8.36815 1.33325 7.99996Z" fill="#155EEF"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99992 1.33329C10.8707 1.33329 11.6116 1.88983 11.8861 2.66663H13.9999C14.3681 2.66663 14.6666 2.9651 14.6666 3.33329C14.6666 3.70148 14.3681 3.99996 13.9999 3.99996H11.8861C11.6116 4.77675 10.8707 5.33329 9.99992 5.33329C8.89535 5.33329 7.99992 4.43786 7.99992 3.33329C7.99992 2.22872 8.89535 1.33329 9.99992 1.33329Z" fill="#155EEF"/>
+<path d="M1.33325 3.33329C1.33325 2.9651 1.63173 2.66663 1.99992 2.66663H5.99992C6.36811 2.66663 6.66659 2.9651 6.66659 3.33329C6.66659 3.70148 6.36811 3.99996 5.99992 3.99996H1.99992C1.63173 3.99996 1.33325 3.70148 1.33325 3.33329Z" fill="#155EEF"/>
+</svg>

+ 11 - 0
app/components/database/create/assets/star-07.svg

@@ -0,0 +1,11 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.00008 0.666626C8.36827 0.666626 8.66675 0.965103 8.66675 1.33329V2.66663C8.66675 3.03482 8.36827 3.33329 8.00008 3.33329C7.63189 3.33329 7.33341 3.03482 7.33341 2.66663V1.33329C7.33341 0.965103 7.63189 0.666626 8.00008 0.666626Z" fill="#FB6514"/>
+<path d="M3.75729 2.81452C3.49694 2.55418 3.07483 2.55418 2.81448 2.81452C2.55413 3.07487 2.55413 3.49698 2.81448 3.75733L3.75729 4.70014C4.01764 4.96049 4.43975 4.96049 4.7001 4.70014C4.96045 4.43979 4.96045 4.01768 4.7001 3.75733L3.75729 2.81452Z" fill="#FB6514"/>
+<path d="M0.666748 7.99996C0.666748 7.63177 0.965225 7.33329 1.33341 7.33329H2.66675C3.03494 7.33329 3.33341 7.63177 3.33341 7.99996C3.33341 8.36815 3.03494 8.66663 2.66675 8.66663H1.33341C0.965225 8.66663 0.666748 8.36815 0.666748 7.99996Z" fill="#FB6514"/>
+<path d="M13.3334 7.33329C12.9652 7.33329 12.6667 7.63177 12.6667 7.99996C12.6667 8.36815 12.9652 8.66663 13.3334 8.66663H14.6667C15.0349 8.66663 15.3334 8.36815 15.3334 7.99996C15.3334 7.63177 15.0349 7.33329 14.6667 7.33329H13.3334Z" fill="#FB6514"/>
+<path d="M12.2426 11.2997C11.9823 11.0394 11.5602 11.0394 11.2998 11.2997C11.0395 11.5601 11.0395 11.9822 11.2998 12.2425L12.2426 13.1853C12.503 13.4457 12.9251 13.4457 13.1855 13.1853C13.4458 12.925 13.4458 12.5029 13.1855 12.2425L12.2426 11.2997Z" fill="#FB6514"/>
+<path d="M13.1855 3.75733C13.4458 3.49698 13.4458 3.07487 13.1855 2.81452C12.9251 2.55418 12.503 2.55418 12.2426 2.81452L11.2998 3.75733C11.0395 4.01768 11.0395 4.43979 11.2998 4.70014C11.5602 4.96049 11.9823 4.96049 12.2426 4.70014L13.1855 3.75733Z" fill="#FB6514"/>
+<path d="M8.00008 12.6666C8.36827 12.6666 8.66675 12.9651 8.66675 13.3333V14.6666C8.66675 15.0348 8.36827 15.3333 8.00008 15.3333C7.63189 15.3333 7.33341 15.0348 7.33341 14.6666V13.3333C7.33341 12.9651 7.63189 12.6666 8.00008 12.6666Z" fill="#FB6514"/>
+<path d="M4.7001 12.2425C4.96045 11.9822 4.96045 11.5601 4.7001 11.2997C4.43975 11.0394 4.01764 11.0394 3.75729 11.2997L2.81448 12.2425C2.55413 12.5029 2.55413 12.925 2.81448 13.1853C3.07483 13.4457 3.49694 13.4457 3.75729 13.1853L4.7001 12.2425Z" fill="#FB6514"/>
+<path d="M8.59791 4.37154C8.48559 4.14401 8.25385 3.99996 8.0001 3.99996C7.74635 3.99996 7.51461 4.14401 7.4023 4.37154L6.52726 6.14427L4.57035 6.4303C4.31931 6.46699 4.11085 6.643 4.0326 6.88434C3.95435 7.12567 4.01987 7.39051 4.20161 7.56753L5.61709 8.9462L5.28303 10.8939C5.24013 11.1441 5.34296 11.3968 5.54828 11.546C5.7536 11.6951 6.02579 11.7148 6.2504 11.5967L8.0001 10.6765L9.7498 11.5967C9.97441 11.7148 10.2466 11.6951 10.4519 11.546C10.6572 11.3968 10.7601 11.1441 10.7172 10.8939L10.3831 8.9462L11.7986 7.56753C11.9803 7.39051 12.0458 7.12567 11.9676 6.88434C11.8893 6.643 11.6809 6.46699 11.4299 6.4303L9.47294 6.14427L8.59791 4.37154Z" fill="#FB6514"/>
+</svg>

+ 11 - 0
app/components/database/create/assets/star.svg

@@ -0,0 +1,11 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6 0.5C6.27614 0.5 6.5 0.723858 6.5 1V2C6.5 2.27614 6.27614 2.5 6 2.5C5.72386 2.5 5.5 2.27614 5.5 2V1C5.5 0.723858 5.72386 0.5 6 0.5Z" fill="#FB6514"/>
+<path d="M2.81791 2.11092C2.62265 1.91566 2.30606 1.91566 2.1108 2.11092C1.91554 2.30619 1.91554 2.62277 2.1108 2.81803L2.81791 3.52514C3.01317 3.7204 3.32975 3.7204 3.52502 3.52514C3.72028 3.32988 3.72028 3.01329 3.52502 2.81803L2.81791 2.11092Z" fill="#FB6514"/>
+<path d="M0.5 6C0.5 5.72386 0.723858 5.5 1 5.5H2C2.27614 5.5 2.5 5.72386 2.5 6C2.5 6.27614 2.27614 6.5 2 6.5H1C0.723858 6.5 0.5 6.27614 0.5 6Z" fill="#FB6514"/>
+<path d="M10 5.5C9.72386 5.5 9.5 5.72386 9.5 6C9.5 6.27614 9.72386 6.5 10 6.5H11C11.2761 6.5 11.5 6.27614 11.5 6C11.5 5.72386 11.2761 5.5 11 5.5H10Z" fill="#FB6514"/>
+<path d="M9.18192 8.47482C8.98666 8.27955 8.67008 8.27955 8.47482 8.47482C8.27955 8.67008 8.27955 8.98666 8.47482 9.18192L9.18192 9.88903C9.37718 10.0843 9.69377 10.0843 9.88903 9.88903C10.0843 9.69377 10.0843 9.37718 9.88903 9.18192L9.18192 8.47482Z" fill="#FB6514"/>
+<path d="M9.88903 2.81803C10.0843 2.62277 10.0843 2.30619 9.88903 2.11092C9.69377 1.91566 9.37718 1.91566 9.18192 2.11092L8.47482 2.81803C8.27955 3.01329 8.27955 3.32988 8.47482 3.52514C8.67008 3.7204 8.98666 3.7204 9.18192 3.52514L9.88903 2.81803Z" fill="#FB6514"/>
+<path d="M6 9.5C6.27614 9.5 6.5 9.72386 6.5 10V11C6.5 11.2761 6.27614 11.5 6 11.5C5.72386 11.5 5.5 11.2761 5.5 11V10C5.5 9.72386 5.72386 9.5 6 9.5Z" fill="#FB6514"/>
+<path d="M3.52502 9.18192C3.72028 8.98666 3.72028 8.67008 3.52502 8.47482C3.32975 8.27955 3.01317 8.27955 2.81791 8.47482L2.1108 9.18192C1.91554 9.37718 1.91554 9.69377 2.1108 9.88903C2.30606 10.0843 2.62265 10.0843 2.81791 9.88903L3.52502 9.18192Z" fill="#FB6514"/>
+<path d="M6.44837 3.27869C6.36413 3.10804 6.19032 3 6.00001 3C5.8097 3 5.6359 3.10804 5.55166 3.27869L4.89538 4.60823L3.4277 4.82276C3.23942 4.85028 3.08308 4.98228 3.02439 5.16328C2.9657 5.34429 3.01484 5.54291 3.15115 5.67568L4.21275 6.70968L3.96221 8.17048C3.93004 8.35807 4.00716 8.54766 4.16115 8.65953C4.31514 8.77139 4.51928 8.78613 4.68774 8.69754L6.00001 8.00742L7.31229 8.69754C7.48075 8.78613 7.68489 8.77139 7.83888 8.65953C7.99287 8.54766 8.06999 8.35807 8.03782 8.17048L7.78728 6.70968L8.84888 5.67568C8.98519 5.54291 9.03433 5.34429 8.97564 5.16328C8.91695 4.98228 8.76061 4.85028 8.57233 4.82276L7.10465 4.60823L6.44837 3.27869Z" fill="#FB6514"/>
+</svg>

+ 3 - 0
app/components/database/create/assets/trash.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6 2H10M2 4H14M12.6667 4L12.1991 11.0129C12.129 12.065 12.0939 12.5911 11.8667 12.99C11.6666 13.3412 11.3648 13.6235 11.0011 13.7998C10.588 14 10.0607 14 9.00623 14H6.99377C5.93927 14 5.41202 14 4.99889 13.7998C4.63517 13.6235 4.33339 13.3412 4.13332 12.99C3.90607 12.5911 3.871 12.065 3.80086 11.0129L3.33333 4M6.66667 7V10.3333M9.33333 7V10.3333" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 23 - 0
app/components/database/create/assets/txt.svg

@@ -0,0 +1,23 @@
+<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_3055_14432)">
+<path d="M4 7.73349C4 5.49329 4 4.37318 4.43597 3.51753C4.81947 2.76489 5.43139 2.15296 6.18404 1.76947C7.03969 1.3335 8.15979 1.3335 10.4 1.3335H18.6667L28 10.6668V24.2668C28 26.507 28 27.6271 27.564 28.4828C27.1805 29.2354 26.5686 29.8474 25.816 30.2309C24.9603 30.6668 23.8402 30.6668 21.6 30.6668H10.4C8.15979 30.6668 7.03969 30.6668 6.18404 30.2309C5.43139 29.8474 4.81947 29.2354 4.43597 28.4828C4 27.6271 4 26.507 4 24.2668V7.73349Z" fill="#E3E5E8"/>
+<path d="M4.25 7.73349C4.25 6.60926 4.25019 5.78113 4.30367 5.12666C4.3569 4.47511 4.46169 4.01774 4.65873 3.63103C5.01825 2.92542 5.59193 2.35175 6.29754 1.99222C6.68424 1.79518 7.14162 1.6904 7.79317 1.63716C8.44763 1.58369 9.27577 1.5835 10.4 1.5835H18.5631L27.75 10.7704V24.2668C27.75 25.3911 27.7498 26.2192 27.6963 26.8737C27.6431 27.5252 27.5383 27.9826 27.3413 28.3693C26.9817 29.0749 26.4081 29.6486 25.7025 30.0081C25.3158 30.2051 24.8584 30.3099 24.2068 30.3632C23.5524 30.4166 22.7242 30.4168 21.6 30.4168H10.4C9.27577 30.4168 8.44763 30.4166 7.79317 30.3632C7.14162 30.3099 6.68424 30.2051 6.29754 30.0081C5.59193 29.6486 5.01825 29.0749 4.65873 28.3693C4.46169 27.9826 4.3569 27.5252 4.30367 26.8737C4.25019 26.2192 4.25 25.3911 4.25 24.2668V7.73349Z" stroke="black" stroke-opacity="0.03" stroke-width="0.5"/>
+</g>
+<g opacity="0.96">
+<path d="M13.2254 19.5522V18.3042H9.02539V19.5522H10.4254V24.0002H11.8254V19.5522H13.2254Z" fill="#667085"/>
+<path d="M18.5371 24.0002L16.7611 21.0802L18.4251 18.3042H16.8331L16.0011 19.9122L15.1691 18.3042H13.5771L15.2411 21.0802L13.4651 24.0002H15.0651L16.0011 22.2482L16.9371 24.0002H18.5371Z" fill="#667085"/>
+<path d="M22.9754 19.5522V18.3042H18.7754V19.5522H20.1754V24.0002H21.5754V19.5522H22.9754Z" fill="#667085"/>
+</g>
+<path opacity="0.5" d="M18.6665 1.3335L27.9998 10.6668H21.3332C19.8604 10.6668 18.6665 9.47292 18.6665 8.00016V1.3335Z" fill="white"/>
+<defs>
+<filter id="filter0_d_3055_14432" x="2" y="0.333496" width="28" height="33.3335" 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_3055_14432"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3055_14432" result="shape"/>
+</filter>
+</defs>
+</svg>

+ 23 - 0
app/components/database/create/assets/unknown.svg

@@ -0,0 +1,23 @@
+<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_3055_14436)">
+<path d="M4 7.73349C4 5.49329 4 4.37318 4.43597 3.51753C4.81947 2.76489 5.43139 2.15296 6.18404 1.76947C7.03969 1.3335 8.15979 1.3335 10.4 1.3335H18.6667L28 10.6668V24.2668C28 26.507 28 27.6271 27.564 28.4828C27.1805 29.2354 26.5686 29.8474 25.816 30.2309C24.9603 30.6668 23.8402 30.6668 21.6 30.6668H10.4C8.15979 30.6668 7.03969 30.6668 6.18404 30.2309C5.43139 29.8474 4.81947 29.2354 4.43597 28.4828C4 27.6271 4 26.507 4 24.2668V7.73349Z" fill="#E3E5E8"/>
+<path d="M4.25 7.73349C4.25 6.60926 4.25019 5.78113 4.30367 5.12666C4.3569 4.47511 4.46169 4.01774 4.65873 3.63103C5.01825 2.92542 5.59193 2.35175 6.29754 1.99222C6.68424 1.79518 7.14162 1.6904 7.79317 1.63716C8.44763 1.58369 9.27577 1.5835 10.4 1.5835H18.5631L27.75 10.7704V24.2668C27.75 25.3911 27.7498 26.2192 27.6963 26.8737C27.6431 27.5252 27.5383 27.9826 27.3413 28.3693C26.9817 29.0749 26.4081 29.6486 25.7025 30.0081C25.3158 30.2051 24.8584 30.3099 24.2068 30.3632C23.5524 30.4166 22.7242 30.4168 21.6 30.4168H10.4C9.27577 30.4168 8.44763 30.4166 7.79317 30.3632C7.14162 30.3099 6.68424 30.2051 6.29754 30.0081C5.59193 29.6486 5.01825 29.0749 4.65873 28.3693C4.46169 27.9826 4.3569 27.5252 4.30367 26.8737C4.25019 26.2192 4.25 25.3911 4.25 24.2668V7.73349Z" stroke="black" stroke-opacity="0.03" stroke-width="0.5"/>
+</g>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9998 23.1992C15.8014 23.1992 15.6039 23.1968 15.4077 23.1924V24.0549C15.4077 24.3819 15.6728 24.647 15.9998 24.647C16.3268 24.647 16.592 24.3819 16.592 24.0549V23.1924C16.3957 23.1968 16.1983 23.1992 15.9998 23.1992Z" fill="#98A2B3"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0984 22.8838L11.757 23.8593C11.649 24.168 11.8117 24.5058 12.1203 24.6138C12.185 24.6364 12.251 24.6472 12.3159 24.6472C12.5605 24.6472 12.7894 24.4944 12.8747 24.2505L13.2936 23.0534C12.8807 23.0073 12.481 22.9506 12.0984 22.8838Z" fill="#98A2B3"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M20.2431 23.8593L19.9018 22.8838C19.5192 22.9506 19.1195 23.0073 18.7065 23.0534L19.1254 24.2505C19.2108 24.4944 19.4396 24.6472 19.6843 24.6472C19.7491 24.6472 19.8151 24.6364 19.8798 24.6138C20.1885 24.5058 20.3511 24.168 20.2431 23.8593Z" fill="#98A2B3"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1624 17.2634C20.2697 17.6416 20.3254 18.0369 20.3254 18.4409C20.3254 18.9087 20.05 19.3327 19.6226 19.5228C19.5564 19.5522 17.9801 20.2436 16.0359 20.2436C14.0917 20.2436 12.5153 19.5522 12.4492 19.5228C12.0218 19.3327 11.7464 18.9086 11.7464 18.4409C11.7464 18.0312 11.8037 17.6305 11.914 17.2476C10.3343 17.5645 8.5 18.2009 8.5 19.4464C8.5 20.2859 9.32512 20.9477 10.9525 21.4134C11.4194 21.547 11.9381 21.66 12.4949 21.7506C12.8783 21.813 13.28 21.8648 13.6953 21.9056C14.2455 21.9597 14.8197 21.9942 15.4079 22.0082C15.6039 22.0128 15.8013 22.0153 16 22.0153C16.1987 22.0153 16.3962 22.0128 16.5921 22.0082C17.1803 21.9943 17.7545 21.9596 18.3047 21.9056C18.72 21.8648 19.1217 21.8131 19.5051 21.7506C20.062 21.66 20.5807 21.547 21.0476 21.4134C22.6749 20.9477 23.5 20.2859 23.5 19.4464C23.5 18.2187 21.7108 17.5833 20.1624 17.2634Z" fill="#98A2B3"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M18.8441 17.1144C18.7585 16.9335 18.6559 16.7622 18.5384 16.6025C18.4174 16.4382 18.2809 16.286 18.1307 16.1486C17.5784 15.6437 16.8433 15.3354 16.036 15.3354C15.2318 15.3354 14.499 15.6411 13.9476 16.1426C13.7974 16.2791 13.6609 16.4303 13.5399 16.5937C13.4217 16.753 13.3185 16.924 13.2322 17.1048C13.039 17.5095 12.9307 17.9624 12.9307 18.4407C12.9307 18.4407 14.321 19.0592 16.036 19.0592C17.751 19.0592 19.1412 18.4407 19.1412 18.4407C19.1412 17.9662 19.0344 17.5167 18.8441 17.1144Z" fill="#98A2B3"/>
+<path opacity="0.5" d="M18.6665 1.3335L27.9998 10.6668H21.3332C19.8604 10.6668 18.6665 9.47292 18.6665 8.00016V1.3335Z" fill="white"/>
+<defs>
+<filter id="filter0_d_3055_14436" x="2" y="0.333496" width="28" height="33.3335" 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_3055_14436"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3055_14436" result="shape"/>
+</filter>
+</defs>
+</svg>

+ 4 - 0
app/components/database/create/assets/upload-cloud-01.svg

@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.4" d="M4 16.2422C2.79401 15.435 2 14.0602 2 12.5C2 10.1564 3.79151 8.23129 6.07974 8.01937C6.54781 5.17213 9.02024 3 12 3C14.9798 3 17.4522 5.17213 17.9203 8.01937C20.2085 8.23129 22 10.1564 22 12.5C22 14.0602 21.206 15.435 20 16.2422" stroke="#344054" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 16L12 12M12 12L16 16M12 12L12 21" stroke="#344054" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 4 - 0
app/components/database/create/assets/web.svg

@@ -0,0 +1,4 @@
+<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7.06124 2.21694C7.57213 2.07545 8.11041 1.99984 8.66634 1.99984C9.97432 1.99984 11.1845 2.41837 12.1704 3.12881C11.6459 3.4755 11.2546 3.85196 10.9834 4.25446C10.569 4.86947 10.4628 5.50546 10.5486 6.08575C10.632 6.64928 10.8907 7.13053 11.1627 7.48656C11.4265 7.83177 11.7501 8.12048 12.0351 8.26293C12.7526 8.62164 13.5806 8.80129 14.1996 8.89387C14.7596 8.97762 15.9468 9.2579 15.9907 8.36596C15.9967 8.24461 15.9997 8.12255 15.9997 7.99984C15.9997 3.94975 12.7164 0.666504 8.66634 0.666504C4.61625 0.666504 1.33301 3.94975 1.33301 7.99984C1.33301 12.0499 4.61625 15.3332 8.66634 15.3332C8.78906 15.3332 8.91112 15.3301 9.03246 15.3242C9.40021 15.3061 9.68364 14.9933 9.66553 14.6255C9.64743 14.2578 9.33463 13.9743 8.96689 13.9925C8.86737 13.9974 8.76717 13.9998 8.66634 13.9998C6.32676 13.9998 4.29993 12.6608 3.31046 10.7073L3.95617 10.3345L6.33797 10.7803C6.95507 10.8958 7.52471 10.4207 7.52192 9.79289L7.51258 7.69081L8.72983 5.60684C8.92705 5.2692 8.90938 4.84758 8.68459 4.52762L7.06124 2.21694Z" fill="#1570EF"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.96344 8.43724C9.71815 8.3442 9.4411 8.40367 9.2556 8.58917C9.07009 8.77467 9.01063 9.05172 9.10367 9.29701L11.2149 14.8629C11.3123 15.1198 11.5575 15.2906 11.8323 15.2931C12.1071 15.2955 12.3552 15.1291 12.4572 14.8739L13.3377 12.6713L15.5403 11.7908C15.7955 11.6888 15.9619 11.4407 15.9595 11.1659C15.9571 10.891 15.7862 10.6459 15.5293 10.5484L9.96344 8.43724Z" fill="#1570EF"/>
+</svg>

+ 18 - 0
app/components/database/create/assets/xlsx.svg

@@ -0,0 +1,18 @@
+<svg width="24" height="26" viewBox="0 0 24 26" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g filter="url(#filter0_d_5938_927)">
+<path d="M3 5.8C3 4.11984 3 3.27976 3.32698 2.63803C3.6146 2.07354 4.07354 1.6146 4.63803 1.32698C5.27976 1 6.11984 1 7.8 1H14L21 8V18.2C21 19.8802 21 20.7202 20.673 21.362C20.3854 21.9265 19.9265 22.3854 19.362 22.673C18.7202 23 17.8802 23 16.2 23H7.8C6.11984 23 5.27976 23 4.63803 22.673C4.07354 22.3854 3.6146 21.9265 3.32698 21.362C3 20.7202 3 19.8802 3 18.2V5.8Z" fill="#169951"/>
+</g>
+<path opacity="0.5" d="M14 1L21 8H16C14.8954 8 14 7.10457 14 6V1Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M17 12C17.5523 12 18 12.4477 18 13V18C18 18.5523 17.5523 19 17 19H7C6.44772 19 6 18.5523 6 18V13C6 12.4477 6.44772 12 7 12H17ZM11.5 13H7L7 15H11.5V13ZM12.5 18H17V16H12.5V18ZM11.5 16V18H7L7 16H11.5ZM12.5 15H17V13H12.5V15Z" fill="white" fill-opacity="0.96"/>
+<defs>
+<filter id="filter0_d_5938_927" x="1" y="0" width="22" height="26" 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_5938_927"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5938_927" result="shape"/>
+</filter>
+</defs>
+</svg>

+ 6 - 0
app/components/database/create/assets/zap-fast.svg

@@ -0,0 +1,6 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1.66675 11.6666C1.66675 11.2985 1.96522 11 2.33341 11H6.00008C6.36827 11 6.66675 11.2985 6.66675 11.6666C6.66675 12.0348 6.36827 12.3333 6.00008 12.3333H2.33341C1.96522 12.3333 1.66675 12.0348 1.66675 11.6666Z" fill="#7839EE"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0.666748 7.99997C0.666748 7.63178 0.965225 7.33331 1.33341 7.33331H4.33341C4.7016 7.33331 5.00008 7.63178 5.00008 7.99997C5.00008 8.36816 4.7016 8.66664 4.33341 8.66664H1.33341C0.965225 8.66664 0.666748 8.36816 0.666748 7.99997Z" fill="#7839EE"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M2.00008 4.33331C2.00008 3.96512 2.29856 3.66664 2.66675 3.66664H6.00008C6.36827 3.66664 6.66675 3.96512 6.66675 4.33331C6.66675 4.7015 6.36827 4.99997 6.00008 4.99997H2.66675C2.29856 4.99997 2.00008 4.7015 2.00008 4.33331Z" fill="#7839EE"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5785 1.37998C11.8632 1.49253 12.0347 1.78458 11.9942 2.08808L11.4282 6.33331H13.9637C13.9714 6.33331 13.979 6.33331 13.9867 6.3333C14.1339 6.33327 14.2864 6.33324 14.4124 6.34476C14.5363 6.35609 14.7618 6.38612 14.9633 6.54167C15.1984 6.72308 15.3407 6.99955 15.3517 7.29625C15.3611 7.55067 15.2545 7.7516 15.1917 7.85904C15.1278 7.96825 15.0391 8.09234 14.9536 8.21207C14.9491 8.21833 14.9446 8.22457 14.9402 8.23079L10.5426 14.3875C10.3646 14.6366 10.0398 14.7325 9.75503 14.62C9.47027 14.5074 9.29879 14.2154 9.33926 13.9119L9.90529 9.66664H7.36978C7.36213 9.66664 7.35447 9.66664 7.34679 9.66664C7.19962 9.66668 7.0471 9.66671 6.92111 9.65519C6.79717 9.64386 6.5717 9.61383 6.37015 9.45828C6.13511 9.27687 5.99283 9.0004 5.98183 8.7037C5.9724 8.44928 6.07901 8.24835 6.14183 8.14091C6.20569 8.0317 6.29437 7.9076 6.37993 7.78787C6.3844 7.78162 6.38886 7.77538 6.3933 7.76915L10.7909 1.61248C10.9689 1.36332 11.2937 1.26743 11.5785 1.37998Z" fill="#7839EE"/>
+</svg>

+ 115 - 0
app/components/database/create/embedding-process/index.module.css

@@ -0,0 +1,115 @@
+.progressContainer {
+  @apply relative pb-4 w-full;
+  border-bottom: 0.5px solid #EAECF0;
+}
+.sourceItem {
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 4px;
+  padding: 0 4px;
+  height: 24px;
+  background: #EFF4FF;
+  border-radius: 6px;
+  overflow: hidden;
+}
+.sourceItem.error {
+  background: #FEE4E2;
+}
+.sourceItem.success {
+  background: #D1FADF;
+}
+.progressbar {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  background-color: #B2CCFF;
+}
+.sourceItem .info {
+  display: flex;
+  align-items: center;
+  z-index: 1;
+}
+.sourceItem .info .name {
+  font-weight: 500;
+  font-size: 12px;
+  line-height: 18px;
+  color: #101828;
+}
+.sourceItem.success .info .name {
+  color: #05603A;
+}
+.sourceItem .percent {
+  font-weight: 500;
+  font-size: 12px;
+  line-height: 18px;
+  color: #344054;
+  z-index: 1;
+}
+.sourceItem .error {
+  color: #D92D20;
+}
+.sourceItem .success {
+  color: #05603A;
+}
+
+
+.cost {
+  @apply flex justify-between items-center text-xs text-gray-700;
+}
+.embeddingStatus {
+  @apply flex items-center justify-between text-gray-900 font-medium text-sm mr-2;
+}
+.commonIcon {
+  @apply w-3 h-3 mr-1 inline-block align-middle;
+}
+.highIcon {
+  mask-image: url(../assets/star.svg);
+  @apply bg-orange-500;
+}
+.economyIcon {
+  background-color: #444ce7;
+  mask-image: url(../assets/normal.svg);
+}
+.tokens {
+  @apply text-xs font-medium px-1;
+}
+.price {
+  color: #f79009;
+  @apply text-xs font-medium;
+}
+
+.fileIcon {
+  @apply w-4 h-4 mr-1 bg-center bg-no-repeat;
+  background-image: url(../assets/unknown.svg);
+  background-size: 16px;
+}
+.fileIcon.csv {
+  background-image: url(../assets/csv.svg);
+}
+.fileIcon.docx {
+  background-image: url(../assets/docx.svg);
+}
+.fileIcon.xlsx,
+.fileIcon.xls {
+  background-image: url(../assets/xlsx.svg);
+}
+.fileIcon.pdf {
+  background-image: url(../assets/pdf.svg);
+}
+.fileIcon.html,
+.fileIcon.htm {
+  background-image: url(../assets/html.svg);
+}
+.fileIcon.md,
+.fileIcon.markdown {
+  background-image: url(../assets/md.svg);
+}
+.fileIcon.txt {
+  background-image: url(../assets/txt.svg);
+}
+.fileIcon.json {
+  background-image: url(../assets/json.svg);
+}

+ 272 - 0
app/components/database/create/embedding-process/index.tsx

@@ -0,0 +1,272 @@
+import type { FC } from 'react'
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import useSWR from 'swr'
+import { useRouter } from 'next/navigation'
+import { useTranslation } from 'react-i18next'
+import { omit } from 'lodash-es'
+import { ArrowRightIcon } from '@heroicons/react/24/solid'
+import {
+  RiErrorWarningFill,
+} from '@remixicon/react'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import { FieldInfo } from '@/app/components/datasets/documents/detail/metadata'
+import Button from '@/app/components/base/button'
+import type { FullDocumentDetail, IndexingStatusResponse, ProcessRuleResponse } from '@/models/datasets'
+import { fetchIndexingStatusBatch as doFetchIndexingStatus, fetchProcessRule } from '@/service/datasets'
+import { DataSourceType } from '@/models/datasets'
+import NotionIcon from '@/app/components/base/notion-icon'
+import PriorityLabel from '@/app/components/billing/priority-label'
+import { Plan } from '@/app/components/billing/type'
+import { ZapFast } from '@/app/components/base/icons/src/vender/solid/general'
+import UpgradeBtn from '@/app/components/billing/upgrade-btn'
+import { useProviderContext } from '@/context/provider-context'
+import Tooltip from '@/app/components/base/tooltip'
+import { sleep } from '@/utils'
+
+type Props = {
+  datasetId: string
+  batchId: string
+  documents?: FullDocumentDetail[]
+  indexingType?: string
+}
+
+const RuleDetail: FC<{ sourceData?: ProcessRuleResponse }> = ({ sourceData }) => {
+  const { t } = useTranslation()
+
+  const segmentationRuleMap = {
+    mode: t('datasetDocuments.embedding.mode'),
+    segmentLength: t('datasetDocuments.embedding.segmentLength'),
+    textCleaning: t('datasetDocuments.embedding.textCleaning'),
+  }
+
+  const getRuleName = (key: string) => {
+    if (key === 'remove_extra_spaces')
+      return t('datasetCreation.stepTwo.removeExtraSpaces')
+
+    if (key === 'remove_urls_emails')
+      return t('datasetCreation.stepTwo.removeUrlEmails')
+
+    if (key === 'remove_stopwords')
+      return t('datasetCreation.stepTwo.removeStopwords')
+  }
+
+  const getValue = useCallback((field: string) => {
+    let value: string | number | undefined = '-'
+    switch (field) {
+      case 'mode':
+        value = sourceData?.mode === 'automatic' ? (t('datasetDocuments.embedding.automatic') as string) : (t('datasetDocuments.embedding.custom') as string)
+        break
+      case 'segmentLength':
+        value = sourceData?.rules?.segmentation?.max_tokens
+        break
+      default:
+        value = sourceData?.mode === 'automatic'
+          ? (t('datasetDocuments.embedding.automatic') as string)
+          // eslint-disable-next-line array-callback-return
+          : sourceData?.rules?.pre_processing_rules?.map((rule) => {
+            if (rule.enabled)
+              return getRuleName(rule.id)
+          }).filter(Boolean).join(';')
+        break
+    }
+    return value
+  }, [sourceData])
+
+  return <div className='flex flex-col pt-8 pb-10 first:mt-0'>
+    {Object.keys(segmentationRuleMap).map((field) => {
+      return <FieldInfo
+        key={field}
+        label={segmentationRuleMap[field as keyof typeof segmentationRuleMap]}
+        displayedValue={String(getValue(field))}
+      />
+    })}
+  </div>
+}
+
+const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], indexingType }) => {
+  const { t } = useTranslation()
+  const { enableBilling, plan } = useProviderContext()
+
+  const getFirstDocument = documents[0]
+
+  const [indexingStatusBatchDetail, setIndexingStatusDetail] = useState<IndexingStatusResponse[]>([])
+  const fetchIndexingStatus = async () => {
+    const status = await doFetchIndexingStatus({ datasetId, batchId })
+    setIndexingStatusDetail(status.data)
+    return status.data
+  }
+
+  const [isStopQuery, setIsStopQuery] = useState(false)
+  const isStopQueryRef = useRef(isStopQuery)
+  useEffect(() => {
+    isStopQueryRef.current = isStopQuery
+  }, [isStopQuery])
+  const stopQueryStatus = () => {
+    setIsStopQuery(true)
+  }
+
+  const startQueryStatus = async () => {
+    if (isStopQueryRef.current)
+      return
+
+    try {
+      const indexingStatusBatchDetail = await fetchIndexingStatus()
+      const isCompleted = indexingStatusBatchDetail.every(indexingStatusDetail => ['completed', 'error', 'paused'].includes(indexingStatusDetail.indexing_status))
+      if (isCompleted) {
+        stopQueryStatus()
+        return
+      }
+      await sleep(2500)
+      await startQueryStatus()
+    }
+    catch (e) {
+      await sleep(2500)
+      await startQueryStatus()
+    }
+  }
+
+  useEffect(() => {
+    startQueryStatus()
+    return () => {
+      stopQueryStatus()
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [])
+
+  // get rule
+  const { data: ruleDetail } = useSWR({
+    action: 'fetchProcessRule',
+    params: { documentId: getFirstDocument.id },
+  }, apiParams => fetchProcessRule(omit(apiParams, 'action')), {
+    revalidateOnFocus: false,
+  })
+
+  const router = useRouter()
+  const navToDocumentList = () => {
+    router.push(`/datasets/${datasetId}/documents`)
+  }
+
+  const isEmbedding = useMemo(() => {
+    return indexingStatusBatchDetail.some(indexingStatusDetail => ['indexing', 'splitting', 'parsing', 'cleaning'].includes(indexingStatusDetail?.indexing_status || ''))
+  }, [indexingStatusBatchDetail])
+  const isEmbeddingCompleted = useMemo(() => {
+    return indexingStatusBatchDetail.every(indexingStatusDetail => ['completed', 'error', 'paused'].includes(indexingStatusDetail?.indexing_status || ''))
+  }, [indexingStatusBatchDetail])
+
+  const getSourceName = (id: string) => {
+    const doc = documents.find(document => document.id === id)
+    return doc?.name
+  }
+  const getFileType = (name?: string) => name?.split('.').pop() || 'txt'
+  const getSourcePercent = (detail: IndexingStatusResponse) => {
+    const completedCount = detail.completed_segments || 0
+    const totalCount = detail.total_segments || 0
+    if (totalCount === 0)
+      return 0
+    const percent = Math.round(completedCount * 100 / totalCount)
+    return percent > 100 ? 100 : percent
+  }
+  const getSourceType = (id: string) => {
+    const doc = documents.find(document => document.id === id)
+    return doc?.data_source_type as DataSourceType
+  }
+
+  const getIcon = (id: string) => {
+    const doc = documents.find(document => document.id === id)
+
+    return doc?.data_source_info.notion_page_icon
+  }
+  const isSourceEmbedding = (detail: IndexingStatusResponse) => ['indexing', 'splitting', 'parsing', 'cleaning', 'waiting'].includes(detail.indexing_status || '')
+
+  return (
+    <>
+      <div className='h-5 flex items-center mb-5'>
+        <div className={s.embeddingStatus}>
+          {isEmbedding && t('datasetDocuments.embedding.processing')}
+          {isEmbeddingCompleted && t('datasetDocuments.embedding.completed')}
+        </div>
+      </div>
+      {
+        enableBilling && plan.type !== Plan.team && (
+          <div className='flex items-center mb-3 p-3 h-14 bg-white border-[0.5px] border-black/5 shadow-md rounded-xl'>
+            <div className='shrink-0 flex items-center justify-center w-8 h-8 bg-[#FFF6ED] rounded-lg'>
+              <ZapFast className='w-4 h-4 text-[#FB6514]' />
+            </div>
+            <div className='grow mx-3 text-[13px] font-medium text-gray-700'>
+              {t('billing.plansCommon.documentProcessingPriorityUpgrade')}
+            </div>
+            <UpgradeBtn loc='knowledge-speed-up' />
+          </div>
+        )
+      }
+      <div className={s.progressContainer}>
+        {indexingStatusBatchDetail.map(indexingStatusDetail => (
+          <div key={indexingStatusDetail.id} className={cn(
+            s.sourceItem,
+            indexingStatusDetail.indexing_status === 'error' && s.error,
+            indexingStatusDetail.indexing_status === 'completed' && s.success,
+          )}>
+            {isSourceEmbedding(indexingStatusDetail) && (
+              <div className={s.progressbar} style={{ width: `${getSourcePercent(indexingStatusDetail)}%` }} />
+            )}
+            <div className={`${s.info} grow`}>
+              {getSourceType(indexingStatusDetail.id) === DataSourceType.FILE && (
+                <div className={cn(s.fileIcon, s[getFileType(getSourceName(indexingStatusDetail.id))])} />
+              )}
+              {getSourceType(indexingStatusDetail.id) === DataSourceType.NOTION && (
+                <NotionIcon
+                  className='shrink-0 mr-1'
+                  type='page'
+                  src={getIcon(indexingStatusDetail.id)}
+                />
+              )}
+              <div className={`${s.name} truncate`} title={getSourceName(indexingStatusDetail.id)}>{getSourceName(indexingStatusDetail.id)}</div>
+              {
+                enableBilling && (
+                  <PriorityLabel />
+                )
+              }
+            </div>
+            <div className='shrink-0'>
+              {isSourceEmbedding(indexingStatusDetail) && (
+                <div className={s.percent}>{`${getSourcePercent(indexingStatusDetail)}%`}</div>
+              )}
+              {indexingStatusDetail.indexing_status === 'error' && indexingStatusDetail.error && (
+                <Tooltip
+                  popupContent={(
+                    <div className='max-w-[400px]'>
+                      {indexingStatusDetail.error}
+                    </div>
+                  )}
+                >
+                  <div className={cn(s.percent, s.error, 'flex items-center')}>
+                    Error
+                    <RiErrorWarningFill className='ml-1 w-4 h-4' />
+                  </div>
+                </Tooltip>
+              )}
+              {indexingStatusDetail.indexing_status === 'error' && !indexingStatusDetail.error && (
+                <div className={cn(s.percent, s.error, 'flex items-center')}>
+                  Error
+                </div>
+              )}
+              {indexingStatusDetail.indexing_status === 'completed' && (
+                <div className={cn(s.percent, s.success)}>100%</div>
+              )}
+            </div>
+          </div>
+        ))}
+      </div>
+      <RuleDetail sourceData={ruleDetail} />
+      <div className='flex items-center gap-2 mt-10'>
+        <Button className='w-fit' variant='primary' onClick={navToDocumentList}>
+          <span>{t('datasetCreation.stepThree.navTo')}</span>
+          <ArrowRightIcon className='h-4 w-4 ml-2 stroke-current stroke-1' />
+        </Button>
+      </div>
+    </>
+  )
+}
+
+export default EmbeddingProcess

+ 38 - 0
app/components/database/create/empty-dataset-creation-modal/index.module.css

@@ -0,0 +1,38 @@
+.modal {
+  position: relative;
+}
+
+.modalHeader {
+  @apply flex items-center place-content-between h-8;
+}
+.modalHeader .title {
+  @apply grow;
+  font-weight: 600;
+  font-size: 20px;
+  line-height: 32px;
+  color: #101828;
+}
+.modalHeader .close {
+  @apply shrink-0 h-4 w-4 bg-center bg-no-repeat cursor-pointer;
+  background-image: url(../assets/close.svg);
+  background-size: 16px;
+}
+
+.modal .tip {
+  @apply mt-1 mb-8;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  color: #667085;
+}
+
+.form {
+  @apply mb-8;
+}
+.form .label {
+  @apply mb-2;
+  font-weight: 500;
+  font-size: 14px;
+  line-height: 20px;
+  color: #101828;
+}

+ 71 - 0
app/components/database/create/empty-dataset-creation-modal/index.tsx

@@ -0,0 +1,71 @@
+'use client'
+import React, { useState } from 'react'
+import { useRouter } from 'next/navigation'
+import { useTranslation } from 'react-i18next'
+import { useContext } from 'use-context-selector'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import Modal from '@/app/components/base/modal'
+import Input from '@/app/components/base/input'
+import Button from '@/app/components/base/button'
+
+import { ToastContext } from '@/app/components/base/toast'
+import { createEmptyDataset } from '@/service/datasets'
+
+type IProps = {
+  show: boolean
+  onHide: () => void
+}
+
+const EmptyDatasetCreationModal = ({
+  show = false,
+  onHide,
+}: IProps) => {
+  const [inputValue, setInputValue] = useState('')
+  const { t } = useTranslation()
+  const { notify } = useContext(ToastContext)
+  const router = useRouter()
+
+  const submit = async () => {
+    if (!inputValue) {
+      notify({ type: 'error', message: t('datasetCreation.stepOne.modal.nameNotEmpty') })
+      return
+    }
+    if (inputValue.length > 40) {
+      notify({ type: 'error', message: t('datasetCreation.stepOne.modal.nameLengthInvalid') })
+      return
+    }
+    try {
+      const dataset = await createEmptyDataset({ name: inputValue })
+      onHide()
+      router.push(`/datasets/${dataset.id}/documents`)
+    }
+    catch (err) {
+      notify({ type: 'error', message: t('datasetCreation.stepOne.modal.failed') })
+    }
+  }
+
+  return (
+    <Modal
+      isShow={show}
+      onClose={onHide}
+      className={cn(s.modal, '!max-w-[520px]', 'px-8')}
+    >
+      <div className={s.modalHeader}>
+        <div className={s.title}>{t('datasetCreation.stepOne.modal.title')}</div>
+        <span className={s.close} onClick={onHide} />
+      </div>
+      <div className={s.tip}>{t('datasetCreation.stepOne.modal.tip')}</div>
+      <div className={s.form}>
+        <div className={s.label}>{t('datasetCreation.stepOne.modal.input')}</div>
+        <Input value={inputValue} placeholder={t('datasetCreation.stepOne.modal.placeholder') || ''} onChange={e => setInputValue(e.target.value)} />
+      </div>
+      <div className='flex flex-row-reverse'>
+        <Button className='w-24 ml-2' variant='primary' onClick={submit}>{t('datasetCreation.stepOne.modal.confirmButton')}</Button>
+        <Button className='w-24' onClick={onHide}>{t('datasetCreation.stepOne.modal.cancelButton')}</Button>
+      </div>
+    </Modal>
+  )
+}
+
+export default EmptyDatasetCreationModal

+ 52 - 0
app/components/database/create/file-preview/index.module.css

@@ -0,0 +1,52 @@
+.filePreview {
+    @apply flex flex-col border-l border-gray-200 shrink-0;
+    width: 528px;
+    background-color: #fcfcfd;
+  }
+  
+  .previewHeader {
+    @apply border-b border-gray-200 shrink-0;
+    margin: 42px 32px 0;
+    padding-bottom: 16px;
+  }
+  
+  .previewHeader .title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    color: #101828;
+    font-weight: 600;
+    font-size: 18px;
+    line-height: 28px;
+  }
+  
+  .previewHeader .fileName {
+    font-weight: 400;
+    font-size: 12px;
+    line-height: 18px;
+    color: #1D2939;
+  }
+  
+  .previewHeader .filetype {
+    color: #667085;
+  }
+  
+  .previewContent {
+    @apply overflow-y-auto grow;
+    padding: 20px 32px;
+    font-weight: 400;
+    font-size: 16px;
+    line-height: 24px;
+    color: #344054;
+  }
+  
+  .previewContent .loading {
+    width: 100%;
+    height: 180px;
+    background: #f9fafb center no-repeat url(../assets/Loading.svg);
+    background-size: contain;
+  }
+  .fileContent {
+    white-space: pre-line;
+  }
+  

+ 69 - 0
app/components/database/create/file-preview/index.tsx

@@ -0,0 +1,69 @@
+'use client'
+import React, { useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { XMarkIcon } from '@heroicons/react/20/solid'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import type { CustomFile as File } from '@/models/datasets'
+import { fetchFilePreview } from '@/service/common'
+
+type IProps = {
+  file?: File
+  hidePreview: () => void
+}
+
+const FilePreview = ({
+  file,
+  hidePreview,
+}: IProps) => {
+  const { t } = useTranslation()
+  const [previewContent, setPreviewContent] = useState('')
+  const [loading, setLoading] = useState(true)
+
+  const getPreviewContent = async (fileID: string) => {
+    try {
+      const res = await fetchFilePreview({ fileID })
+      setPreviewContent(res.content)
+      setLoading(false)
+    }
+    catch { }
+  }
+
+  const getFileName = (currentFile?: File) => {
+    if (!currentFile)
+      return ''
+    const arr = currentFile.name.split('.')
+    return arr.slice(0, -1).join()
+  }
+
+  useEffect(() => {
+    if (file?.id) {
+      setLoading(true)
+      getPreviewContent(file.id)
+    }
+  }, [file])
+
+  return (
+    <div className={cn(s.filePreview)}>
+      <div className={cn(s.previewHeader)}>
+        <div className={cn(s.title)}>
+          <span>{t('datasetCreation.stepOne.filePreview')}</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={cn(s.fileName)}>
+          <span>{getFileName(file)}</span><span className={cn(s.filetype)}>.{file?.extension}</span>
+        </div>
+      </div>
+      <div className={cn(s.previewContent)}>
+        {loading && <div className={cn(s.loading)} />}
+        {!loading && (
+          <div className={cn(s.fileContent)}>{previewContent}</div>
+        )}
+      </div>
+    </div>
+  )
+}
+
+export default FilePreview

+ 196 - 0
app/components/database/create/file-uploader/index.module.css

@@ -0,0 +1,196 @@
+.fileUploader {
+  @apply mb-6;
+}
+
+.fileUploader .title {
+  @apply mb-2;
+  font-weight: 500;
+  font-size: 16px;
+  line-height: 24px;
+  color: #344054;
+}
+
+.fileUploader .tip {
+  font-weight: 400;
+  font-size: 12px;
+  line-height: 18px;
+  color: #667085;
+}
+
+.uploader {
+  @apply relative box-border flex justify-center items-center mb-2 p-3;
+  flex-direction: column;
+  max-width: 640px;
+  min-height: 80px;
+  background: #F9FAFB;
+  border: 1px dashed #EAECF0;
+  border-radius: 12px;
+  font-weight: 400;
+  font-size: 14px;
+  line-height: 20px;
+  color: #667085;
+}
+
+.uploader.dragging {
+  background: #F5F8FF;
+  border: 1px dashed #B2CCFF;
+}
+
+.uploader .draggingCover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
+
+.uploader .uploadIcon {
+  content: '';
+  display: block;
+  margin-right: 8px;
+  width: 24px;
+  height: 24px;
+  background: center no-repeat url(../assets/upload-cloud-01.svg);
+  background-size: contain;
+}
+
+.uploader .browse {
+  @apply pl-1 cursor-pointer;
+  color: #155eef;
+}
+
+.fileList {
+  @apply space-y-2;
+}
+
+.file {
+  @apply box-border relative flex items-center justify-between;
+  padding: 8px 12px 8px 8px;
+  max-width: 640px;
+  height: 40px;
+  background: #ffffff;
+  border: 0.5px solid #EAECF0;
+  box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
+  border-radius: 8px;
+  overflow: hidden;
+  cursor: pointer;
+}
+
+.progressbar {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  background-color: #F2F4F7;
+}
+
+.file.uploading,
+.file.uploading:hover {
+  background: #FCFCFD;
+  border: 0.5px solid #EAECF0;
+}
+
+.file.active {
+  background: #F5F8FF;
+  border: 1px solid #D1E0FF;
+  box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
+}
+
+.file:hover {
+  background: #F5F8FF;
+  border: 1px solid #D1E0FF;
+  box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
+}
+
+.fileIcon {
+  @apply shrink-0 w-6 h-6 mr-2 bg-center bg-no-repeat;
+  background-image: url(../assets/unknown.svg);
+  background-size: 24px;
+}
+
+.fileIcon.csv {
+  background-image: url(../assets/csv.svg);
+}
+
+.fileIcon.doc {
+  background-image: url(../assets/doc.svg);
+}
+
+.fileIcon.docx {
+  background-image: url(../assets/docx.svg);
+}
+
+.fileIcon.xlsx,
+.fileIcon.xls {
+  background-image: url(../assets/xlsx.svg);
+}
+
+.fileIcon.pdf {
+  background-image: url(../assets/pdf.svg);
+}
+
+.fileIcon.html,
+.fileIcon.htm {
+  background-image: url(../assets/html.svg);
+}
+
+.fileIcon.md,
+.fileIcon.markdown {
+  background-image: url(../assets/md.svg);
+}
+
+.fileIcon.txt {
+  background-image: url(../assets/txt.svg);
+}
+
+.fileIcon.json {
+  background-image: url(../assets/json.svg);
+}
+
+.fileInfo {
+  @apply grow flex items-center;
+  z-index: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.filename {
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 18px;
+  color: #1D2939;
+}
+
+.size {
+  @apply ml-3;
+  font-weight: 400;
+  font-size: 12px;
+  line-height: 18px;
+  color: #667085;
+}
+
+.actionWrapper {
+  @apply flex items-center shrink-0;
+  z-index: 1;
+}
+
+.actionWrapper .percent {
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  color: #344054;
+}
+
+.actionWrapper .remove {
+  display: none;
+  width: 24px;
+  height: 24px;
+  background: center no-repeat url(../assets/trash.svg);
+  background-size: 16px;
+  cursor: pointer;
+}
+
+.file:hover .actionWrapper .remove {
+  display: block;
+}

+ 306 - 0
app/components/database/create/file-uploader/index.tsx

@@ -0,0 +1,306 @@
+'use client'
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useContext } from 'use-context-selector'
+import useSWR from 'swr'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import type { CustomFile as File, FileItem } from '@/models/datasets'
+import { ToastContext } from '@/app/components/base/toast'
+
+import { upload } from '@/service/base'
+import { fetchFileUploadConfig } from '@/service/common'
+import { fetchSupportFileTypes } from '@/service/datasets'
+import I18n from '@/context/i18n'
+import { LanguagesSupported } from '@/i18n/language'
+import { IS_CE_EDITION } from '@/config'
+
+const FILES_NUMBER_LIMIT = 20
+
+type IFileUploaderProps = {
+  fileList: FileItem[]
+  titleClassName?: string
+  prepareFileList: (files: FileItem[]) => void
+  onFileUpdate: (fileItem: FileItem, progress: number, list: FileItem[]) => void
+  onFileListUpdate?: (files: FileItem[]) => void
+  onPreview: (file: File) => void
+  notSupportBatchUpload?: boolean
+}
+
+const FileUploader = ({
+  fileList,
+  titleClassName,
+  prepareFileList,
+  onFileUpdate,
+  onFileListUpdate,
+  onPreview,
+  notSupportBatchUpload,
+}: IFileUploaderProps) => {
+  const { t } = useTranslation()
+  const { notify } = useContext(ToastContext)
+  const { locale } = useContext(I18n)
+  const [dragging, setDragging] = useState(false)
+  const dropRef = useRef<HTMLDivElement>(null)
+  const dragRef = useRef<HTMLDivElement>(null)
+  const fileUploader = useRef<HTMLInputElement>(null)
+  const hideUpload = notSupportBatchUpload && fileList.length > 0
+
+  const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
+  const { data: supportFileTypesResponse } = useSWR({ url: '/files/support-type' }, fetchSupportFileTypes)
+  const supportTypes = supportFileTypesResponse?.allowed_extensions || []
+  const supportTypesShowNames = (() => {
+    const extensionMap: { [key: string]: string } = {
+      md: 'markdown',
+      pptx: 'pptx',
+      htm: 'html',
+      xlsx: 'xlsx',
+      docx: 'docx',
+    }
+
+    return [...supportTypes]
+      .map(item => extensionMap[item] || item) // map to standardized extension
+      .map(item => item.toLowerCase()) // convert to lower case
+      .filter((item, index, self) => self.indexOf(item) === index) // remove duplicates
+      .map(item => item.toUpperCase()) // convert to upper case
+      .join(locale !== LanguagesSupported[1] ? ', ' : '、 ')
+  })()
+  const ACCEPTS = supportTypes.map((ext: string) => `.${ext}`)
+  const fileUploadConfig = useMemo(() => fileUploadConfigResponse ?? {
+    file_size_limit: 15,
+    batch_count_limit: 5,
+  }, [fileUploadConfigResponse])
+
+  const fileListRef = useRef<FileItem[]>([])
+
+  // utils
+  const getFileType = (currentFile: File) => {
+    if (!currentFile)
+      return ''
+
+    const arr = currentFile.name.split('.')
+    return arr[arr.length - 1]
+  }
+
+  const getFileSize = (size: number) => {
+    if (size / 1024 < 10)
+      return `${(size / 1024).toFixed(2)}KB`
+
+    return `${(size / 1024 / 1024).toFixed(2)}MB`
+  }
+
+  const isValid = useCallback((file: File) => {
+    const { size } = file
+    const ext = `.${getFileType(file)}`
+    const isValidType = ACCEPTS.includes(ext.toLowerCase())
+    if (!isValidType)
+      notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.typeError') })
+
+    const isValidSize = size <= fileUploadConfig.file_size_limit * 1024 * 1024
+    if (!isValidSize)
+      notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.size', { size: fileUploadConfig.file_size_limit }) })
+
+    return isValidType && isValidSize
+  }, [fileUploadConfig, notify, t, ACCEPTS])
+
+  const fileUpload = useCallback(async (fileItem: FileItem): Promise<FileItem> => {
+    const formData = new FormData()
+    formData.append('file', fileItem.file)
+    const onProgress = (e: ProgressEvent) => {
+      if (e.lengthComputable) {
+        const percent = Math.floor(e.loaded / e.total * 100)
+        onFileUpdate(fileItem, percent, fileListRef.current)
+      }
+    }
+
+    return upload({
+      xhr: new XMLHttpRequest(),
+      data: formData,
+      onprogress: onProgress,
+    }, false, undefined, '?source=datasets')
+      .then((res: File) => {
+        const completeFile = {
+          fileID: fileItem.fileID,
+          file: res,
+          progress: -1,
+        }
+        const index = fileListRef.current.findIndex(item => item.fileID === fileItem.fileID)
+        fileListRef.current[index] = completeFile
+        onFileUpdate(completeFile, 100, fileListRef.current)
+        return Promise.resolve({ ...completeFile })
+      })
+      .catch((e) => {
+        notify({ type: 'error', message: e?.response?.code === 'forbidden' ? e?.response?.message : t('datasetCreation.stepOne.uploader.failed') })
+        onFileUpdate(fileItem, -2, fileListRef.current)
+        return Promise.resolve({ ...fileItem })
+      })
+      .finally()
+  }, [fileListRef, notify, onFileUpdate, t])
+
+  const uploadBatchFiles = useCallback((bFiles: FileItem[]) => {
+    bFiles.forEach(bf => (bf.progress = 0))
+    return Promise.all(bFiles.map(fileUpload))
+  }, [fileUpload])
+
+  const uploadMultipleFiles = useCallback(async (files: FileItem[]) => {
+    const batchCountLimit = fileUploadConfig.batch_count_limit
+    const length = files.length
+    let start = 0
+    let end = 0
+
+    while (start < length) {
+      if (start + batchCountLimit > length)
+        end = length
+      else
+        end = start + batchCountLimit
+      const bFiles = files.slice(start, end)
+      await uploadBatchFiles(bFiles)
+      start = end
+    }
+  }, [fileUploadConfig, uploadBatchFiles])
+
+  const initialUpload = useCallback((files: File[]) => {
+    if (!files.length)
+      return false
+
+    if (files.length + fileList.length > FILES_NUMBER_LIMIT && !IS_CE_EDITION) {
+      notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.filesNumber', { filesNumber: FILES_NUMBER_LIMIT }) })
+      return false
+    }
+
+    const preparedFiles = files.map((file, index) => ({
+      fileID: `file${index}-${Date.now()}`,
+      file,
+      progress: -1,
+    }))
+    const newFiles = [...fileListRef.current, ...preparedFiles]
+    prepareFileList(newFiles)
+    fileListRef.current = newFiles
+    uploadMultipleFiles(preparedFiles)
+  }, [prepareFileList, uploadMultipleFiles, notify, t, fileList])
+
+  const handleDragEnter = (e: DragEvent) => {
+    e.preventDefault()
+    e.stopPropagation()
+    e.target !== dragRef.current && setDragging(true)
+  }
+  const handleDragOver = (e: DragEvent) => {
+    e.preventDefault()
+    e.stopPropagation()
+  }
+  const handleDragLeave = (e: DragEvent) => {
+    e.preventDefault()
+    e.stopPropagation()
+    e.target === dragRef.current && setDragging(false)
+  }
+
+  const handleDrop = useCallback((e: DragEvent) => {
+    e.preventDefault()
+    e.stopPropagation()
+    setDragging(false)
+    if (!e.dataTransfer)
+      return
+
+    const files = [...e.dataTransfer.files] as File[]
+    const validFiles = files.filter(isValid)
+    initialUpload(validFiles)
+  }, [initialUpload, isValid])
+
+  const selectHandle = () => {
+    if (fileUploader.current)
+      fileUploader.current.click()
+  }
+
+  const removeFile = (fileID: string) => {
+    if (fileUploader.current)
+      fileUploader.current.value = ''
+
+    fileListRef.current = fileListRef.current.filter(item => item.fileID !== fileID)
+    onFileListUpdate?.([...fileListRef.current])
+  }
+  const fileChangeHandle = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
+    const files = [...(e.target.files ?? [])] as File[]
+    initialUpload(files.filter(isValid))
+  }, [isValid, initialUpload])
+
+  useEffect(() => {
+    dropRef.current?.addEventListener('dragenter', handleDragEnter)
+    dropRef.current?.addEventListener('dragover', handleDragOver)
+    dropRef.current?.addEventListener('dragleave', handleDragLeave)
+    dropRef.current?.addEventListener('drop', handleDrop)
+    return () => {
+      dropRef.current?.removeEventListener('dragenter', handleDragEnter)
+      dropRef.current?.removeEventListener('dragover', handleDragOver)
+      dropRef.current?.removeEventListener('dragleave', handleDragLeave)
+      dropRef.current?.removeEventListener('drop', handleDrop)
+    }
+  }, [handleDrop])
+
+  return (
+    <div className={s.fileUploader}>
+      {!hideUpload && (
+        <input
+          ref={fileUploader}
+          id="fileUploader"
+          style={{ display: 'none' }}
+          type="file"
+          multiple={!notSupportBatchUpload}
+          accept={ACCEPTS.join(',')}
+          onChange={fileChangeHandle}
+        />
+      )}
+
+      <div className={cn(s.title, titleClassName)}>{t('datasetCreation.stepOne.uploader.title')}</div>
+      {!hideUpload && (
+
+        <div ref={dropRef} className={cn(s.uploader, dragging && s.dragging)}>
+          <div className='flex justify-center items-center min-h-6 mb-2'>
+            <span className={s.uploadIcon} />
+            <span>
+              {t('datasetCreation.stepOne.uploader.button')}
+              <label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
+            </span>
+          </div>
+          <div className={s.tip}>{t('datasetCreation.stepOne.uploader.tip', {
+            size: fileUploadConfig.file_size_limit,
+            supportTypes: supportTypesShowNames,
+          })}</div>
+          {dragging && <div ref={dragRef} className={s.draggingCover} />}
+        </div>
+      )}
+      <div className={s.fileList}>
+        {fileList.map((fileItem, index) => (
+          <div
+            key={`${fileItem.fileID}-${index}`}
+            onClick={() => fileItem.file?.id && onPreview(fileItem.file)}
+            className={cn(
+              s.file,
+              fileItem.progress < 100 && s.uploading,
+            )}
+          >
+            {fileItem.progress < 100 && (
+              <div className={s.progressbar} style={{ width: `${fileItem.progress}%` }} />
+            )}
+            <div className={s.fileInfo}>
+              <div className={cn(s.fileIcon, s[getFileType(fileItem.file)])} />
+              <div className={s.filename}>{fileItem.file.name}</div>
+              <div className={s.size}>{getFileSize(fileItem.file.size)}</div>
+            </div>
+            <div className={s.actionWrapper}>
+              {(fileItem.progress < 100 && fileItem.progress >= 0) && (
+                <div className={s.percent}>{`${fileItem.progress}%`}</div>
+              )}
+              {fileItem.progress === 100 && (
+                <div className={s.remove} onClick={(e) => {
+                  e.stopPropagation()
+                  removeFile(fileItem.fileID)
+                }} />
+              )}
+            </div>
+          </div>
+        ))}
+      </div>
+    </div>
+  )
+}
+
+export default FileUploader

+ 0 - 0
app/components/database/create/index.module.css


+ 175 - 0
app/components/database/create/index.tsx

@@ -0,0 +1,175 @@
+'use client'
+import React, { useCallback, useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import AppUnavailable from '../../base/app-unavailable'
+import { ModelTypeEnum } from '../../header/account-setting/model-provider-page/declarations'
+import StepsNavBar from './steps-nav-bar'
+import StepOne from './step-one'
+import StepTwo from './step-two'
+import StepThree from './step-three'
+import { DataSourceType } from '@/models/datasets'
+import type { CrawlOptions, CrawlResultItem, DataSet, FileItem, createDocumentResponse } from '@/models/datasets'
+import { fetchDataSource } from '@/service/common'
+import { fetchDatasetDetail } from '@/service/datasets'
+import { DataSourceProvider, type NotionPage } from '@/models/common'
+import { useModalContext } from '@/context/modal-context'
+import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
+
+type DatasetUpdateFormProps = {
+  datasetId?: string
+}
+
+const DEFAULT_CRAWL_OPTIONS: CrawlOptions = {
+  crawl_sub_pages: true,
+  only_main_content: true,
+  includes: '',
+  excludes: '',
+  limit: 10,
+  max_depth: '',
+  use_sitemap: true,
+}
+
+const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
+  const { t } = useTranslation()
+  const { setShowAccountSettingModal } = useModalContext()
+  const [hasConnection, setHasConnection] = useState(true)
+  const [dataSourceType, setDataSourceType] = useState<DataSourceType>(DataSourceType.FILE)
+  const [step, setStep] = useState(1)
+  const [indexingTypeCache, setIndexTypeCache] = useState('')
+  const [fileList, setFiles] = useState<FileItem[]>([])
+  const [result, setResult] = useState<createDocumentResponse | undefined>()
+  const [hasError, setHasError] = useState(false)
+  const { data: embeddingsDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
+
+  const [notionPages, setNotionPages] = useState<NotionPage[]>([])
+  const updateNotionPages = (value: NotionPage[]) => {
+    setNotionPages(value)
+  }
+
+  const [websitePages, setWebsitePages] = useState<CrawlResultItem[]>([])
+  const [crawlOptions, setCrawlOptions] = useState<CrawlOptions>(DEFAULT_CRAWL_OPTIONS)
+
+  const updateFileList = (preparedFiles: FileItem[]) => {
+    setFiles(preparedFiles)
+  }
+  const [websiteCrawlProvider, setWebsiteCrawlProvider] = useState<DataSourceProvider>(DataSourceProvider.fireCrawl)
+  const [websiteCrawlJobId, setWebsiteCrawlJobId] = useState('')
+
+  const updateFile = (fileItem: FileItem, progress: number, list: FileItem[]) => {
+    const targetIndex = list.findIndex(file => file.fileID === fileItem.fileID)
+    list[targetIndex] = {
+      ...list[targetIndex],
+      progress,
+    }
+    setFiles([...list])
+    // use follow code would cause dirty list update problem
+    // const newList = list.map((file) => {
+    //   if (file.fileID === fileItem.fileID) {
+    //     return {
+    //       ...fileItem,
+    //       progress,
+    //     }
+    //   }
+    //   return file
+    // })
+    // setFiles(newList)
+  }
+  const updateIndexingTypeCache = (type: string) => {
+    setIndexTypeCache(type)
+  }
+  const updateResultCache = (res?: createDocumentResponse) => {
+    setResult(res)
+  }
+
+  const nextStep = useCallback(() => {
+    setStep(step + 1)
+  }, [step, setStep])
+
+  const changeStep = useCallback((delta: number) => {
+    setStep(step + delta)
+  }, [step, setStep])
+
+  const checkNotionConnection = async () => {
+    const { data } = await fetchDataSource({ url: '/data-source/integrates' })
+    const hasConnection = data.filter(item => item.provider === 'notion') || []
+    setHasConnection(hasConnection.length > 0)
+  }
+
+  useEffect(() => {
+    checkNotionConnection()
+  }, [])
+
+  const [detail, setDetail] = useState<DataSet | null>(null)
+  useEffect(() => {
+    (async () => {
+      if (datasetId) {
+        try {
+          const detail = await fetchDatasetDetail(datasetId)
+          setDetail(detail)
+        }
+        catch (e) {
+          setHasError(true)
+        }
+      }
+    })()
+  }, [datasetId])
+
+  if (hasError)
+    return <AppUnavailable code={500} unknownReason={t('datasetCreation.error.unavailable') as string} />
+
+  return (
+    <div className='flex' style={{ height: 'calc(100vh - 56px)' }}>
+      <div className="flex flex-col w-11 sm:w-56 overflow-y-auto bg-white border-r border-gray-200 shrink-0">
+        <StepsNavBar step={step} datasetId={datasetId} />
+      </div>
+      <div className="grow bg-white">
+        <div className={step === 1 ? 'block h-full' : 'hidden'}>
+          <StepOne
+            hasConnection={hasConnection}
+            onSetting={() => setShowAccountSettingModal({ payload: 'data-source' })}
+            datasetId={datasetId}
+            dataSourceType={dataSourceType}
+            dataSourceTypeDisable={!!detail?.data_source_type}
+            changeType={setDataSourceType}
+            files={fileList}
+            updateFile={updateFile}
+            updateFileList={updateFileList}
+            notionPages={notionPages}
+            updateNotionPages={updateNotionPages}
+            onStepChange={nextStep}
+            websitePages={websitePages}
+            updateWebsitePages={setWebsitePages}
+            onWebsiteCrawlProviderChange={setWebsiteCrawlProvider}
+            onWebsiteCrawlJobIdChange={setWebsiteCrawlJobId}
+            crawlOptions={crawlOptions}
+            onCrawlOptionsChange={setCrawlOptions}
+          />
+        </div>
+        {(step === 2 && (!datasetId || (datasetId && !!detail))) && <StepTwo
+          isAPIKeySet={!!embeddingsDefaultModel}
+          onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
+          indexingType={detail?.indexing_technique}
+          datasetId={datasetId}
+          dataSourceType={dataSourceType}
+          files={fileList.map(file => file.file)}
+          notionPages={notionPages}
+          websitePages={websitePages}
+          websiteCrawlProvider={websiteCrawlProvider}
+          websiteCrawlJobId={websiteCrawlJobId}
+          onStepChange={changeStep}
+          updateIndexingTypeCache={updateIndexingTypeCache}
+          updateResultCache={updateResultCache}
+          crawlOptions={crawlOptions}
+        />}
+        {step === 3 && <StepThree
+          datasetId={datasetId}
+          datasetName={detail?.name}
+          indexingType={detail?.indexing_technique || indexingTypeCache}
+          creationCache={result}
+        />}
+      </div>
+    </div>
+  )
+}
+
+export default DatasetUpdateForm

+ 54 - 0
app/components/database/create/notion-page-preview/index.module.css

@@ -0,0 +1,54 @@
+.filePreview {
+    @apply flex flex-col border-l border-gray-200 shrink-0;
+    width: 528px;
+    background-color: #fcfcfd;
+  }
+  
+  .previewHeader {
+    @apply border-b border-gray-200 shrink-0;
+    margin: 42px 32px 0;
+    padding-bottom: 16px;
+  }
+  
+  .previewHeader .title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    color: #101828;
+    font-weight: 600;
+    font-size: 18px;
+    line-height: 28px;
+  }
+  
+  .previewHeader .fileName {
+    display: flex;
+    align-items: center;
+    font-weight: 400;
+    font-size: 12px;
+    line-height: 18px;
+    color: #1D2939;
+  }
+  
+  .previewHeader .filetype {
+    color: #667085;
+  }
+  
+  .previewContent {
+    @apply overflow-y-auto grow;
+    padding: 20px 32px;
+    font-weight: 400;
+    font-size: 16px;
+    line-height: 24px;
+    color: #344054;
+  }
+  
+  .previewContent .loading {
+    width: 100%;
+    height: 180px;
+    background: #f9fafb center no-repeat url(../assets/Loading.svg);
+    background-size: contain;
+  }
+  .fileContent {
+    white-space: pre-line;
+  }
+  

+ 74 - 0
app/components/database/create/notion-page-preview/index.tsx

@@ -0,0 +1,74 @@
+'use client'
+import React, { useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { XMarkIcon } from '@heroicons/react/20/solid'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import type { NotionPage } from '@/models/common'
+import NotionIcon from '@/app/components/base/notion-icon'
+import { fetchNotionPagePreview } from '@/service/datasets'
+
+type IProps = {
+  currentPage?: NotionPage
+  hidePreview: () => void
+}
+
+const NotionPagePreview = ({
+  currentPage,
+  hidePreview,
+}: IProps) => {
+  const { t } = useTranslation()
+  const [previewContent, setPreviewContent] = useState('')
+  const [loading, setLoading] = useState(true)
+
+  const getPreviewContent = async () => {
+    if (!currentPage)
+      return
+    try {
+      const res = await fetchNotionPagePreview({
+        workspaceID: currentPage.workspace_id,
+        pageID: currentPage.page_id,
+        pageType: currentPage.type,
+      })
+      setPreviewContent(res.content)
+      setLoading(false)
+    }
+    catch { }
+  }
+
+  useEffect(() => {
+    if (currentPage) {
+      setLoading(true)
+      getPreviewContent()
+    }
+  }, [currentPage])
+
+  return (
+    <div className={cn(s.filePreview)}>
+      <div className={cn(s.previewHeader)}>
+        <div className={cn(s.title)}>
+          <span>{t('datasetCreation.stepOne.pagePreview')}</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={cn(s.fileName)}>
+          <NotionIcon
+            className='shrink-0 mr-1'
+            type='page'
+            src={currentPage?.page_icon}
+          />
+          {currentPage?.page_name}
+        </div>
+      </div>
+      <div className={cn(s.previewContent)}>
+        {loading && <div className={cn(s.loading)} />}
+        {!loading && (
+          <div className={cn(s.fileContent)}>{previewContent}</div>
+        )}
+      </div>
+    </div>
+  )
+}
+
+export default NotionPagePreview

+ 158 - 0
app/components/database/create/step-one/index.module.css

@@ -0,0 +1,158 @@
+.stepHeader {
+  position: sticky;
+  top: 0;
+  left: 0;
+  padding: 42px 64px 12px;
+  font-weight: 600;
+  font-size: 18px;
+  line-height: 28px;
+  color: #101828;
+}
+
+.form {
+  position: relative;
+  padding: 12px 64px;
+  background-color: #fff;
+}
+
+.dataSourceItem {
+  @apply box-border relative shrink-0 flex items-center mr-3 p-3 h-14 bg-white rounded-xl cursor-pointer;
+  border: 0.5px solid #EAECF0;
+  box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
+  font-weight: 500;
+  font-size: 14px;
+  line-height: 20px;
+  color: #101828;
+}
+.dataSourceItem:hover {
+  background-color: #f5f8ff;
+  border: 0.5px solid #B2CCFF;
+  box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
+}
+.dataSourceItem.active {
+  background-color: #f5f8ff;
+  border: 1.5px solid #528BFF;
+  box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
+}
+.dataSourceItem.disabled {
+  background-color: #f9fafb;
+  border: 0.5px solid #EAECF0;
+  box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
+  cursor: default;
+}
+.dataSourceItem.disabled:hover {
+  background-color: #f9fafb;
+  border: 0.5px solid #EAECF0;
+  box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
+}
+.comingTag {
+  @apply flex justify-center items-center bg-white;
+  position: absolute;
+  right: 8px;
+  top: -10px;
+  padding: 1px 6px;
+  height: 20px;
+  border: 1px solid #E0EAFF;
+  border-radius: 6px;
+  font-weight: 500;
+  font-size: 12px;
+  line-height: 18px;
+  color: #444CE7;
+}
+.datasetIcon {
+  @apply flex mr-2 w-8 h-8 rounded-lg bg-center bg-no-repeat;
+  background-color: #F5FAFF;
+  background-image: url(../assets/file.svg);
+  background-size: 16px;
+  border: 0.5px solid #D1E9FF;
+}
+.dataSourceItem:active .datasetIcon,
+.dataSourceItem:hover .datasetIcon {
+  background-color: #F5F8FF;
+  border: 0.5px solid #E0EAFF;
+}
+.datasetIcon.notion {
+  background-image: url(../assets/notion.svg);
+  background-size: 20px;
+}
+.datasetIcon.web {
+  background-image: url(../assets/web.svg);
+}
+
+.submitButton {
+  width: 120px;
+}
+
+.dividerLine {
+  margin: 32px 0;
+  max-width: 640px;
+  height: 1px;
+  background-color: #eaecf0;
+}
+
+.OtherCreationOption {
+  @apply flex items-center cursor-pointer;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 18px;
+  color: #155EEF;
+}
+.OtherCreationOption::before {
+  content: '';
+  display: block;
+  margin-right: 4px;
+  width: 16px;
+  height: 16px;
+  background: center no-repeat url(../assets/folder-plus.svg);
+  background-size: contain;
+}
+
+.notionConnectionTip {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  padding: 24px;
+  max-width: 640px;
+  background: #F9FAFB;
+  border-radius: 16px;
+}
+
+.notionIcon {
+  display: flex;
+  padding: 12px;
+  width: 48px;
+  height: 48px;
+  background: #fff center no-repeat url(../assets/notion.svg);
+  background-size: 24px;
+  border: 0.5px solid #EAECF5;
+  box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
+  border-radius: 12px;
+}
+
+.notionConnectionTip .title {
+  position: relative;
+  margin: 24px 0 4px;
+  font-style: normal;
+  font-weight: 600;
+  font-size: 16px;
+  line-height: 24px;
+  color: #374151;
+}
+.notionConnectionTip .title::after {
+  content: '';
+  position: absolute;
+  top: -6px;
+  right: -12px;
+  width: 16px;
+  height: 16px;
+  background: center no-repeat url(../assets/Icon-3-dots.svg);
+  background-size: contain;
+}
+.notionConnectionTip .tip {
+  margin-bottom: 20px;
+  font-style: normal;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  color: #6B7280;
+}

+ 264 - 0
app/components/database/create/step-one/index.tsx

@@ -0,0 +1,264 @@
+'use client'
+import React, { useMemo, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import FilePreview from '../file-preview'
+import FileUploader from '../file-uploader'
+import NotionPagePreview from '../notion-page-preview'
+import EmptyDatasetCreationModal from '../empty-dataset-creation-modal'
+import Website from '../website'
+import WebsitePreview from '../website/preview'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import type { CrawlOptions, CrawlResultItem, FileItem } from '@/models/datasets'
+import type { DataSourceProvider, NotionPage } from '@/models/common'
+import { DataSourceType } from '@/models/datasets'
+import Button from '@/app/components/base/button'
+import { NotionPageSelector } from '@/app/components/base/notion-page-selector'
+import { useDatasetDetailContext } from '@/context/dataset-detail'
+import { useProviderContext } from '@/context/provider-context'
+import VectorSpaceFull from '@/app/components/billing/vector-space-full'
+
+type IStepOneProps = {
+  datasetId?: string
+  dataSourceType?: DataSourceType
+  dataSourceTypeDisable: Boolean
+  hasConnection: boolean
+  onSetting: () => void
+  files: FileItem[]
+  updateFileList: (files: FileItem[]) => void
+  updateFile: (fileItem: FileItem, progress: number, list: FileItem[]) => void
+  notionPages?: NotionPage[]
+  updateNotionPages: (value: NotionPage[]) => void
+  onStepChange: () => void
+  changeType: (type: DataSourceType) => void
+  websitePages?: CrawlResultItem[]
+  updateWebsitePages: (value: CrawlResultItem[]) => void
+  onWebsiteCrawlProviderChange: (provider: DataSourceProvider) => void
+  onWebsiteCrawlJobIdChange: (jobId: string) => void
+  crawlOptions: CrawlOptions
+  onCrawlOptionsChange: (payload: CrawlOptions) => void
+}
+
+type NotionConnectorProps = {
+  onSetting: () => void
+}
+export const NotionConnector = ({ onSetting }: NotionConnectorProps) => {
+  const { t } = useTranslation()
+
+  return (
+    <div className={s.notionConnectionTip}>
+      <span className={s.notionIcon} />
+      <div className={s.title}>{t('datasetCreation.stepOne.notionSyncTitle')}</div>
+      <div className={s.tip}>{t('datasetCreation.stepOne.notionSyncTip')}</div>
+      <Button className='h-8' variant='primary' onClick={onSetting}>{t('datasetCreation.stepOne.connect')}</Button>
+    </div>
+  )
+}
+
+const StepOne = ({
+  datasetId,
+  dataSourceType: inCreatePageDataSourceType,
+  dataSourceTypeDisable,
+  changeType,
+  hasConnection,
+  onSetting,
+  onStepChange,
+  files,
+  updateFileList,
+  updateFile,
+  notionPages = [],
+  updateNotionPages,
+  websitePages = [],
+  updateWebsitePages,
+  onWebsiteCrawlProviderChange,
+  onWebsiteCrawlJobIdChange,
+  crawlOptions,
+  onCrawlOptionsChange,
+}: IStepOneProps) => {
+  const { dataset } = useDatasetDetailContext()
+  const [showModal, setShowModal] = useState(false)
+  const [currentFile, setCurrentFile] = useState<File | undefined>()
+  const [currentNotionPage, setCurrentNotionPage] = useState<NotionPage | undefined>()
+  const [currentWebsite, setCurrentWebsite] = useState<CrawlResultItem | undefined>()
+  const { t } = useTranslation()
+
+  const modalShowHandle = () => setShowModal(true)
+  const modalCloseHandle = () => setShowModal(false)
+
+  const updateCurrentFile = (file: File) => {
+    setCurrentFile(file)
+  }
+  const hideFilePreview = () => {
+    setCurrentFile(undefined)
+  }
+
+  const updateCurrentPage = (page: NotionPage) => {
+    setCurrentNotionPage(page)
+  }
+
+  const hideNotionPagePreview = () => {
+    setCurrentNotionPage(undefined)
+  }
+
+  const hideWebsitePreview = () => {
+    setCurrentWebsite(undefined)
+  }
+
+  const shouldShowDataSourceTypeList = !datasetId || (datasetId && !dataset?.data_source_type)
+  const isInCreatePage = shouldShowDataSourceTypeList
+  const dataSourceType = isInCreatePage ? inCreatePageDataSourceType : dataset?.data_source_type
+  const { plan, enableBilling } = useProviderContext()
+  const allFileLoaded = (files.length > 0 && files.every(file => file.file.id))
+  const hasNotin = notionPages.length > 0
+  const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace
+  const isShowVectorSpaceFull = (allFileLoaded || hasNotin) && isVectorSpaceFull && enableBilling
+  const notSupportBatchUpload = enableBilling && plan.type === 'sandbox'
+  const nextDisabled = useMemo(() => {
+    if (!files.length)
+      return true
+    if (files.some(file => !file.file.id))
+      return true
+    if (isShowVectorSpaceFull)
+      return true
+
+    return false
+  }, [files])
+  return (
+    <div className='flex w-full h-full'>
+      <div className='grow overflow-y-auto relative'>
+        {
+          shouldShowDataSourceTypeList && (
+            <div className={s.stepHeader}>{t('datasetCreation.steps.one')}</div>
+          )
+        }
+        <div className={s.form}>
+          {
+            shouldShowDataSourceTypeList && (
+              <div className='flex items-center mb-8 flex-wrap gap-y-4'>
+                <div
+                  className={cn(
+                    s.dataSourceItem,
+                    dataSourceType === DataSourceType.FILE && s.active,
+                    dataSourceTypeDisable && dataSourceType !== DataSourceType.FILE && s.disabled,
+                  )}
+                  onClick={() => {
+                    if (dataSourceTypeDisable)
+                      return
+                    changeType(DataSourceType.FILE)
+                    hideFilePreview()
+                    hideNotionPagePreview()
+                  }}
+                >
+                  <span className={cn(s.datasetIcon)} />
+                  {t('datasetCreation.stepOne.dataSourceType.file')}
+                </div>
+                <div
+                  className={cn(
+                    s.dataSourceItem,
+                    dataSourceType === DataSourceType.NOTION && s.active,
+                    dataSourceTypeDisable && dataSourceType !== DataSourceType.NOTION && s.disabled,
+                  )}
+                  onClick={() => {
+                    if (dataSourceTypeDisable)
+                      return
+                    changeType(DataSourceType.NOTION)
+                    hideFilePreview()
+                    hideNotionPagePreview()
+                  }}
+                >
+                  <span className={cn(s.datasetIcon, s.notion)} />
+                  {t('datasetCreation.stepOne.dataSourceType.notion')}
+                </div>
+                <div
+                  className={cn(
+                    s.dataSourceItem,
+                    dataSourceType === DataSourceType.WEB && s.active,
+                    dataSourceTypeDisable && dataSourceType !== DataSourceType.WEB && s.disabled,
+                  )}
+                  onClick={() => changeType(DataSourceType.WEB)}
+                >
+                  <span className={cn(s.datasetIcon, s.web)} />
+                  {t('datasetCreation.stepOne.dataSourceType.web')}
+                </div>
+              </div>
+            )
+          }
+          {dataSourceType === DataSourceType.FILE && (
+            <>
+              <FileUploader
+                fileList={files}
+                titleClassName={!shouldShowDataSourceTypeList ? 'mt-[30px] !mb-[44px] !text-lg !font-semibold !text-gray-900' : undefined}
+                prepareFileList={updateFileList}
+                onFileListUpdate={updateFileList}
+                onFileUpdate={updateFile}
+                onPreview={updateCurrentFile}
+                notSupportBatchUpload={notSupportBatchUpload}
+              />
+              {isShowVectorSpaceFull && (
+                <div className='max-w-[640px] mb-4'>
+                  <VectorSpaceFull />
+                </div>
+              )}
+              <Button disabled={nextDisabled} className={s.submitButton} variant='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
+            </>
+          )}
+          {dataSourceType === DataSourceType.NOTION && (
+            <>
+              {!hasConnection && <NotionConnector onSetting={onSetting} />}
+              {hasConnection && (
+                <>
+                  <div className='mb-8 w-[640px]'>
+                    <NotionPageSelector
+                      value={notionPages.map(page => page.page_id)}
+                      onSelect={updateNotionPages}
+                      onPreview={updateCurrentPage}
+                    />
+                  </div>
+                  {isShowVectorSpaceFull && (
+                    <div className='max-w-[640px] mb-4'>
+                      <VectorSpaceFull />
+                    </div>
+                  )}
+                  <Button disabled={isShowVectorSpaceFull || !notionPages.length} className={s.submitButton} variant='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
+                </>
+              )}
+            </>
+          )}
+          {dataSourceType === DataSourceType.WEB && (
+            <>
+              <div className={cn('mb-8 w-[640px]', !shouldShowDataSourceTypeList && 'mt-12')}>
+                <Website
+                  onPreview={setCurrentWebsite}
+                  checkedCrawlResult={websitePages}
+                  onCheckedCrawlResultChange={updateWebsitePages}
+                  onCrawlProviderChange={onWebsiteCrawlProviderChange}
+                  onJobIdChange={onWebsiteCrawlJobIdChange}
+                  crawlOptions={crawlOptions}
+                  onCrawlOptionsChange={onCrawlOptionsChange}
+                />
+              </div>
+              {isShowVectorSpaceFull && (
+                <div className='max-w-[640px] mb-4'>
+                  <VectorSpaceFull />
+                </div>
+              )}
+              <Button disabled={isShowVectorSpaceFull || !websitePages.length} className={s.submitButton} variant='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
+            </>
+          )}
+          {!datasetId && (
+            <>
+              <div className={s.dividerLine} />
+              <div onClick={modalShowHandle} className={s.OtherCreationOption}>{t('datasetCreation.stepOne.emptyDatasetCreation')}</div>
+            </>
+          )}
+        </div>
+        <EmptyDatasetCreationModal show={showModal} onHide={modalCloseHandle} />
+      </div>
+      {currentFile && <FilePreview file={currentFile} hidePreview={hideFilePreview} />}
+      {currentNotionPage && <NotionPagePreview currentPage={currentNotionPage} hidePreview={hideNotionPagePreview} />}
+      {currentWebsite && <WebsitePreview payload={currentWebsite} hidePreview={hideWebsitePreview} />}
+    </div>
+  )
+}
+
+export default StepOne

+ 75 - 0
app/components/database/create/step-three/index.module.css

@@ -0,0 +1,75 @@
+.creationInfo {
+  padding-top: 42px;
+}
+.creationInfo .title {
+  @apply mb-2;
+  font-weight: 500;
+  font-size: 20px;
+  line-height: 30px;
+  color: #101828;
+}
+.creationInfo .content {
+  margin-bottom: 44px;
+  font-weight: 400;
+  font-size: 14px;
+  line-height: 20px;
+  color: #667085;
+}
+.creationInfo .label {
+  @apply mb-2;
+  font-weight: 500;
+  font-size: 14px;
+  line-height: 20px;
+  color: #101828;
+}
+.datasetName {
+  padding: 8px 12px;
+  background: #F9FAFB;
+  border-radius: 8px;
+  font-weight: 400;
+  font-size: 14px;
+  line-height: 20px;
+  color: #101828;
+  word-break: break-all;
+}
+
+.dividerLine {
+  margin: 24px 0;
+  height: 1px;
+  background-color: #eaecf0;
+}
+
+.sideTip {
+  @apply flex flex-col items-center shrink-0 ;
+  padding-top: 108px;
+  width: 524px;
+  border-left: 0.5px solid #F2F4F7;
+}
+.tipCard {
+  @apply flex flex-col items-start p-6;
+  width: 320px;
+  background-color: #F9FAFB;
+  box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
+  border-radius: 12px;
+}
+.tipCard .icon {
+  width: 32px;
+  height: 32px;
+  border: 1px solid #EAECF0;
+  border-radius: 6px;
+  background: center no-repeat url(../assets/book-open-01.svg);
+  background-size: 16px;
+}
+.tipCard .title {
+  margin: 12px 0;
+  font-weight: 500;
+  font-size: 16px;
+  line-height: 24px;
+  color: #344054;
+}
+.tipCard .content {
+  font-weight: 400;
+  font-size: 14px;
+  line-height: 20px;
+  color: #344054;
+}

+ 64 - 0
app/components/database/create/step-three/index.tsx

@@ -0,0 +1,64 @@
+'use client'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import EmbeddingProcess from '../embedding-process'
+
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
+import type { FullDocumentDetail, createDocumentResponse } from '@/models/datasets'
+
+type StepThreeProps = {
+  datasetId?: string
+  datasetName?: string
+  indexingType?: string
+  creationCache?: createDocumentResponse
+}
+
+const StepThree = ({ datasetId, datasetName, indexingType, creationCache }: StepThreeProps) => {
+  const { t } = useTranslation()
+
+  const media = useBreakpoints()
+  const isMobile = media === MediaType.mobile
+
+  return (
+    <div className='flex w-full h-full'>
+      <div className={'h-full w-full overflow-y-scroll px-6 sm:px-16'}>
+        <div className='max-w-[636px]'>
+          {!datasetId && (
+            <>
+              <div className={s.creationInfo}>
+                <div className={s.title}>{t('datasetCreation.stepThree.creationTitle')}</div>
+                <div className={s.content}>{t('datasetCreation.stepThree.creationContent')}</div>
+                <div className={s.label}>{t('datasetCreation.stepThree.label')}</div>
+                <div className={s.datasetName}>{datasetName || creationCache?.dataset?.name}</div>
+              </div>
+              <div className={s.dividerLine} />
+            </>
+          )}
+          {datasetId && (
+            <div className={s.creationInfo}>
+              <div className={s.title}>{t('datasetCreation.stepThree.additionTitle')}</div>
+              <div className={s.content}>{`${t('datasetCreation.stepThree.additionP1')} ${datasetName || creationCache?.dataset?.name} ${t('datasetCreation.stepThree.additionP2')}`}</div>
+            </div>
+          )}
+          <EmbeddingProcess
+            datasetId={datasetId || creationCache?.dataset?.id || ''}
+            batchId={creationCache?.batch || ''}
+            documents={creationCache?.documents as FullDocumentDetail[]}
+            indexingType={indexingType || creationCache?.dataset?.indexing_technique}
+          />
+        </div>
+      </div>
+      {!isMobile && <div className={cn(s.sideTip)}>
+        <div className={s.tipCard}>
+          <span className={s.icon} />
+          <div className={s.title}>{t('datasetCreation.stepThree.sideTipTitle')}</div>
+          <div className={s.content}>{t('datasetCreation.stepThree.sideTipContent')}</div>
+        </div>
+      </div>}
+    </div>
+  )
+}
+
+export default StepThree

+ 18 - 0
app/components/database/create/step-two/escape.ts

@@ -0,0 +1,18 @@
+function escape(input: string): string {
+  if (!input || typeof input !== 'string')
+    return ''
+
+  const res = input
+    // .replaceAll('\\', '\\\\') // This would add too many backslashes
+    .replaceAll('\0', '\\0')
+    .replaceAll('\b', '\\b')
+    .replaceAll('\f', '\\f')
+    .replaceAll('\n', '\\n')
+    .replaceAll('\r', '\\r')
+    .replaceAll('\t', '\\t')
+    .replaceAll('\v', '\\v')
+    .replaceAll('\'', '\\\'')
+  return res
+}
+
+export default escape

+ 435 - 0
app/components/database/create/step-two/index.module.css

@@ -0,0 +1,435 @@
+.pageHeader {
+  @apply px-16 flex justify-between items-center;
+  position: sticky;
+  top: 0;
+  left: 0;
+  padding-top: 42px;
+  padding-bottom: 12px;
+  background-color: #fff;
+  font-weight: 600;
+  font-size: 18px;
+  line-height: 28px;
+  color: #101828;
+  z-index: 10;
+}
+
+.form {
+  @apply px-16 pb-8;
+}
+
+.form .label {
+  @apply pt-6 pb-2 flex items-center;
+  font-weight: 500;
+  font-size: 16px;
+  line-height: 24px;
+  color: #344054;
+}
+
+.segmentationItem {
+  min-height: 68px;
+}
+
+.indexItem {
+  min-height: 126px;
+}
+
+.indexItem .disableMask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(255, 255, 255, 0.5);
+  border-radius: 12px;
+  z-index: 2;
+}
+
+.indexItem .warningTip {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  padding: 8px 20px 8px 40px;
+  background: #FFFAEB;
+  border-top: 0.5px solid #FEF0C7;
+  border-radius: 12px;
+  font-size: 12px;
+  line-height: 18px;
+  color: #344054;
+  z-index: 3;
+}
+
+.indexItem .warningTip::before {
+  content: '';
+  position: absolute;
+  top: 11px;
+  left: 20px;
+  width: 12px;
+  height: 12px;
+  background: center no-repeat url(../assets/alert-triangle.svg);
+  background-size: 12px;
+}
+
+.indexItem .warningTip .click {
+  color: #155EEF;
+  cursor: pointer;
+}
+
+.indexItem.disabled:hover {
+  background-color: #fcfcfd;
+  border-color: #f2f4f7;
+  box-shadow: none;
+  cursor: default;
+}
+
+.indexItem.disabled:hover .radio {
+  @apply w-4 h-4 border-[2px] border-gray-200 rounded-full;
+}
+
+.radioItem {
+  @apply relative mb-2 rounded-xl border border-gray-100 cursor-pointer;
+  background-color: #fcfcfd;
+}
+
+.radioItem.segmentationItem.custom {
+  height: auto;
+}
+
+.radioItem.segmentationItem.custom .typeHeader {
+  /* height: 65px; */
+}
+
+.radioItem.indexItem .typeHeader {
+  @apply py-4 pr-5;
+}
+
+.radioItem.indexItem.active .typeHeader {
+  padding: 15.5px 19.5px 15.5px 63.5px;
+}
+
+.radioItem.indexItem .radio {
+  top: 16px;
+  right: 20px;
+}
+
+.radioItem.indexItem.active .radio {
+  top: 16px;
+  right: 19.5px;
+}
+
+.radioItem.indexItem .typeHeader .title {
+  @apply pb-1;
+}
+
+.radioItem .typeIcon {
+  position: absolute;
+  top: 18px;
+  left: 20px;
+  width: 32px;
+  height: 32px;
+  background: #EEF4FF center no-repeat;
+  border-radius: 8px;
+}
+
+.typeIcon.auto {
+  background-color: #F5F3FF;
+  background-image: url(../assets/zap-fast.svg);
+}
+
+.typeIcon.customize {
+  background-image: url(../assets/sliders-02.svg);
+}
+
+.typeIcon.qualified {
+  background-color: #FFF6ED;
+  background-image: url(../assets/star-07.svg);
+}
+
+.typeIcon.economical {
+  background-image: url(../assets/piggy-bank-01.svg);
+}
+
+.radioItem .radio {
+  @apply w-4 h-4 border-[2px] border-gray-200 rounded-full;
+  position: absolute;
+  top: 26px;
+  right: 20px;
+}
+
+.radioItem:hover {
+  background-color: #ffffff;
+  border-color: #B2CCFF;
+  box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
+}
+
+.radioItem:hover .radio {
+  border-color: #155eef;
+}
+
+.radioItem.active {
+  border-width: 1.5px;
+  border-color: #528BFF;
+  box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
+}
+
+.radioItem.active .radio {
+  top: 25.5px;
+  right: 19.5px;
+  border-width: 5px;
+  border-color: #155EEF;
+}
+
+.radioItem.active:hover {
+  border-width: 1.5px;
+  border-color: #528BFF;
+  box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06);
+}
+
+.radioItem.active .typeIcon {
+  top: 17.5px;
+  left: 19.5px;
+}
+
+.radioItem.active .typeHeader {
+  padding: 11.5px 63.5px;
+}
+
+.typeHeader {
+  @apply flex flex-col px-16 py-3 justify-center;
+}
+
+.typeHeader .title {
+  display: flex;
+  align-items: center;
+  padding-bottom: 2px;
+  font-weight: 500;
+  font-size: 16px;
+  line-height: 24px;
+  color: #101828;
+}
+
+.typeHeader .tip {
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 18px;
+  color: #667085;
+}
+
+.recommendTag {
+  display: inline-flex;
+  justify-content: center;
+  align-items: center;
+  padding: 0 6px;
+  margin-left: 4px;
+  border: 1px solid #E0EAFF;
+  border-radius: 6px;
+  font-weight: 500;
+  font-size: 12px;
+  line-height: 20px;
+  color: #444CE7;
+}
+
+.typeFormBody {
+  @apply px-16;
+  border-top: 1px solid #F2F4F7;
+}
+
+.formRow {
+  @apply flex justify-between mt-6;
+}
+
+.formRow .label {
+  @apply mb-2 p-0;
+  font-weight: 500;
+  font-size: 14px;
+  line-height: 20px;
+  color: #101828;
+}
+
+.ruleItem {
+  @apply flex items-center;
+}
+
+.formFooter {
+  padding: 16px 0 28px;
+}
+
+.formFooter .button {
+  font-size: 13px;
+  line-height: 18px;
+}
+
+.input {
+  @apply inline-flex h-9 w-full py-1 px-2 pr-14 rounded-lg text-xs leading-normal;
+  @apply bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-white placeholder:text-gray-400;
+}
+
+.source {
+  @apply flex justify-between items-center mt-8 px-6 py-4 rounded-xl bg-gray-50 border border-gray-100;
+}
+
+.source .divider {
+  @apply shrink-0 mx-4 w-px bg-gray-200;
+  height: 42px;
+}
+
+.fileIcon {
+  @apply inline-flex mr-1 w-6 h-6 bg-center bg-no-repeat;
+  background-image: url(../assets/pdf.svg);
+  background-size: 24px;
+}
+
+.fileIcon.pdf {
+  background-image: url(../assets/pdf.svg);
+}
+
+.fileIcon.csv {
+  background-image: url(../assets/csv.svg);
+}
+
+.fileIcon.doc {
+  background-image: url(../assets/doc.svg);
+}
+
+.fileIcon.docx {
+  background-image: url(../assets/docx.svg);
+}
+
+.fileIcon.xlsx,
+.fileIcon.xls {
+  background-image: url(../assets/xlsx.svg);
+}
+
+.fileIcon.html,
+.fileIcon.htm {
+  background-image: url(../assets/html.svg);
+}
+
+.fileIcon.md,
+.fileIcon.markdown {
+  background-image: url(../assets/md.svg);
+}
+
+.fileIcon.txt {
+  background-image: url(../assets/txt.svg);
+}
+
+.fileIcon.json {
+  background-image: url(../assets/json.svg);
+}
+
+.sourceContent {
+  width: 0;
+  flex: 1 1 auto;
+}
+
+.sourceCount {
+  @apply shrink-0 ml-1;
+  font-weight: 500;
+  font-size: 13px;
+  line-height: 18px;
+  color: #667085;
+}
+
+.segmentCount {
+  flex: 1 1 30%;
+  max-width: 120px;
+}
+
+.divider {
+  @apply mx-3 w-px h-4 bg-gray-200;
+}
+
+.calculating {
+  color: #98A2B3;
+  font-size: 12px;
+  line-height: 18px;
+}
+
+.sideTip {
+  @apply flex flex-col items-center shrink-0;
+  padding-top: 108px;
+  width: 524px;
+  border-left: 0.5px solid #F2F4F7;
+}
+
+.tipCard {
+  @apply flex flex-col items-start p-6;
+  width: 320px;
+  background-color: #F9FAFB;
+  box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
+  border-radius: 12px;
+}
+
+.tipCard .icon {
+  width: 32px;
+  height: 32px;
+  border: 1px solid #EAECF0;
+  border-radius: 6px;
+  background: center no-repeat url(../assets/book-open-01.svg);
+  background-size: 16px;
+}
+
+.tipCard .title {
+  margin: 12px 0;
+  font-weight: 500;
+  font-size: 16px;
+  line-height: 24px;
+  color: #344054;
+}
+
+.tipCard .content {
+  font-weight: 400;
+  font-size: 14px;
+  line-height: 20px;
+  color: #344054;
+}
+
+.previewWrap {
+  flex-shrink: 0;
+  width: 524px;
+}
+
+.previewWrap.isMobile {
+  max-width: 524px;
+}
+
+.previewHeader {
+  position: sticky;
+  top: 0;
+  left: 0;
+  padding-top: 42px;
+  background-color: #fff;
+  font-weight: 600;
+  font-size: 18px;
+  line-height: 28px;
+  color: #101828;
+  z-index: 10;
+}
+
+/* 
+ * `fixed` must under `previewHeader` because of style override would not work
+ */
+.fixed {
+  padding-top: 12px;
+  font-size: 12px;
+  line-height: 18px;
+  background: rgba(255, 255, 255, 0.9);
+  border-bottom: 0.5px solid #EAECF0;
+  backdrop-filter: blur(4px);
+  animation: fix 0.5s;
+}
+
+@keyframes fix {
+  from {
+    padding-top: 42px;
+    font-size: 18px;
+    line-height: 28px;
+  }
+
+  to {
+    padding-top: 12px;
+    font-size: 12px;
+    line-height: 18px;
+  }
+}

+ 1026 - 0
app/components/database/create/step-two/index.tsx

@@ -0,0 +1,1026 @@
+'use client'
+import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useContext } from 'use-context-selector'
+import { useBoolean } from 'ahooks'
+import { XMarkIcon } from '@heroicons/react/20/solid'
+import { RocketLaunchIcon } from '@heroicons/react/24/outline'
+import {
+  RiCloseLine,
+} from '@remixicon/react'
+import Link from 'next/link'
+import { groupBy } from 'lodash-es'
+import PreviewItem, { PreviewType } from './preview-item'
+import LanguageSelect from './language-select'
+import s from './index.module.css'
+import unescape from './unescape'
+import escape from './escape'
+import cn from '@/utils/classnames'
+import type { CrawlOptions, CrawlResultItem, CreateDocumentReq, CustomFile, FileIndexingEstimateResponse, FullDocumentDetail, IndexingEstimateParams, NotionInfo, PreProcessingRule, ProcessRule, Rules, createDocumentResponse } from '@/models/datasets'
+import {
+  createDocument,
+  createFirstDocument,
+  fetchFileIndexingEstimate as didFetchFileIndexingEstimate,
+  fetchDefaultProcessRule,
+} from '@/service/datasets'
+import Button from '@/app/components/base/button'
+import Input from '@/app/components/base/input'
+import Loading from '@/app/components/base/loading'
+import FloatRightContainer from '@/app/components/base/float-right-container'
+import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
+import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
+import { type RetrievalConfig } from '@/types/app'
+import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
+import Toast from '@/app/components/base/toast'
+import { formatNumber } from '@/utils/format'
+import type { NotionPage } from '@/models/common'
+import { DataSourceProvider } from '@/models/common'
+import { DataSourceType, DocForm } from '@/models/datasets'
+import NotionIcon from '@/app/components/base/notion-icon'
+import Switch from '@/app/components/base/switch'
+import { MessageChatSquare } from '@/app/components/base/icons/src/public/common'
+import { useDatasetDetailContext } from '@/context/dataset-detail'
+import I18n from '@/context/i18n'
+import { IS_CE_EDITION } from '@/config'
+import { RETRIEVE_METHOD } from '@/types/app'
+import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
+import Tooltip from '@/app/components/base/tooltip'
+import { useDefaultModel, useModelList, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
+import { LanguagesSupported } from '@/i18n/language'
+import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
+import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
+import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
+import { Globe01 } from '@/app/components/base/icons/src/vender/line/mapsAndTravel'
+
+type ValueOf<T> = T[keyof T]
+type StepTwoProps = {
+  isSetting?: boolean
+  documentDetail?: FullDocumentDetail
+  isAPIKeySet: boolean
+  onSetting: () => void
+  datasetId?: string
+  indexingType?: ValueOf<IndexingType>
+  dataSourceType: DataSourceType
+  files: CustomFile[]
+  notionPages?: NotionPage[]
+  websitePages?: CrawlResultItem[]
+  crawlOptions?: CrawlOptions
+  websiteCrawlProvider?: DataSourceProvider
+  websiteCrawlJobId?: string
+  onStepChange?: (delta: number) => void
+  updateIndexingTypeCache?: (type: string) => void
+  updateResultCache?: (res: createDocumentResponse) => void
+  onSave?: () => void
+  onCancel?: () => void
+}
+
+enum SegmentType {
+  AUTO = 'automatic',
+  CUSTOM = 'custom',
+}
+enum IndexingType {
+  QUALIFIED = 'high_quality',
+  ECONOMICAL = 'economy',
+}
+
+const DEFAULT_SEGMENT_IDENTIFIER = '\\n\\n'
+
+const StepTwo = ({
+  isSetting,
+  documentDetail,
+  isAPIKeySet,
+  onSetting,
+  datasetId,
+  indexingType,
+  dataSourceType: inCreatePageDataSourceType,
+  files,
+  notionPages = [],
+  websitePages = [],
+  crawlOptions,
+  websiteCrawlProvider = DataSourceProvider.fireCrawl,
+  websiteCrawlJobId = '',
+  onStepChange,
+  updateIndexingTypeCache,
+  updateResultCache,
+  onSave,
+  onCancel,
+}: StepTwoProps) => {
+  const { t } = useTranslation()
+  const { locale } = useContext(I18n)
+  const media = useBreakpoints()
+  const isMobile = media === MediaType.mobile
+
+  const { dataset: currentDataset, mutateDatasetRes } = useDatasetDetailContext()
+  const isInCreatePage = !datasetId || (datasetId && !currentDataset?.data_source_type)
+  const dataSourceType = isInCreatePage ? inCreatePageDataSourceType : currentDataset?.data_source_type
+  const scrollRef = useRef<HTMLDivElement>(null)
+  const [scrolled, setScrolled] = useState(false)
+  const previewScrollRef = useRef<HTMLDivElement>(null)
+  const [previewScrolled, setPreviewScrolled] = useState(false)
+  const [segmentationType, setSegmentationType] = useState<SegmentType>(SegmentType.AUTO)
+  const [segmentIdentifier, doSetSegmentIdentifier] = useState(DEFAULT_SEGMENT_IDENTIFIER)
+  const setSegmentIdentifier = useCallback((value: string) => {
+    doSetSegmentIdentifier(value ? escape(value) : DEFAULT_SEGMENT_IDENTIFIER)
+  }, [])
+  const [maxChunkLength, setMaxChunkLength] = useState(4000) // default chunk length
+  const [limitMaxChunkLength, setLimitMaxChunkLength] = useState(4000)
+  const [overlap, setOverlap] = useState(50)
+  const [rules, setRules] = useState<PreProcessingRule[]>([])
+  const [defaultConfig, setDefaultConfig] = useState<Rules>()
+  const hasSetIndexType = !!indexingType
+  const [indexType, setIndexType] = useState<ValueOf<IndexingType>>(
+    (indexingType
+      || isAPIKeySet)
+      ? IndexingType.QUALIFIED
+      : IndexingType.ECONOMICAL,
+  )
+  const [isLanguageSelectDisabled, setIsLanguageSelectDisabled] = useState(false)
+  const [docForm, setDocForm] = useState<DocForm | string>(
+    (datasetId && documentDetail) ? documentDetail.doc_form : DocForm.TEXT,
+  )
+  const [docLanguage, setDocLanguage] = useState<string>(
+    (datasetId && documentDetail) ? documentDetail.doc_language : (locale !== LanguagesSupported[1] ? 'English' : 'Chinese'),
+  )
+  const [QATipHide, setQATipHide] = useState(false)
+  const [previewSwitched, setPreviewSwitched] = useState(false)
+  const [showPreview, { setTrue: setShowPreview, setFalse: hidePreview }] = useBoolean()
+  const [customFileIndexingEstimate, setCustomFileIndexingEstimate] = useState<FileIndexingEstimateResponse | null>(null)
+  const [automaticFileIndexingEstimate, setAutomaticFileIndexingEstimate] = useState<FileIndexingEstimateResponse | null>(null)
+
+  const fileIndexingEstimate = (() => {
+    return segmentationType === SegmentType.AUTO ? automaticFileIndexingEstimate : customFileIndexingEstimate
+  })()
+  const [isCreating, setIsCreating] = useState(false)
+
+  const scrollHandle = (e: Event) => {
+    if ((e.target as HTMLDivElement).scrollTop > 0)
+      setScrolled(true)
+
+    else
+      setScrolled(false)
+  }
+
+  const previewScrollHandle = (e: Event) => {
+    if ((e.target as HTMLDivElement).scrollTop > 0)
+      setPreviewScrolled(true)
+
+    else
+      setPreviewScrolled(false)
+  }
+  const getFileName = (name: string) => {
+    const arr = name.split('.')
+    return arr.slice(0, -1).join('.')
+  }
+
+  const getRuleName = (key: string) => {
+    if (key === 'remove_extra_spaces')
+      return t('datasetCreation.stepTwo.removeExtraSpaces')
+
+    if (key === 'remove_urls_emails')
+      return t('datasetCreation.stepTwo.removeUrlEmails')
+
+    if (key === 'remove_stopwords')
+      return t('datasetCreation.stepTwo.removeStopwords')
+  }
+  const ruleChangeHandle = (id: string) => {
+    const newRules = rules.map((rule) => {
+      if (rule.id === id) {
+        return {
+          id: rule.id,
+          enabled: !rule.enabled,
+        }
+      }
+      return rule
+    })
+    setRules(newRules)
+  }
+  const resetRules = () => {
+    if (defaultConfig) {
+      setSegmentIdentifier(defaultConfig.segmentation.separator)
+      setMaxChunkLength(defaultConfig.segmentation.max_tokens)
+      setOverlap(defaultConfig.segmentation.chunk_overlap)
+      setRules(defaultConfig.pre_processing_rules)
+    }
+  }
+
+  const fetchFileIndexingEstimate = async (docForm = DocForm.TEXT, language?: string) => {
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
+    const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams(docForm, language)!)
+    if (segmentationType === SegmentType.CUSTOM)
+      setCustomFileIndexingEstimate(res)
+    else
+      setAutomaticFileIndexingEstimate(res)
+  }
+
+  const confirmChangeCustomConfig = () => {
+    if (segmentationType === SegmentType.CUSTOM && maxChunkLength > limitMaxChunkLength) {
+      Toast.notify({ type: 'error', message: t('datasetCreation.stepTwo.maxLengthCheck', { limit: limitMaxChunkLength }) })
+      return
+    }
+    setCustomFileIndexingEstimate(null)
+    setShowPreview()
+    fetchFileIndexingEstimate()
+    setPreviewSwitched(false)
+  }
+
+  const getIndexing_technique = () => indexingType || indexType
+
+  const getProcessRule = () => {
+    const processRule: ProcessRule = {
+      rules: {} as any, // api will check this. It will be removed after api refactored.
+      mode: segmentationType,
+    }
+    if (segmentationType === SegmentType.CUSTOM) {
+      const ruleObj = {
+        pre_processing_rules: rules,
+        segmentation: {
+          separator: unescape(segmentIdentifier),
+          max_tokens: maxChunkLength,
+          chunk_overlap: overlap,
+        },
+      }
+      processRule.rules = ruleObj
+    }
+    return processRule
+  }
+
+  const getNotionInfo = () => {
+    const workspacesMap = groupBy(notionPages, 'workspace_id')
+    const workspaces = Object.keys(workspacesMap).map((workspaceId) => {
+      return {
+        workspaceId,
+        pages: workspacesMap[workspaceId],
+      }
+    })
+    return workspaces.map((workspace) => {
+      return {
+        workspace_id: workspace.workspaceId,
+        pages: workspace.pages.map((page) => {
+          const { page_id, page_name, page_icon, type } = page
+          return {
+            page_id,
+            page_name,
+            page_icon,
+            type,
+          }
+        }),
+      }
+    }) as NotionInfo[]
+  }
+
+  const getWebsiteInfo = () => {
+    return {
+      provider: websiteCrawlProvider,
+      job_id: websiteCrawlJobId,
+      urls: websitePages.map(page => page.source_url),
+      only_main_content: crawlOptions?.only_main_content,
+    }
+  }
+
+  const getFileIndexingEstimateParams = (docForm: DocForm, language?: string): IndexingEstimateParams | undefined => {
+    if (dataSourceType === DataSourceType.FILE) {
+      return {
+        info_list: {
+          data_source_type: dataSourceType,
+          file_info_list: {
+            file_ids: files.map(file => file.id) as string[],
+          },
+        },
+        indexing_technique: getIndexing_technique() as string,
+        process_rule: getProcessRule(),
+        doc_form: docForm,
+        doc_language: language || docLanguage,
+        dataset_id: datasetId as string,
+      }
+    }
+    if (dataSourceType === DataSourceType.NOTION) {
+      return {
+        info_list: {
+          data_source_type: dataSourceType,
+          notion_info_list: getNotionInfo(),
+        },
+        indexing_technique: getIndexing_technique() as string,
+        process_rule: getProcessRule(),
+        doc_form: docForm,
+        doc_language: language || docLanguage,
+        dataset_id: datasetId as string,
+      }
+    }
+    if (dataSourceType === DataSourceType.WEB) {
+      return {
+        info_list: {
+          data_source_type: dataSourceType,
+          website_info_list: getWebsiteInfo(),
+        },
+        indexing_technique: getIndexing_technique() as string,
+        process_rule: getProcessRule(),
+        doc_form: docForm,
+        doc_language: language || docLanguage,
+        dataset_id: datasetId as string,
+      }
+    }
+  }
+  const {
+    modelList: rerankModelList,
+    defaultModel: rerankDefaultModel,
+    currentModel: isRerankDefaultModelValid,
+  } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
+  const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
+  const { data: defaultEmbeddingModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
+  const [embeddingModel, setEmbeddingModel] = useState<DefaultModel>(
+    currentDataset?.embedding_model
+      ? {
+        provider: currentDataset.embedding_model_provider,
+        model: currentDataset.embedding_model,
+      }
+      : {
+        provider: defaultEmbeddingModel?.provider.provider || '',
+        model: defaultEmbeddingModel?.model || '',
+      },
+  )
+  const getCreationParams = () => {
+    let params
+    if (segmentationType === SegmentType.CUSTOM && overlap > maxChunkLength) {
+      Toast.notify({ type: 'error', message: t('datasetCreation.stepTwo.overlapCheck') })
+      return
+    }
+    if (segmentationType === SegmentType.CUSTOM && maxChunkLength > limitMaxChunkLength) {
+      Toast.notify({ type: 'error', message: t('datasetCreation.stepTwo.maxLengthCheck', { limit: limitMaxChunkLength }) })
+      return
+    }
+    if (isSetting) {
+      params = {
+        original_document_id: documentDetail?.id,
+        doc_form: docForm,
+        doc_language: docLanguage,
+        process_rule: getProcessRule(),
+        // eslint-disable-next-line @typescript-eslint/no-use-before-define
+        retrieval_model: retrievalConfig, // Readonly. If want to changed, just go to settings page.
+        embedding_model: embeddingModel.model, // Readonly
+        embedding_model_provider: embeddingModel.provider, // Readonly
+      } as CreateDocumentReq
+    }
+    else { // create
+      const indexMethod = getIndexing_technique()
+      if (
+        !isReRankModelSelected({
+          rerankDefaultModel,
+          isRerankDefaultModelValid: !!isRerankDefaultModelValid,
+          rerankModelList,
+          // eslint-disable-next-line @typescript-eslint/no-use-before-define
+          retrievalConfig,
+          indexMethod: indexMethod as string,
+        })
+      ) {
+        Toast.notify({ type: 'error', message: t('appDebug.datasetConfig.rerankModelRequired') })
+        return
+      }
+      const postRetrievalConfig = ensureRerankModelSelected({
+        rerankDefaultModel: rerankDefaultModel!,
+        // eslint-disable-next-line @typescript-eslint/no-use-before-define
+        retrievalConfig,
+        indexMethod: indexMethod as string,
+      })
+      params = {
+        data_source: {
+          type: dataSourceType,
+          info_list: {
+            data_source_type: dataSourceType,
+          },
+        },
+        indexing_technique: getIndexing_technique(),
+        process_rule: getProcessRule(),
+        doc_form: docForm,
+        doc_language: docLanguage,
+
+        retrieval_model: postRetrievalConfig,
+        embedding_model: embeddingModel.model,
+        embedding_model_provider: embeddingModel.provider,
+      } as CreateDocumentReq
+      if (dataSourceType === DataSourceType.FILE) {
+        params.data_source.info_list.file_info_list = {
+          file_ids: files.map(file => file.id || '').filter(Boolean),
+        }
+      }
+      if (dataSourceType === DataSourceType.NOTION)
+        params.data_source.info_list.notion_info_list = getNotionInfo()
+
+      if (dataSourceType === DataSourceType.WEB)
+        params.data_source.info_list.website_info_list = getWebsiteInfo()
+    }
+    return params
+  }
+
+  const getRules = async () => {
+    try {
+      const res = await fetchDefaultProcessRule({ url: '/datasets/process-rule' })
+      const separator = res.rules.segmentation.separator
+      setSegmentIdentifier(separator)
+      setMaxChunkLength(res.rules.segmentation.max_tokens)
+      setLimitMaxChunkLength(res.limits.indexing_max_segmentation_tokens_length)
+      setOverlap(res.rules.segmentation.chunk_overlap)
+      setRules(res.rules.pre_processing_rules)
+      setDefaultConfig(res.rules)
+    }
+    catch (err) {
+      console.log(err)
+    }
+  }
+
+  const getRulesFromDetail = () => {
+    if (documentDetail) {
+      const rules = documentDetail.dataset_process_rule.rules
+      const separator = rules.segmentation.separator
+      const max = rules.segmentation.max_tokens
+      const overlap = rules.segmentation.chunk_overlap
+      setSegmentIdentifier(separator)
+      setMaxChunkLength(max)
+      setOverlap(overlap)
+      setRules(rules.pre_processing_rules)
+      setDefaultConfig(rules)
+    }
+  }
+
+  const getDefaultMode = () => {
+    if (documentDetail)
+      setSegmentationType(documentDetail.dataset_process_rule.mode)
+  }
+
+  const createHandle = async () => {
+    if (isCreating)
+      return
+    setIsCreating(true)
+    try {
+      let res
+      const params = getCreationParams()
+      if (!params)
+        return false
+
+      setIsCreating(true)
+      if (!datasetId) {
+        res = await createFirstDocument({
+          body: params as CreateDocumentReq,
+        })
+        updateIndexingTypeCache && updateIndexingTypeCache(indexType as string)
+        updateResultCache && updateResultCache(res)
+      }
+      else {
+        res = await createDocument({
+          datasetId,
+          body: params as CreateDocumentReq,
+        })
+        updateIndexingTypeCache && updateIndexingTypeCache(indexType as string)
+        updateResultCache && updateResultCache(res)
+      }
+      if (mutateDatasetRes)
+        mutateDatasetRes()
+      onStepChange && onStepChange(+1)
+      isSetting && onSave && onSave()
+    }
+    catch (err) {
+      Toast.notify({
+        type: 'error',
+        message: `${err}`,
+      })
+    }
+    finally {
+      setIsCreating(false)
+    }
+  }
+
+  const handleSwitch = (state: boolean) => {
+    if (state)
+      setDocForm(DocForm.QA)
+    else
+      setDocForm(DocForm.TEXT)
+  }
+
+  const previewSwitch = async (language?: string) => {
+    setPreviewSwitched(true)
+    setIsLanguageSelectDisabled(true)
+    if (segmentationType === SegmentType.AUTO)
+      setAutomaticFileIndexingEstimate(null)
+    else
+      setCustomFileIndexingEstimate(null)
+    try {
+      await fetchFileIndexingEstimate(DocForm.QA, language)
+    }
+    finally {
+      setIsLanguageSelectDisabled(false)
+    }
+  }
+
+  const handleSelect = (language: string) => {
+    setDocLanguage(language)
+    // Switch language, re-cutter
+    if (docForm === DocForm.QA && previewSwitched)
+      previewSwitch(language)
+  }
+
+  const changeToEconomicalType = () => {
+    if (!hasSetIndexType) {
+      setIndexType(IndexingType.ECONOMICAL)
+      setDocForm(DocForm.TEXT)
+    }
+  }
+
+  useEffect(() => {
+    // fetch rules
+    if (!isSetting) {
+      getRules()
+    }
+    else {
+      getRulesFromDetail()
+      getDefaultMode()
+    }
+  }, [])
+
+  useEffect(() => {
+    scrollRef.current?.addEventListener('scroll', scrollHandle)
+    return () => {
+      scrollRef.current?.removeEventListener('scroll', scrollHandle)
+    }
+  }, [])
+
+  useLayoutEffect(() => {
+    if (showPreview) {
+      previewScrollRef.current?.addEventListener('scroll', previewScrollHandle)
+      return () => {
+        previewScrollRef.current?.removeEventListener('scroll', previewScrollHandle)
+      }
+    }
+  }, [showPreview])
+
+  useEffect(() => {
+    if (indexingType === IndexingType.ECONOMICAL && docForm === DocForm.QA)
+      setDocForm(DocForm.TEXT)
+  }, [indexingType, docForm])
+
+  useEffect(() => {
+    // get indexing type by props
+    if (indexingType)
+      setIndexType(indexingType as IndexingType)
+
+    else
+      setIndexType(isAPIKeySet ? IndexingType.QUALIFIED : IndexingType.ECONOMICAL)
+  }, [isAPIKeySet, indexingType, datasetId])
+
+  useEffect(() => {
+    if (segmentationType === SegmentType.AUTO) {
+      setAutomaticFileIndexingEstimate(null)
+      !isMobile && setShowPreview()
+      fetchFileIndexingEstimate()
+      setPreviewSwitched(false)
+    }
+    else {
+      hidePreview()
+      setCustomFileIndexingEstimate(null)
+      setPreviewSwitched(false)
+    }
+  }, [segmentationType, indexType])
+
+  const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict || {
+    search_method: RETRIEVE_METHOD.semantic,
+    reranking_enable: false,
+    reranking_model: {
+      reranking_provider_name: rerankDefaultModel?.provider.provider,
+      reranking_model_name: rerankDefaultModel?.model,
+    },
+    top_k: 3,
+    score_threshold_enabled: false,
+    score_threshold: 0.5,
+  } as RetrievalConfig)
+
+  return (
+    <div className='flex w-full h-full'>
+      <div ref={scrollRef} className='relative h-full w-full overflow-y-scroll'>
+        <div className={cn(s.pageHeader, scrolled && s.fixed, isMobile && '!px-6')}>
+          <span>{t('datasetCreation.steps.two')}</span>
+          {(isMobile || !showPreview) && (
+            <Button
+              className='border-[0.5px] !h-8 hover:outline hover:outline-[0.5px] hover:outline-gray-300 text-gray-700 font-medium bg-white shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)]'
+              onClick={setShowPreview}
+            >
+              <Tooltip>
+                <div className="flex flex-row items-center">
+                  <RocketLaunchIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" />
+                  <span className="text-[13px]">{t('datasetCreation.stepTwo.previewTitleButton')}</span>
+                </div>
+              </Tooltip>
+            </Button>
+          )}
+        </div>
+        <div className={cn(s.form, isMobile && '!px-4')}>
+          <div className={s.label}>{t('datasetCreation.stepTwo.segmentation')}</div>
+          <div className='max-w-[640px]'>
+            <div
+              className={cn(
+                s.radioItem,
+                s.segmentationItem,
+                segmentationType === SegmentType.AUTO && s.active,
+              )}
+              onClick={() => setSegmentationType(SegmentType.AUTO)}
+            >
+              <span className={cn(s.typeIcon, s.auto)} />
+              <span className={cn(s.radio)} />
+              <div className={s.typeHeader}>
+                <div className={s.title}>{t('datasetCreation.stepTwo.auto')}</div>
+                <div className={s.tip}>{t('datasetCreation.stepTwo.autoDescription')}</div>
+              </div>
+            </div>
+            <div
+              className={cn(
+                s.radioItem,
+                s.segmentationItem,
+                segmentationType === SegmentType.CUSTOM && s.active,
+                segmentationType === SegmentType.CUSTOM && s.custom,
+              )}
+              onClick={() => setSegmentationType(SegmentType.CUSTOM)}
+            >
+              <span className={cn(s.typeIcon, s.customize)} />
+              <span className={cn(s.radio)} />
+              <div className={s.typeHeader}>
+                <div className={s.title}>{t('datasetCreation.stepTwo.custom')}</div>
+                <div className={s.tip}>{t('datasetCreation.stepTwo.customDescription')}</div>
+              </div>
+              {segmentationType === SegmentType.CUSTOM && (
+                <div className={s.typeFormBody}>
+                  <div className={s.formRow}>
+                    <div className='w-full'>
+                      <div className={s.label}>
+                        {t('datasetCreation.stepTwo.separator')}
+                        <Tooltip
+                          popupContent={
+                            <div className='max-w-[200px]'>
+                              {t('datasetCreation.stepTwo.separatorTip')}
+                            </div>
+                          }
+                        />
+                      </div>
+                      <Input
+                        type="text"
+                        className='h-9'
+                        placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={segmentIdentifier}
+                        onChange={e => setSegmentIdentifier(e.target.value)}
+                      />
+                    </div>
+                  </div>
+                  <div className={s.formRow}>
+                    <div className='w-full'>
+                      <div className={s.label}>{t('datasetCreation.stepTwo.maxLength')}</div>
+                      <Input
+                        type="number"
+                        className='h-9'
+                        placeholder={t('datasetCreation.stepTwo.maxLength') || ''}
+                        value={maxChunkLength}
+                        max={limitMaxChunkLength}
+                        min={1}
+                        onChange={e => setMaxChunkLength(parseInt(e.target.value.replace(/^0+/, ''), 10))}
+                      />
+                    </div>
+                  </div>
+                  <div className={s.formRow}>
+                    <div className='w-full'>
+                      <div className={s.label}>
+                        {t('datasetCreation.stepTwo.overlap')}
+                        <Tooltip
+                          popupContent={
+                            <div className='max-w-[200px]'>
+                              {t('datasetCreation.stepTwo.overlapTip')}
+                            </div>
+                          }
+                        />
+                      </div>
+                      <Input
+                        type="number"
+                        className='h-9'
+                        placeholder={t('datasetCreation.stepTwo.overlap') || ''}
+                        value={overlap}
+                        min={1}
+                        onChange={e => setOverlap(parseInt(e.target.value.replace(/^0+/, ''), 10))}
+                      />
+                    </div>
+                  </div>
+                  <div className={s.formRow}>
+                    <div className='w-full flex flex-col gap-1'>
+                      <div className={s.label}>{t('datasetCreation.stepTwo.rules')}</div>
+                      {rules.map(rule => (
+                        <div key={rule.id} className={s.ruleItem}>
+                          <input id={rule.id} type="checkbox" checked={rule.enabled} onChange={() => ruleChangeHandle(rule.id)} className="w-4 h-4 rounded border-gray-300 text-blue-700 focus:ring-blue-700" />
+                          <label htmlFor={rule.id} className="ml-2 text-sm font-normal cursor-pointer text-gray-800">{getRuleName(rule.id)}</label>
+                        </div>
+                      ))}
+                    </div>
+                  </div>
+                  <div className={s.formFooter}>
+                    <Button variant="primary" className={cn(s.button)} onClick={confirmChangeCustomConfig}>{t('datasetCreation.stepTwo.preview')}</Button>
+                    <Button className={cn(s.button, 'ml-2')} onClick={resetRules}>{t('datasetCreation.stepTwo.reset')}</Button>
+                  </div>
+                </div>
+              )}
+            </div>
+          </div>
+          <div className={s.label}>{t('datasetCreation.stepTwo.indexMode')}</div>
+          <div className='max-w-[640px]'>
+            <div className='flex items-center gap-3 flex-wrap sm:flex-nowrap'>
+              {(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.QUALIFIED)) && (
+                <div
+                  className={cn(
+                    s.radioItem,
+                    s.indexItem,
+                    !isAPIKeySet && s.disabled,
+                    !hasSetIndexType && indexType === IndexingType.QUALIFIED && s.active,
+                    hasSetIndexType && s.disabled,
+                    hasSetIndexType && '!w-full !min-h-[96px]',
+                  )}
+                  onClick={() => {
+                    if (isAPIKeySet)
+                      setIndexType(IndexingType.QUALIFIED)
+                  }}
+                >
+                  <span className={cn(s.typeIcon, s.qualified)} />
+                  {!hasSetIndexType && <span className={cn(s.radio)} />}
+                  <div className={s.typeHeader}>
+                    <div className={s.title}>
+                      {t('datasetCreation.stepTwo.qualified')}
+                      {!hasSetIndexType && <span className={s.recommendTag}>{t('datasetCreation.stepTwo.recommend')}</span>}
+                    </div>
+                    <div className={s.tip}>{t('datasetCreation.stepTwo.qualifiedTip')}</div>
+                  </div>
+                  {!isAPIKeySet && (
+                    <div className={s.warningTip}>
+                      <span>{t('datasetCreation.stepTwo.warning')}&nbsp;</span>
+                      <span className={s.click} onClick={onSetting}>{t('datasetCreation.stepTwo.click')}</span>
+                    </div>
+                  )}
+                </div>
+              )}
+
+              {(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.ECONOMICAL)) && (
+                <div
+                  className={cn(
+                    s.radioItem,
+                    s.indexItem,
+                    !hasSetIndexType && indexType === IndexingType.ECONOMICAL && s.active,
+                    hasSetIndexType && s.disabled,
+                    hasSetIndexType && '!w-full !min-h-[96px]',
+                  )}
+                  onClick={changeToEconomicalType}
+                >
+                  <span className={cn(s.typeIcon, s.economical)} />
+                  {!hasSetIndexType && <span className={cn(s.radio)} />}
+                  <div className={s.typeHeader}>
+                    <div className={s.title}>{t('datasetCreation.stepTwo.economical')}</div>
+                    <div className={s.tip}>{t('datasetCreation.stepTwo.economicalTip')}</div>
+                  </div>
+                </div>
+              )}
+            </div>
+            {hasSetIndexType && indexType === IndexingType.ECONOMICAL && (
+              <div className='mt-2 text-xs text-gray-500 font-medium'>
+                {t('datasetCreation.stepTwo.indexSettingTip')}
+                <Link className='text-[#155EEF]' href={`/datasets/${datasetId}/settings`}>{t('datasetCreation.stepTwo.datasetSettingLink')}</Link>
+              </div>
+            )}
+            {IS_CE_EDITION && indexType === IndexingType.QUALIFIED && (
+              <div className='mt-3 rounded-xl bg-gray-50 border border-gray-100'>
+                <div className='flex justify-between items-center px-5 py-4'>
+                  <div className='flex justify-center items-center w-8 h-8 rounded-lg bg-indigo-50'>
+                    <MessageChatSquare className='w-4 h-4' />
+                  </div>
+                  <div className='grow mx-3'>
+                    <div className='mb-[2px] text-md font-medium text-gray-900'>{t('datasetCreation.stepTwo.QATitle')}</div>
+                    <div className='inline-flex items-center text-[13px] leading-[18px] text-gray-500'>
+                      <span className='pr-1'>{t('datasetCreation.stepTwo.QALanguage')}</span>
+                      <LanguageSelect currentLanguage={docLanguage} onSelect={handleSelect} disabled={isLanguageSelectDisabled} />
+                    </div>
+                  </div>
+                  <div className='shrink-0'>
+                    <Switch
+                      defaultValue={docForm === DocForm.QA}
+                      onChange={handleSwitch}
+                      size='md'
+                    />
+                  </div>
+                </div>
+                {docForm === DocForm.QA && !QATipHide && (
+                  <div className='flex justify-between items-center px-5 py-2 bg-orange-50 border-t border-amber-100 rounded-b-xl text-[13px] leading-[18px] text-medium text-amber-500'>
+                    {t('datasetCreation.stepTwo.QATip')}
+                    <RiCloseLine className='w-4 h-4 text-gray-500 cursor-pointer' onClick={() => setQATipHide(true)} />
+                  </div>
+                )}
+              </div>
+            )}
+            {/* Embedding model */}
+            {indexType === IndexingType.QUALIFIED && (
+              <div className='mb-2'>
+                <div className={cn(s.label, datasetId && 'flex justify-between items-center')}>{t('datasetSettings.form.embeddingModel')}</div>
+                <ModelSelector
+                  readonly={!!datasetId}
+                  defaultModel={embeddingModel}
+                  modelList={embeddingModelList}
+                  onSelect={(model: DefaultModel) => {
+                    setEmbeddingModel(model)
+                  }}
+                />
+                {!!datasetId && (
+                  <div className='mt-2 text-xs text-gray-500 font-medium'>
+                    {t('datasetCreation.stepTwo.indexSettingTip')}
+                    <Link className='text-[#155EEF]' href={`/datasets/${datasetId}/settings`}>{t('datasetCreation.stepTwo.datasetSettingLink')}</Link>
+                  </div>
+                )}
+              </div>
+            )}
+            {/* Retrieval Method Config */}
+            <div>
+              {!datasetId
+                ? (
+                  <div className={s.label}>
+                    <div className='shrink-0 mr-4'>{t('datasetSettings.form.retrievalSetting.title')}</div>
+                    <div className='leading-[18px] text-xs font-normal text-gray-500'>
+                      <a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-[#155eef]'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
+                      {t('datasetSettings.form.retrievalSetting.longDescription')}
+                    </div>
+                  </div>
+                )
+                : (
+                  <div className={cn(s.label, 'flex justify-between items-center')}>
+                    <div>{t('datasetSettings.form.retrievalSetting.title')}</div>
+                  </div>
+                )}
+
+              <div className='max-w-[640px]'>
+                {
+                  getIndexing_technique() === IndexingType.QUALIFIED
+                    ? (
+                      <RetrievalMethodConfig
+                        value={retrievalConfig}
+                        onChange={setRetrievalConfig}
+                      />
+                    )
+                    : (
+                      <EconomicalRetrievalMethodConfig
+                        value={retrievalConfig}
+                        onChange={setRetrievalConfig}
+                      />
+                    )
+                }
+              </div>
+            </div>
+
+            <div className={s.source}>
+              <div className={s.sourceContent}>
+                {dataSourceType === DataSourceType.FILE && (
+                  <>
+                    <div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.fileSource')}</div>
+                    <div className='flex items-center text-sm leading-6 font-medium text-gray-800'>
+                      <span className={cn(s.fileIcon, files.length && s[files[0].extension || ''])} />
+                      {getFileName(files[0].name || '')}
+                      {files.length > 1 && (
+                        <span className={s.sourceCount}>
+                          <span>{t('datasetCreation.stepTwo.other')}</span>
+                          <span>{files.length - 1}</span>
+                          <span>{t('datasetCreation.stepTwo.fileUnit')}</span>
+                        </span>
+                      )}
+                    </div>
+                  </>
+                )}
+                {dataSourceType === DataSourceType.NOTION && (
+                  <>
+                    <div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.notionSource')}</div>
+                    <div className='flex items-center text-sm leading-6 font-medium text-gray-800'>
+                      <NotionIcon
+                        className='shrink-0 mr-1'
+                        type='page'
+                        src={notionPages[0]?.page_icon}
+                      />
+                      {notionPages[0]?.page_name}
+                      {notionPages.length > 1 && (
+                        <span className={s.sourceCount}>
+                          <span>{t('datasetCreation.stepTwo.other')}</span>
+                          <span>{notionPages.length - 1}</span>
+                          <span>{t('datasetCreation.stepTwo.notionUnit')}</span>
+                        </span>
+                      )}
+                    </div>
+                  </>
+                )}
+                {dataSourceType === DataSourceType.WEB && (
+                  <>
+                    <div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.websiteSource')}</div>
+                    <div className='flex items-center text-sm leading-6 font-medium text-gray-800'>
+                      <Globe01 className='shrink-0 mr-1' />
+                      <span className='grow w-0 truncate'>{websitePages[0].source_url}</span>
+                      {websitePages.length > 1 && (
+                        <span className={s.sourceCount}>
+                          <span>{t('datasetCreation.stepTwo.other')}</span>
+                          <span>{websitePages.length - 1}</span>
+                          <span>{t('datasetCreation.stepTwo.webpageUnit')}</span>
+                        </span>
+                      )}
+                    </div>
+                  </>
+                )}
+              </div>
+              <div className={s.divider} />
+              <div className={s.segmentCount}>
+                <div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.estimateSegment')}</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>
+                      )
+                  }
+                </div>
+              </div>
+            </div>
+            {!isSetting
+              ? (
+                <div className='flex items-center mt-8 py-2'>
+                  <Button onClick={() => onStepChange && onStepChange(-1)}>{t('datasetCreation.stepTwo.previousStep')}</Button>
+                  <div className={s.divider} />
+                  <Button loading={isCreating} variant='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.nextStep')}</Button>
+                </div>
+              )
+              : (
+                <div className='flex items-center mt-8 py-2'>
+                  <Button loading={isCreating} variant='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.save')}</Button>
+                  <Button className='ml-2' onClick={onCancel}>{t('datasetCreation.stepTwo.cancel')}</Button>
+                </div>
+              )}
+          </div>
+        </div>
+      </div>
+      <FloatRightContainer isMobile={isMobile} isOpen={showPreview} onClose={hidePreview} footer={null}>
+        {showPreview && <div ref={previewScrollRef} className={cn(s.previewWrap, isMobile && s.isMobile, 'relative h-full overflow-y-scroll border-l border-[#F2F4F7]')}>
+          <div className={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`)}>
+            <div className='flex items-center justify-between px-8'>
+              <div className='grow flex items-center'>
+                <div>{t('datasetCreation.stepTwo.previewTitle')}</div>
+                {docForm === DocForm.QA && !previewSwitched && (
+                  <Button className='ml-2' variant='secondary-accent' onClick={() => previewSwitch()}>{t('datasetCreation.stepTwo.previewButton')}</Button>
+                )}
+              </div>
+              <div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
+                <XMarkIcon className='h-4 w-4'></XMarkIcon>
+              </div>
+            </div>
+            {docForm === DocForm.QA && !previewSwitched && (
+              <div className='px-8 pr-12 text-xs text-gray-500'>
+                <span>{t('datasetCreation.stepTwo.previewSwitchTipStart')}</span>
+                <span className='text-amber-600'>{t('datasetCreation.stepTwo.previewSwitchTipEnd')}</span>
+              </div>
+            )}
+          </div>
+          <div className='my-4 px-8 space-y-4'>
+            {previewSwitched && docForm === DocForm.QA && fileIndexingEstimate?.qa_preview && (
+              <>
+                {fileIndexingEstimate?.qa_preview.map((item, index) => (
+                  <PreviewItem type={PreviewType.QA} key={item.question} qa={item} index={index + 1} />
+                ))}
+              </>
+            )}
+            {(docForm === DocForm.TEXT || !previewSwitched) && fileIndexingEstimate?.preview && (
+              <>
+                {fileIndexingEstimate?.preview.map((item, index) => (
+                  <PreviewItem type={PreviewType.TEXT} key={item} content={item} index={index + 1} />
+                ))}
+              </>
+            )}
+            {previewSwitched && docForm === DocForm.QA && !fileIndexingEstimate?.qa_preview && (
+              <div className='flex items-center justify-center h-[200px]'>
+                <Loading type='area' />
+              </div>
+            )}
+            {!previewSwitched && !fileIndexingEstimate?.preview && (
+              <div className='flex items-center justify-center h-[200px]'>
+                <Loading type='area' />
+              </div>
+            )}
+          </div>
+        </div>}
+        {!showPreview && (
+          <div className={cn(s.sideTip)}>
+            <div className={s.tipCard}>
+              <span className={s.icon} />
+              <div className={s.title}>{t('datasetCreation.stepTwo.sideTipTitle')}</div>
+              <div className={s.content}>
+                <p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP1')}</p>
+                <p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP2')}</p>
+                <p className='mb-3'>{t('datasetCreation.stepTwo.sideTipP3')}</p>
+                <p>{t('datasetCreation.stepTwo.sideTipP4')}</p>
+              </div>
+            </div>
+          </div>
+        )}
+      </FloatRightContainer>
+    </div>
+  )
+}
+
+export default StepTwo

+ 47 - 0
app/components/database/create/step-two/language-select/index.tsx

@@ -0,0 +1,47 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { RiArrowDownSLine } from '@remixicon/react'
+import cn from '@/utils/classnames'
+import Popover from '@/app/components/base/popover'
+import { languages } from '@/i18n/language'
+
+export type ILanguageSelectProps = {
+  currentLanguage: string
+  onSelect: (language: string) => void
+  disabled?: boolean
+}
+
+const LanguageSelect: FC<ILanguageSelectProps> = ({
+  currentLanguage,
+  onSelect,
+  disabled,
+}) => {
+  return (
+    <Popover
+      manualClose
+      trigger='click'
+      disabled={disabled}
+      htmlContent={
+        <div className='w-full py-1'>
+          {languages.filter(language => language.supported).map(({ prompt_name }) => (
+            <div
+              key={prompt_name}
+              className='py-2 px-3 mx-1 flex items-center gap-2 hover:bg-gray-100 rounded-lg cursor-pointer text-gray-700 text-sm'
+              onClick={() => onSelect(prompt_name)}>{prompt_name}
+            </div>
+          ))}
+        </div>
+      }
+      btnElement={
+        <div className='inline-flex items-center'>
+          <span className='pr-[2px] text-xs leading-[18px] font-medium'>{currentLanguage}</span>
+          <RiArrowDownSLine className='w-3 h-3 opacity-60' />
+        </div>
+      }
+      btnClassName={open => cn('!border-0 !px-0 !py-0 !bg-inherit !hover:bg-inherit', open ? 'text-blue-600' : 'text-gray-500')}
+      className='!w-[120px] h-fit !z-20 !translate-x-0 !left-[-16px]'
+    />
+  )
+}
+export default React.memo(LanguageSelect)

+ 78 - 0
app/components/database/create/step-two/preview-item/index.tsx

@@ -0,0 +1,78 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+
+export type IPreviewItemProps = {
+  type: string
+  index: number
+  content?: string
+  qa?: {
+    answer: string
+    question: string
+  }
+}
+
+export enum PreviewType {
+  TEXT = 'text',
+  QA = 'QA',
+}
+
+const sharpIcon = (
+  <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path d="M4.74999 1.5L3.24999 10.5M8.74998 1.5L7.24998 10.5M10.25 4H1.75M9.75 8H1.25" stroke="#98A2B3" strokeLinecap="round" strokeLinejoin="round" />
+  </svg>
+)
+
+const textIcon = (
+  <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path d="M4 3.5H8M6 3.5V8.5M3.9 10.5H8.1C8.94008 10.5 9.36012 10.5 9.68099 10.3365C9.96323 10.1927 10.1927 9.96323 10.3365 9.68099C10.5 9.36012 10.5 8.94008 10.5 8.1V3.9C10.5 3.05992 10.5 2.63988 10.3365 2.31901C10.1927 2.03677 9.96323 1.8073 9.68099 1.66349C9.36012 1.5 8.94008 1.5 8.1 1.5H3.9C3.05992 1.5 2.63988 1.5 2.31901 1.66349C2.03677 1.8073 1.8073 2.03677 1.66349 2.31901C1.5 2.63988 1.5 3.05992 1.5 3.9V8.1C1.5 8.94008 1.5 9.36012 1.66349 9.68099C1.8073 9.96323 2.03677 10.1927 2.31901 10.3365C2.63988 10.5 3.05992 10.5 3.9 10.5Z" stroke="#667085" strokeLinecap="round" strokeLinejoin="round" />
+  </svg>
+
+)
+
+const PreviewItem: FC<IPreviewItemProps> = ({
+  type = PreviewType.TEXT,
+  index,
+  content,
+  qa,
+}) => {
+  const { t } = useTranslation()
+  const charNums = type === PreviewType.TEXT
+    ? (content || '').length
+    : (qa?.answer || '').length + (qa?.question || '').length
+  const formattedIndex = (() => String(index).padStart(3, '0'))()
+
+  return (
+    <div className='p-4 rounded-xl bg-gray-50'>
+      <div className='flex items-center justify-between h-5 text-xs text-gray-500'>
+        <div className='flex items-center h-[18px] space-x-1 border border-gray-200 box-border rounded-md italic pl-1 pr-1.5 font-medium'>
+          {sharpIcon}
+          <span>{formattedIndex}</span>
+        </div>
+        <div className='flex items-center space-x-1'>
+          {textIcon}
+          <span>{charNums} {t('datasetCreation.stepTwo.characters')}</span>
+        </div>
+      </div>
+      <div className='mt-2 max-h-[120px] line-clamp-6 overflow-hidden text-sm text-gray-800'>
+        {type === PreviewType.TEXT && (
+          <div style={{ whiteSpace: 'pre-line' }}>{content}</div>
+        )}
+        {type === PreviewType.QA && (
+          <div style={{ whiteSpace: 'pre-line' }}>
+            <div className='flex'>
+              <div className='shrink-0 mr-2 text-medium text-gray-400'>Q</div>
+              <div style={{ whiteSpace: 'pre-line' }}>{qa?.question}</div>
+            </div>
+            <div className='flex'>
+              <div className='shrink-0 mr-2 text-medium text-gray-400'>A</div>
+              <div style={{ whiteSpace: 'pre-line' }}>{qa?.answer}</div>
+            </div>
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}
+export default React.memo(PreviewItem)

+ 54 - 0
app/components/database/create/step-two/unescape.ts

@@ -0,0 +1,54 @@
+// https://github.com/iamakulov/unescape-js/blob/master/src/index.js
+
+/**
+ * \\ - matches the backslash which indicates the beginning of an escape sequence
+ * (
+ *   u\{([0-9A-Fa-f]+)\} - first alternative; matches the variable-length hexadecimal escape sequence (\u{ABCD0})
+ * |
+ *   u([0-9A-Fa-f]{4}) - second alternative; matches the 4-digit hexadecimal escape sequence (\uABCD)
+ * |
+ *   x([0-9A-Fa-f]{2}) - third alternative; matches the 2-digit hexadecimal escape sequence (\xA5)
+ * |
+ *   ([1-7][0-7]{0,2}|[0-7]{2,3}) - fourth alternative; matches the up-to-3-digit octal escape sequence (\5 or \512)
+ * |
+ *   (['"tbrnfv0\\]) - fifth alternative; matches the special escape characters (\t, \n and so on)
+ * |
+ *   \U([0-9A-Fa-f]+) - sixth alternative; matches the 8-digit hexadecimal escape sequence used by python (\U0001F3B5)
+ * )
+ */
+const jsEscapeRegex = /\\(u\{([0-9A-Fa-f]+)\}|u([0-9A-Fa-f]{4})|x([0-9A-Fa-f]{2})|([1-7][0-7]{0,2}|[0-7]{2,3})|(['"tbrnfv0\\]))|\\U([0-9A-Fa-f]{8})/g
+
+const usualEscapeSequences: Record<string, string> = {
+  '0': '\0',
+  'b': '\b',
+  'f': '\f',
+  'n': '\n',
+  'r': '\r',
+  't': '\t',
+  'v': '\v',
+  '\'': '\'',
+  '"': '"',
+  '\\': '\\',
+}
+
+const fromHex = (str: string) => String.fromCodePoint(parseInt(str, 16))
+const fromOct = (str: string) => String.fromCodePoint(parseInt(str, 8))
+
+const unescape = (str: string) => {
+  return str.replace(jsEscapeRegex, (_, __, varHex, longHex, shortHex, octal, specialCharacter, python) => {
+    if (varHex !== undefined)
+      return fromHex(varHex)
+    else if (longHex !== undefined)
+      return fromHex(longHex)
+    else if (shortHex !== undefined)
+      return fromHex(shortHex)
+    else if (octal !== undefined)
+      return fromOct(octal)
+    else if (python !== undefined)
+      return fromHex(python)
+    else
+      return usualEscapeSequences[specialCharacter]
+  })
+}
+
+export default unescape

+ 107 - 0
app/components/database/create/steps-nav-bar/index.module.css

@@ -0,0 +1,107 @@
+.stepsHeader {
+  @apply flex items-center px-6 py-6;
+  color: #344054;
+  font-weight: 600;
+  font-size: 14px;
+  line-height: 20px;
+}
+.navBack {
+  @apply box-border flex justify-center items-center mr-3 w-8 h-8 bg-white bg-center bg-no-repeat cursor-pointer hover:border-gray-300;
+  border: 0.5px solid #F2F4F7;
+  box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
+  border-radius: 32px;
+  background-image: url(../assets/arrow-narrow-left.svg);
+  background-size: 16px;
+}
+.stepList {
+  @apply p-4 relative;
+  line-height: 18px;
+}
+
+.stepItem {
+  @apply relative flex justify-items-start pt-3 pr-0 pb-3 box-content;
+  padding-left: 52px;
+  font-size: 13px;
+  height: 18px;
+}
+
+.stepItem.step1::before {
+  content: '';
+  position: absolute;
+  bottom: 0;
+  left: 23px;
+  width: 2px;
+  height: 7px;
+  background-color: #f2f4f7;
+}
+
+.stepItem.step2::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 23px;
+  width: 2px;
+  height: 100%;
+  background-color: #f2f4f7;
+}
+.stepItem.step2::after {
+  content: '';
+  position: absolute;
+  top: 6px;
+  left: 23px;
+  width: 2px;
+  height: 28px;
+  background-color: #fff;
+}
+
+.stepItem.step3::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 23px;
+  width: 2px;
+  height: 7px;
+  background-color: #f2f4f7;
+}
+
+.stepNum {
+  @apply box-border absolute top-2 left-3 flex justify-center items-center  w-6 h-6;
+  color: #98a2b3;
+  font-size: 12px;
+  border: 1px solid #F2F4F7;
+  border-radius: 24px;
+  z-index: 1;
+}
+
+.stepName {
+  color: #98a2b3;
+}
+
+.stepItem.active .stepNum {
+  color: #1c64f2;
+  background-color: #EFF4FF;
+  border: none;
+}
+
+.stepItem.active .stepName {
+  color: #1c64f2;
+}
+
+.stepItem.done .stepNum {
+  color: #667085;
+  background-color: #f2f4f7;
+  border: none;
+}
+
+.stepItem.done .stepNum::after {
+  content: '';
+  display: flex;
+  width: 12px;
+  height: 12px;
+  background: center no-repeat url(../assets/check.svg);
+  background-size: 12px;
+}
+
+.stepItem.done .stepName {
+  color: #667085;
+}

+ 61 - 0
app/components/database/create/steps-nav-bar/index.tsx

@@ -0,0 +1,61 @@
+'use client'
+import { useTranslation } from 'react-i18next'
+import { useRouter } from 'next/navigation'
+
+import { useCallback } from 'react'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
+
+type IStepsNavBarProps = {
+  step: number
+  datasetId?: string
+}
+
+const STEP_T_MAP: Record<number, string> = {
+  1: 'datasetCreation.steps.one',
+  2: 'datasetCreation.steps.two',
+  3: 'datasetCreation.steps.three',
+}
+
+const STEP_LIST = [1, 2, 3]
+
+const StepsNavBar = ({
+  step,
+  datasetId,
+}: IStepsNavBarProps) => {
+  const { t } = useTranslation()
+  const router = useRouter()
+
+  const media = useBreakpoints()
+  const isMobile = media === MediaType.mobile
+
+  const navBackHandle = useCallback(() => {
+    if (!datasetId)
+      router.replace('/datasets')
+    else
+      router.replace(`/datasets/${datasetId}/documents`)
+  }, [router, datasetId])
+
+  return (
+    <div className='w-full pt-4'>
+      <div className={cn(s.stepsHeader, isMobile && '!px-0 justify-center')}>
+        <div onClick={navBackHandle} className={cn(s.navBack, isMobile && '!mr-0')} />
+        {!isMobile && (!datasetId ? t('datasetCreation.steps.header.creation') : t('datasetCreation.steps.header.update'))}
+      </div>
+      <div className={cn(s.stepList, isMobile && '!p-0')}>
+        {STEP_LIST.map(item => (
+          <div
+            key={item}
+            className={cn(s.stepItem, s[`step${item}`], step === item && s.active, step > item && s.done, isMobile && 'px-0')}
+          >
+            <div className={cn(s.stepNum)}>{step > item ? '' : item}</div>
+            <div className={cn(s.stepName)}>{isMobile ? '' : t(STEP_T_MAP[item])}</div>
+          </div>
+        ))}
+      </div>
+    </div>
+  )
+}
+
+export default StepsNavBar

+ 37 - 0
app/components/database/create/stop-embedding-modal/index.module.css

@@ -0,0 +1,37 @@
+.modal {
+  position: relative;
+}
+.modal .icon {
+  width: 48px;
+  height: 48px;
+  background: rgba(255, 255, 255, 0.9) center no-repeat url(../assets/annotation-info.svg);
+  background-size: 24px;
+  border: 0.5px solid #F2F4F7;
+  box-shadow: 0px 20px 24px -4px rgba(16, 24, 40, 0.08), 0px 8px 8px -4px rgba(16, 24, 40, 0.03);
+  border-radius: 12px;
+}
+.modal .close {
+  position: absolute;
+  right: 16px;
+  top: 16px;
+  width: 32px;
+  height: 32px;
+  border-radius: 8px;
+  background: center no-repeat url(../assets/close.svg);
+  background-size: 16px;
+  cursor: pointer;
+}
+.modal .title {
+  @apply mt-3 mb-1;
+  font-weight: 600;
+  font-size: 20px;
+  line-height: 30px;
+  color: #101828;
+}
+.modal .content {
+  @apply mb-10;
+  font-weight: 400;
+  font-size: 14px;
+  line-height: 20px;
+  color: #667085;
+}

+ 45 - 0
app/components/database/create/stop-embedding-modal/index.tsx

@@ -0,0 +1,45 @@
+'use client'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import s from './index.module.css'
+import cn from '@/utils/classnames'
+import Modal from '@/app/components/base/modal'
+import Button from '@/app/components/base/button'
+
+type IProps = {
+  show: boolean
+  onConfirm: () => void
+  onHide: () => void
+}
+
+const StopEmbeddingModal = ({
+  show = false,
+  onConfirm,
+  onHide,
+}: IProps) => {
+  const { t } = useTranslation()
+
+  const submit = () => {
+    onConfirm()
+    onHide()
+  }
+
+  return (
+    <Modal
+      isShow={show}
+      onClose={onHide}
+      className={cn(s.modal, '!max-w-[480px]', 'px-8')}
+    >
+      <div className={s.icon} />
+      <span className={s.close} onClick={onHide} />
+      <div className={s.title}>{t('datasetCreation.stepThree.modelTitle')}</div>
+      <div className={s.content}>{t('datasetCreation.stepThree.modelContent')}</div>
+      <div className='flex flex-row-reverse'>
+        <Button className='w-24 ml-2' variant='primary' onClick={submit}>{t('datasetCreation.stepThree.modelButtonConfirm')}</Button>
+        <Button className='w-24' onClick={onHide}>{t('datasetCreation.stepThree.modelButtonCancel')}</Button>
+      </div>
+    </Modal>
+  )
+}
+
+export default StopEmbeddingModal

+ 40 - 0
app/components/database/create/website/base/checkbox-with-label.tsx

@@ -0,0 +1,40 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import cn from '@/utils/classnames'
+import Checkbox from '@/app/components/base/checkbox'
+import Tooltip from '@/app/components/base/tooltip'
+
+type Props = {
+  className?: string
+  isChecked: boolean
+  onChange: (isChecked: boolean) => void
+  label: string
+  labelClassName?: string
+  tooltip?: string
+}
+
+const CheckboxWithLabel: FC<Props> = ({
+  className = '',
+  isChecked,
+  onChange,
+  label,
+  labelClassName,
+  tooltip,
+}) => {
+  return (
+    <label className={cn(className, 'flex items-center h-7 space-x-2')}>
+      <Checkbox checked={isChecked} onCheck={() => onChange(!isChecked)} />
+      <div className={cn(labelClassName, 'text-sm font-normal text-gray-800')}>{label}</div>
+      {tooltip && (
+        <Tooltip
+          popupContent={
+            <div className='w-[200px]'>{tooltip}</div>
+          }
+          triggerClassName='ml-0.5 w-4 h-4'
+        />
+      )}
+    </label>
+  )
+}
+export default React.memo(CheckboxWithLabel)

+ 40 - 0
app/components/database/create/website/base/crawled-result-item.tsx

@@ -0,0 +1,40 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import cn from '@/utils/classnames'
+import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets'
+import Checkbox from '@/app/components/base/checkbox'
+
+type Props = {
+  payload: CrawlResultItemType
+  isChecked: boolean
+  isPreview: boolean
+  onCheckChange: (checked: boolean) => void
+  onPreview: () => void
+}
+
+const CrawledResultItem: FC<Props> = ({
+  isPreview,
+  payload,
+  isChecked,
+  onCheckChange,
+  onPreview,
+}) => {
+  const { t } = useTranslation()
+
+  const handleCheckChange = useCallback(() => {
+    onCheckChange(!isChecked)
+  }, [isChecked, onCheckChange])
+  return (
+    <div className={cn(isPreview ? 'border-[#D1E0FF] bg-primary-50 shadow-xs' : 'group hover:bg-gray-100', 'rounded-md px-2 py-[5px] cursor-pointer border border-transparent')}>
+      <div className='flex items-center h-5'>
+        <Checkbox className='group-hover:border-2 group-hover:border-primary-600 mr-2 shrink-0' checked={isChecked} onCheck={handleCheckChange} />
+        <div className='grow w-0 truncate text-sm font-medium text-gray-700' title={payload.title}>{payload.title}</div>
+        <div onClick={onPreview} className='hidden group-hover:flex items-center h-6 px-2 text-xs rounded-md font-medium text-gray-500 uppercase hover:bg-gray-50'>{t('datasetCreation.stepOne.website.preview')}</div>
+      </div>
+      <div className='mt-0.5 truncate pl-6 leading-[18px] text-xs font-normal text-gray-500' title={payload.source_url}>{payload.source_url}</div>
+    </div>
+  )
+}
+export default React.memo(CrawledResultItem)

+ 87 - 0
app/components/database/create/website/base/crawled-result.tsx

@@ -0,0 +1,87 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import CheckboxWithLabel from './checkbox-with-label'
+import CrawledResultItem from './crawled-result-item'
+import cn from '@/utils/classnames'
+import type { CrawlResultItem } from '@/models/datasets'
+
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  className?: string
+  list: CrawlResultItem[]
+  checkedList: CrawlResultItem[]
+  onSelectedChange: (selected: CrawlResultItem[]) => void
+  onPreview: (payload: CrawlResultItem) => void
+  usedTime: number
+}
+
+const CrawledResult: FC<Props> = ({
+  className = '',
+  list,
+  checkedList,
+  onSelectedChange,
+  onPreview,
+  usedTime,
+}) => {
+  const { t } = useTranslation()
+
+  const isCheckAll = checkedList.length === list.length
+
+  const handleCheckedAll = useCallback(() => {
+    if (!isCheckAll)
+      onSelectedChange(list)
+
+    else
+      onSelectedChange([])
+  }, [isCheckAll, list, onSelectedChange])
+
+  const handleItemCheckChange = useCallback((item: CrawlResultItem) => {
+    return (checked: boolean) => {
+      if (checked)
+        onSelectedChange([...checkedList, item])
+
+      else
+        onSelectedChange(checkedList.filter(checkedItem => checkedItem.source_url !== item.source_url))
+    }
+  }, [checkedList, onSelectedChange])
+
+  const [previewIndex, setPreviewIndex] = React.useState<number>(-1)
+  const handlePreview = useCallback((index: number) => {
+    return () => {
+      setPreviewIndex(index)
+      onPreview(list[index])
+    }
+  }, [list, onPreview])
+
+  return (
+    <div className={cn(className, 'border-t border-gray-200')}>
+      <div className='flex items-center justify-between h-[34px] px-4 bg-gray-50 shadow-xs border-b-[0.5px] border-black/8 text-xs font-normal text-gray-700'>
+        <CheckboxWithLabel
+          isChecked={isCheckAll}
+          onChange={handleCheckedAll} label={isCheckAll ? t(`${I18N_PREFIX}.resetAll`) : t(`${I18N_PREFIX}.selectAll`)}
+          labelClassName='!font-medium'
+        />
+        <div>{t(`${I18N_PREFIX}.scrapTimeInfo`, {
+          total: list.length,
+          time: usedTime.toFixed(1),
+        })}</div>
+      </div>
+      <div className='p-2'>
+        {list.map((item, index) => (
+          <CrawledResultItem
+            key={item.source_url}
+            isPreview={index === previewIndex}
+            onPreview={handlePreview(index)}
+            payload={item}
+            isChecked={checkedList.some(checkedItem => checkedItem.source_url === item.source_url)}
+            onCheckChange={handleItemCheckChange(item)}
+          />
+        ))}
+      </div>
+    </div>
+  )
+}
+export default React.memo(CrawledResult)

+ 37 - 0
app/components/database/create/website/base/crawling.tsx

@@ -0,0 +1,37 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import cn from '@/utils/classnames'
+import { RowStruct } from '@/app/components/base/icons/src/public/other'
+
+type Props = {
+  className?: string
+  crawledNum: number
+  totalNum: number
+}
+
+const Crawling: FC<Props> = ({
+  className = '',
+  crawledNum,
+  totalNum,
+}) => {
+  const { t } = useTranslation()
+
+  return (
+    <div className={cn(className, 'border-t border-gray-200')}>
+      <div className='flex items-center h-[34px] px-4 bg-gray-50 shadow-xs border-b-[0.5px] border-black/8 text-xs font-normal text-gray-700'>
+        {t('datasetCreation.stepOne.website.totalPageScraped')} {crawledNum}/{totalNum}
+      </div>
+
+      <div className='p-2'>
+        {['', '', '', ''].map((item, index) => (
+          <div className='py-[5px]' key={index}>
+            <RowStruct />
+          </div>
+        ))}
+      </div>
+    </div>
+  )
+}
+export default React.memo(Crawling)

+ 30 - 0
app/components/database/create/website/base/error-message.tsx

@@ -0,0 +1,30 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import cn from '@/utils/classnames'
+import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
+
+type Props = {
+  className?: string
+  title: string
+  errorMsg?: string
+}
+
+const ErrorMessage: FC<Props> = ({
+  className,
+  title,
+  errorMsg,
+}) => {
+  return (
+    <div className={cn(className, 'py-2 px-4 border-t border-gray-200 bg-[#FFFAEB]')}>
+      <div className='flex items-center h-5'>
+        <AlertTriangle className='mr-2 w-4 h-4 text-[#F79009]' />
+        <div className='text-sm font-medium text-[#DC6803]'>{title}</div>
+      </div>
+      {errorMsg && (
+        <div className='mt-1 pl-6 leading-[18px] text-xs font-normal text-gray-700'>{errorMsg}</div>
+      )}
+    </div>
+  )
+}
+export default React.memo(ErrorMessage)

+ 54 - 0
app/components/database/create/website/base/field.tsx

@@ -0,0 +1,54 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import Input from './input'
+import cn from '@/utils/classnames'
+import Tooltip from '@/app/components/base/tooltip'
+
+type Props = {
+  className?: string
+  label: string
+  labelClassName?: string
+  value: string | number
+  onChange: (value: string | number) => void
+  isRequired?: boolean
+  placeholder?: string
+  isNumber?: boolean
+  tooltip?: string
+}
+
+const Field: FC<Props> = ({
+  className,
+  label,
+  labelClassName,
+  value,
+  onChange,
+  isRequired = false,
+  placeholder = '',
+  isNumber = false,
+  tooltip,
+}) => {
+  return (
+    <div className={cn(className)}>
+      <div className='flex py-[7px]'>
+        <div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-gray-900')}>{label} </div>
+        {isRequired && <span className='ml-0.5 text-xs font-semibold text-[#D92D20]'>*</span>}
+        {tooltip && (
+          <Tooltip
+            popupContent={
+              <div className='w-[200px]'>{tooltip}</div>
+            }
+            triggerClassName='ml-0.5 w-4 h-4'
+          />
+        )}
+      </div>
+      <Input
+        value={value}
+        onChange={onChange}
+        placeholder={placeholder}
+        isNumber={isNumber}
+      />
+    </div>
+  )
+}
+export default React.memo(Field)

+ 58 - 0
app/components/database/create/website/base/input.tsx

@@ -0,0 +1,58 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback } from 'react'
+
+type Props = {
+  value: string | number
+  onChange: (value: string | number) => void
+  placeholder?: string
+  isNumber?: boolean
+}
+
+const MIN_VALUE = 0
+
+const Input: FC<Props> = ({
+  value,
+  onChange,
+  placeholder = '',
+  isNumber = false,
+}) => {
+  const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
+    const value = e.target.value
+    if (isNumber) {
+      let numberValue = parseInt(value, 10) // integer only
+      if (isNaN(numberValue)) {
+        onChange('')
+        return
+      }
+      if (numberValue < MIN_VALUE)
+        numberValue = MIN_VALUE
+
+      onChange(numberValue)
+      return
+    }
+    onChange(value)
+  }, [isNumber, onChange])
+
+  const otherOption = (() => {
+    if (isNumber) {
+      return {
+        min: MIN_VALUE,
+      }
+    }
+    return {
+
+    }
+  })()
+  return (
+    <input
+      type={isNumber ? 'number' : 'text'}
+      {...otherOption}
+      value={value}
+      onChange={handleChange}
+      className='flex h-9 w-full py-1 px-2 rounded-lg text-xs leading-normal bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-gray-50 placeholder:text-gray-400'
+      placeholder={placeholder}
+    />
+  )
+}
+export default React.memo(Input)

+ 24 - 0
app/components/database/create/website/base/mock-crawl-result.ts

@@ -0,0 +1,24 @@
+import type { CrawlResultItem } from '@/models/datasets'
+
+const result: CrawlResultItem[] = [
+  {
+    title: 'Start the frontend Docker container separately',
+    markdown: 'Markdown 1',
+    description: 'Description 1',
+    source_url: 'https://example.com/1',
+  },
+  {
+    title: 'Advanced Tool Integration',
+    markdown: 'Markdown 2',
+    description: 'Description 2',
+    source_url: 'https://example.com/2',
+  },
+  {
+    title: 'Local Source Code Start | English | Dify',
+    markdown: 'Markdown 3',
+    description: 'Description 3',
+    source_url: 'https://example.com/3',
+  },
+]
+
+export default result

+ 55 - 0
app/components/database/create/website/base/options-wrap.tsx

@@ -0,0 +1,55 @@
+'use client'
+import { useBoolean } from 'ahooks'
+import type { FC } from 'react'
+import React, { useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+import cn from '@/utils/classnames'
+import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
+import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  className?: string
+  children: React.ReactNode
+  controlFoldOptions?: number
+}
+
+const OptionsWrap: FC<Props> = ({
+  className = '',
+  children,
+  controlFoldOptions,
+}) => {
+  const { t } = useTranslation()
+
+  const [fold, {
+    toggle: foldToggle,
+    setTrue: foldHide,
+  }] = useBoolean(false)
+
+  useEffect(() => {
+    if (controlFoldOptions)
+      foldHide()
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [controlFoldOptions])
+  return (
+    <div className={cn(className, !fold ? 'mb-0' : 'mb-3')}>
+      <div
+        className='flex justify-between items-center h-[26px] py-1 cursor-pointer select-none'
+        onClick={foldToggle}
+      >
+        <div className='flex items-center text-gray-700'>
+          <Settings04 className='mr-1 w-4 h-4' />
+          <div className='text-[13px] font-semibold text-gray-800 uppercase'>{t(`${I18N_PREFIX}.options`)}</div>
+        </div>
+        <ChevronRight className={cn(!fold && 'rotate-90', 'w-4 h-4 text-gray-500')} />
+      </div>
+      {!fold && (
+        <div className='mb-4'>
+          {children}
+        </div>
+      )}
+
+    </div>
+  )
+}
+export default React.memo(OptionsWrap)

+ 48 - 0
app/components/database/create/website/base/url-input.tsx

@@ -0,0 +1,48 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import Input from './input'
+import Button from '@/app/components/base/button'
+
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  isRunning: boolean
+  onRun: (url: string) => void
+}
+
+const UrlInput: FC<Props> = ({
+  isRunning,
+  onRun,
+}) => {
+  const { t } = useTranslation()
+  const [url, setUrl] = useState('')
+  const handleUrlChange = useCallback((url: string | number) => {
+    setUrl(url as string)
+  }, [])
+  const handleOnRun = useCallback(() => {
+    if (isRunning)
+      return
+    onRun(url)
+  }, [isRunning, onRun, url])
+
+  return (
+    <div className='flex items-center justify-between'>
+      <Input
+        value={url}
+        onChange={handleUrlChange}
+        placeholder='https://docs.dify.ai'
+      />
+      <Button
+        variant='primary'
+        onClick={handleOnRun}
+        className='ml-2'
+        loading={isRunning}
+      >
+        {!isRunning ? t(`${I18N_PREFIX}.run`) : ''}
+      </Button>
+    </div>
+  )
+}
+export default React.memo(UrlInput)

+ 42 - 0
app/components/database/create/website/firecrawl/header.tsx

@@ -0,0 +1,42 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
+import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
+
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  onSetting: () => void
+}
+
+const Header: FC<Props> = ({
+  onSetting,
+}) => {
+  const { t } = useTranslation()
+
+  return (
+    <div className='flex h-6 items-center justify-between'>
+      <div className='flex items-center'>
+        <div className='text-base font-medium text-gray-700'>{t(`${I18N_PREFIX}.firecrawlTitle`)}</div>
+        <div className='ml-2 mr-1 w-px h-3.5 bg-gray-200'></div>
+        <div
+          className='p-1 rounded-md hover:bg-black/5 cursor-pointer'
+          onClick={onSetting}
+        >
+          <Settings01 className='w-3.5 h-3.5 text-gray-500' />
+        </div>
+      </div>
+      <a
+        href='https://docs.firecrawl.dev/introduction'
+        target='_blank' rel='noopener noreferrer'
+        className='flex items-center text-xs text-primary-600'
+      >
+        <BookOpen01 className='mr-1 w-3.5 h-3.5 text-primary-600' />
+        {t(`${I18N_PREFIX}.firecrawlDoc`)}
+      </a>
+    </div>
+  )
+}
+export default React.memo(Header)

+ 218 - 0
app/components/database/create/website/firecrawl/index.tsx

@@ -0,0 +1,218 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback, useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import UrlInput from '../base/url-input'
+import OptionsWrap from '../base/options-wrap'
+import CrawledResult from '../base/crawled-result'
+import Crawling from '../base/crawling'
+import ErrorMessage from '../base/error-message'
+import Header from './header'
+import Options from './options'
+import cn from '@/utils/classnames'
+import { useModalContext } from '@/context/modal-context'
+import type { CrawlOptions, CrawlResultItem } from '@/models/datasets'
+import Toast from '@/app/components/base/toast'
+import { checkFirecrawlTaskStatus, createFirecrawlTask } from '@/service/datasets'
+import { sleep } from '@/utils'
+
+const ERROR_I18N_PREFIX = 'common.errorMsg'
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  onPreview: (payload: CrawlResultItem) => void
+  checkedCrawlResult: CrawlResultItem[]
+  onCheckedCrawlResultChange: (payload: CrawlResultItem[]) => void
+  onJobIdChange: (jobId: string) => void
+  crawlOptions: CrawlOptions
+  onCrawlOptionsChange: (payload: CrawlOptions) => void
+}
+
+enum Step {
+  init = 'init',
+  running = 'running',
+  finished = 'finished',
+}
+
+const FireCrawl: FC<Props> = ({
+  onPreview,
+  checkedCrawlResult,
+  onCheckedCrawlResultChange,
+  onJobIdChange,
+  crawlOptions,
+  onCrawlOptionsChange,
+}) => {
+  const { t } = useTranslation()
+  const [step, setStep] = useState<Step>(Step.init)
+  const [controlFoldOptions, setControlFoldOptions] = useState<number>(0)
+  useEffect(() => {
+    if (step !== Step.init)
+      setControlFoldOptions(Date.now())
+  }, [step])
+  const { setShowAccountSettingModal } = useModalContext()
+  const handleSetting = useCallback(() => {
+    setShowAccountSettingModal({
+      payload: 'data-source',
+    })
+  }, [setShowAccountSettingModal])
+
+  const checkValid = useCallback((url: string) => {
+    let errorMsg = ''
+    if (!url) {
+      errorMsg = t(`${ERROR_I18N_PREFIX}.fieldRequired`, {
+        field: 'url',
+      })
+    }
+
+    if (!errorMsg && !((url.startsWith('http://') || url.startsWith('https://'))))
+      errorMsg = t(`${ERROR_I18N_PREFIX}.urlError`)
+
+    if (!errorMsg && (crawlOptions.limit === null || crawlOptions.limit === undefined || crawlOptions.limit === '')) {
+      errorMsg = t(`${ERROR_I18N_PREFIX}.fieldRequired`, {
+        field: t(`${I18N_PREFIX}.limit`),
+      })
+    }
+
+    return {
+      isValid: !errorMsg,
+      errorMsg,
+    }
+  }, [crawlOptions, t])
+
+  const isInit = step === Step.init
+  const isCrawlFinished = step === Step.finished
+  const isRunning = step === Step.running
+  const [crawlResult, setCrawlResult] = useState<{
+    current: number
+    total: number
+    data: CrawlResultItem[]
+    time_consuming: number | string
+  } | undefined>(undefined)
+  const [crawlErrorMessage, setCrawlErrorMessage] = useState('')
+  const showError = isCrawlFinished && crawlErrorMessage
+
+  const waitForCrawlFinished = useCallback(async (jobId: string) => {
+    try {
+      const res = await checkFirecrawlTaskStatus(jobId) as any
+      if (res.status === 'completed') {
+        return {
+          isError: false,
+          data: {
+            ...res,
+            total: Math.min(res.total, parseFloat(crawlOptions.limit as string)),
+          },
+        }
+      }
+      if (res.status === 'error' || !res.status) {
+        // can't get the error message from the firecrawl api
+        return {
+          isError: true,
+          errorMessage: res.message,
+          data: {
+            data: [],
+          },
+        }
+      }
+      // update the progress
+      setCrawlResult({
+        ...res,
+        total: Math.min(res.total, parseFloat(crawlOptions.limit as string)),
+      })
+      onCheckedCrawlResultChange(res.data || []) // default select the crawl result
+      await sleep(2500)
+      return await waitForCrawlFinished(jobId)
+    }
+    catch (e: any) {
+      const errorBody = await e.json()
+      return {
+        isError: true,
+        errorMessage: errorBody.message,
+        data: {
+          data: [],
+        },
+      }
+    }
+  }, [crawlOptions.limit])
+
+  const handleRun = useCallback(async (url: string) => {
+    const { isValid, errorMsg } = checkValid(url)
+    if (!isValid) {
+      Toast.notify({
+        message: errorMsg!,
+        type: 'error',
+      })
+      return
+    }
+    setStep(Step.running)
+    try {
+      const passToServerCrawlOptions: any = {
+        ...crawlOptions,
+      }
+      if (crawlOptions.max_depth === '')
+        delete passToServerCrawlOptions.max_depth
+
+      const res = await createFirecrawlTask({
+        url,
+        options: passToServerCrawlOptions,
+      }) as any
+      const jobId = res.job_id
+      onJobIdChange(jobId)
+      const { isError, data, errorMessage } = await waitForCrawlFinished(jobId)
+      if (isError) {
+        setCrawlErrorMessage(errorMessage || t(`${I18N_PREFIX}.unknownError`))
+      }
+      else {
+        setCrawlResult(data)
+        onCheckedCrawlResultChange(data.data || []) // default select the crawl result
+        setCrawlErrorMessage('')
+      }
+    }
+    catch (e) {
+      setCrawlErrorMessage(t(`${I18N_PREFIX}.unknownError`)!)
+      console.log(e)
+    }
+    finally {
+      setStep(Step.finished)
+    }
+  }, [checkValid, crawlOptions, onJobIdChange, t, waitForCrawlFinished])
+
+  return (
+    <div>
+      <Header onSetting={handleSetting} />
+      <div className={cn('mt-2 p-4 pb-0 rounded-xl border border-gray-200')}>
+        <UrlInput onRun={handleRun} isRunning={isRunning} />
+        <OptionsWrap
+          className={cn('mt-4')}
+          controlFoldOptions={controlFoldOptions}
+        >
+          <Options className='mt-2' payload={crawlOptions} onChange={onCrawlOptionsChange} />
+        </OptionsWrap>
+
+        {!isInit && (
+          <div className='mt-3 relative left-[-16px] w-[calc(100%_+_32px)] rounded-b-xl'>
+            {isRunning
+              && <Crawling
+                className='mt-2'
+                crawledNum={crawlResult?.current || 0}
+                totalNum={crawlResult?.total || parseFloat(crawlOptions.limit as string) || 0}
+              />}
+            {showError && (
+              <ErrorMessage className='rounded-b-xl' title={t(`${I18N_PREFIX}.exceptionErrorTitle`)} errorMsg={crawlErrorMessage} />
+            )}
+            {isCrawlFinished && !showError
+              && <CrawledResult
+                className='mb-2'
+                list={crawlResult?.data || []}
+                checkedList={checkedCrawlResult}
+                onSelectedChange={onCheckedCrawlResultChange}
+                onPreview={onPreview}
+                usedTime={parseFloat(crawlResult?.time_consuming as string) || 0}
+              />
+            }
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}
+export default React.memo(FireCrawl)

+ 83 - 0
app/components/database/create/website/firecrawl/options.tsx

@@ -0,0 +1,83 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import CheckboxWithLabel from '../base/checkbox-with-label'
+import Field from '../base/field'
+import cn from '@/utils/classnames'
+import type { CrawlOptions } from '@/models/datasets'
+
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  className?: string
+  payload: CrawlOptions
+  onChange: (payload: CrawlOptions) => void
+}
+
+const Options: FC<Props> = ({
+  className = '',
+  payload,
+  onChange,
+}) => {
+  const { t } = useTranslation()
+
+  const handleChange = useCallback((key: keyof CrawlOptions) => {
+    return (value: any) => {
+      onChange({
+        ...payload,
+        [key]: value,
+      })
+    }
+  }, [payload, onChange])
+  return (
+    <div className={cn(className, ' space-y-2')}>
+      <CheckboxWithLabel
+        label={t(`${I18N_PREFIX}.crawlSubPage`)}
+        isChecked={payload.crawl_sub_pages}
+        onChange={handleChange('crawl_sub_pages')}
+      />
+      <div className='flex justify-between space-x-4'>
+        <Field
+          className='grow shrink-0'
+          label={t(`${I18N_PREFIX}.limit`)}
+          value={payload.limit}
+          onChange={handleChange('limit')}
+          isNumber
+          isRequired
+        />
+        <Field
+          className='grow shrink-0'
+          label={t(`${I18N_PREFIX}.maxDepth`)}
+          value={payload.max_depth}
+          onChange={handleChange('max_depth')}
+          isNumber
+          tooltip={t(`${I18N_PREFIX}.maxDepthTooltip`)!}
+        />
+      </div>
+
+      <div className='flex justify-between space-x-4'>
+        <Field
+          className='grow shrink-0'
+          label={t(`${I18N_PREFIX}.excludePaths`)}
+          value={payload.excludes}
+          onChange={handleChange('excludes')}
+          placeholder='blog/*, /about/*'
+        />
+        <Field
+          className='grow shrink-0'
+          label={t(`${I18N_PREFIX}.includeOnlyPaths`)}
+          value={payload.includes}
+          onChange={handleChange('includes')}
+          placeholder='articles/*'
+        />
+      </div>
+      <CheckboxWithLabel
+        label={t(`${I18N_PREFIX}.extractOnlyMainContent`)}
+        isChecked={payload.only_main_content}
+        onChange={handleChange('only_main_content')}
+      />
+    </div>
+  )
+}
+export default React.memo(Options)

+ 6 - 0
app/components/database/create/website/index.module.css

@@ -0,0 +1,6 @@
+.jinaLogo {
+  @apply w-4 h-4 bg-center bg-no-repeat inline-block;
+  background-color: #F5FAFF;
+  background-image: url(../assets/jina.png);
+  background-size: 16px;
+}

+ 138 - 0
app/components/database/create/website/index.tsx

@@ -0,0 +1,138 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback, useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import s from './index.module.css'
+import NoData from './no-data'
+import Firecrawl from './firecrawl'
+import JinaReader from './jina-reader'
+import cn from '@/utils/classnames'
+import { useModalContext } from '@/context/modal-context'
+import type { CrawlOptions, CrawlResultItem } from '@/models/datasets'
+import { fetchDataSources } from '@/service/datasets'
+import { type DataSourceItem, DataSourceProvider } from '@/models/common'
+
+type Props = {
+  onPreview: (payload: CrawlResultItem) => void
+  checkedCrawlResult: CrawlResultItem[]
+  onCheckedCrawlResultChange: (payload: CrawlResultItem[]) => void
+  onCrawlProviderChange: (provider: DataSourceProvider) => void
+  onJobIdChange: (jobId: string) => void
+  crawlOptions: CrawlOptions
+  onCrawlOptionsChange: (payload: CrawlOptions) => void
+}
+
+const Website: FC<Props> = ({
+  onPreview,
+  checkedCrawlResult,
+  onCheckedCrawlResultChange,
+  onCrawlProviderChange,
+  onJobIdChange,
+  crawlOptions,
+  onCrawlOptionsChange,
+}) => {
+  const { t } = useTranslation()
+  const { setShowAccountSettingModal } = useModalContext()
+  const [isLoaded, setIsLoaded] = useState(false)
+  const [selectedProvider, setSelectedProvider] = useState<DataSourceProvider>(DataSourceProvider.jinaReader)
+  const [sources, setSources] = useState<DataSourceItem[]>([])
+
+  useEffect(() => {
+    onCrawlProviderChange(selectedProvider)
+  }, [selectedProvider, onCrawlProviderChange])
+
+  const checkSetApiKey = useCallback(async () => {
+    const res = await fetchDataSources() as any
+    setSources(res.sources)
+
+    // If users have configured one of the providers, select it.
+    const availableProviders = res.sources.filter((item: DataSourceItem) =>
+      [DataSourceProvider.jinaReader, DataSourceProvider.fireCrawl].includes(item.provider),
+    )
+
+    if (availableProviders.length > 0)
+      setSelectedProvider(availableProviders[0].provider)
+  }, [])
+
+  useEffect(() => {
+    checkSetApiKey().then(() => {
+      setIsLoaded(true)
+    })
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [])
+  const handleOnConfig = useCallback(() => {
+    setShowAccountSettingModal({
+      payload: 'data-source',
+      onCancelCallback: checkSetApiKey,
+    })
+  }, [checkSetApiKey, setShowAccountSettingModal])
+
+  if (!isLoaded)
+    return null
+
+  return (
+    <div>
+      <div className="mb-4">
+        <div className="font-medium text-gray-700 mb-2 h-6">
+          {t('datasetCreation.stepOne.website.chooseProvider')}
+        </div>
+        <div className="flex space-x-2">
+          <button
+            className={`px-4 py-2 text-sm font-medium rounded-md flex items-center justify-center ${
+              selectedProvider === DataSourceProvider.jinaReader
+                ? 'bg-primary-50 text-primary-600'
+                : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
+            }`}
+            onClick={() => setSelectedProvider(DataSourceProvider.jinaReader)}
+          >
+            <span className={cn(s.jinaLogo, 'mr-2')} />
+            <span>Jina Reader</span>
+          </button>
+          <button
+            className={`px-4 py-2 text-sm font-medium rounded-md ${
+              selectedProvider === DataSourceProvider.fireCrawl
+                ? 'bg-primary-50 text-primary-600'
+                : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
+            }`}
+            onClick={() => setSelectedProvider(DataSourceProvider.fireCrawl)}
+          >
+            🔥 Firecrawl
+          </button>
+        </div>
+      </div>
+
+      {
+        selectedProvider === DataSourceProvider.fireCrawl
+          ? sources.find(source => source.provider === DataSourceProvider.fireCrawl)
+            ? (
+              <Firecrawl
+                onPreview={onPreview}
+                checkedCrawlResult={checkedCrawlResult}
+                onCheckedCrawlResultChange={onCheckedCrawlResultChange}
+                onJobIdChange={onJobIdChange}
+                crawlOptions={crawlOptions}
+                onCrawlOptionsChange={onCrawlOptionsChange}
+              />
+            )
+            : (
+              <NoData onConfig={handleOnConfig} provider={selectedProvider} />
+            )
+          : sources.find(source => source.provider === DataSourceProvider.jinaReader)
+            ? (
+              <JinaReader
+                onPreview={onPreview}
+                checkedCrawlResult={checkedCrawlResult}
+                onCheckedCrawlResultChange={onCheckedCrawlResultChange}
+                onJobIdChange={onJobIdChange}
+                crawlOptions={crawlOptions}
+                onCrawlOptionsChange={onCrawlOptionsChange}
+              />
+            )
+            : (
+              <NoData onConfig={handleOnConfig} provider={selectedProvider} />
+            )
+      }
+    </div>
+  )
+}
+export default React.memo(Website)

+ 40 - 0
app/components/database/create/website/jina-reader/base/checkbox-with-label.tsx

@@ -0,0 +1,40 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import cn from '@/utils/classnames'
+import Checkbox from '@/app/components/base/checkbox'
+import Tooltip from '@/app/components/base/tooltip'
+
+type Props = {
+  className?: string
+  isChecked: boolean
+  onChange: (isChecked: boolean) => void
+  label: string
+  labelClassName?: string
+  tooltip?: string
+}
+
+const CheckboxWithLabel: FC<Props> = ({
+  className = '',
+  isChecked,
+  onChange,
+  label,
+  labelClassName,
+  tooltip,
+}) => {
+  return (
+    <label className={cn(className, 'flex items-center h-7 space-x-2')}>
+      <Checkbox checked={isChecked} onCheck={() => onChange(!isChecked)} />
+      <div className={cn(labelClassName, 'text-sm font-normal text-gray-800')}>{label}</div>
+      {tooltip && (
+        <Tooltip
+          popupContent={
+            <div className='w-[200px]'>{tooltip}</div>
+          }
+          triggerClassName='ml-0.5 w-4 h-4'
+        />
+      )}
+    </label>
+  )
+}
+export default React.memo(CheckboxWithLabel)

+ 30 - 0
app/components/database/create/website/jina-reader/base/error-message.tsx

@@ -0,0 +1,30 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import cn from '@/utils/classnames'
+import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
+
+type Props = {
+  className?: string
+  title: string
+  errorMsg?: string
+}
+
+const ErrorMessage: FC<Props> = ({
+  className,
+  title,
+  errorMsg,
+}) => {
+  return (
+    <div className={cn(className, 'py-2 px-4 border-t border-gray-200 bg-[#FFFAEB]')}>
+      <div className='flex items-center h-5'>
+        <AlertTriangle className='mr-2 w-4 h-4 text-[#F79009]' />
+        <div className='text-sm font-medium text-[#DC6803]'>{title}</div>
+      </div>
+      {errorMsg && (
+        <div className='mt-1 pl-6 leading-[18px] text-xs font-normal text-gray-700'>{errorMsg}</div>
+      )}
+    </div>
+  )
+}
+export default React.memo(ErrorMessage)

+ 54 - 0
app/components/database/create/website/jina-reader/base/field.tsx

@@ -0,0 +1,54 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import Input from './input'
+import cn from '@/utils/classnames'
+import Tooltip from '@/app/components/base/tooltip'
+
+type Props = {
+  className?: string
+  label: string
+  labelClassName?: string
+  value: string | number
+  onChange: (value: string | number) => void
+  isRequired?: boolean
+  placeholder?: string
+  isNumber?: boolean
+  tooltip?: string
+}
+
+const Field: FC<Props> = ({
+  className,
+  label,
+  labelClassName,
+  value,
+  onChange,
+  isRequired = false,
+  placeholder = '',
+  isNumber = false,
+  tooltip,
+}) => {
+  return (
+    <div className={cn(className)}>
+      <div className='flex py-[7px]'>
+        <div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-gray-900')}>{label} </div>
+        {isRequired && <span className='ml-0.5 text-xs font-semibold text-[#D92D20]'>*</span>}
+        {tooltip && (
+          <Tooltip
+            popupContent={
+              <div className='w-[200px]'>{tooltip}</div>
+            }
+            triggerClassName='ml-0.5 w-4 h-4'
+          />
+        )}
+      </div>
+      <Input
+        value={value}
+        onChange={onChange}
+        placeholder={placeholder}
+        isNumber={isNumber}
+      />
+    </div>
+  )
+}
+export default React.memo(Field)

+ 58 - 0
app/components/database/create/website/jina-reader/base/input.tsx

@@ -0,0 +1,58 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback } from 'react'
+
+type Props = {
+  value: string | number
+  onChange: (value: string | number) => void
+  placeholder?: string
+  isNumber?: boolean
+}
+
+const MIN_VALUE = 0
+
+const Input: FC<Props> = ({
+  value,
+  onChange,
+  placeholder = '',
+  isNumber = false,
+}) => {
+  const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
+    const value = e.target.value
+    if (isNumber) {
+      let numberValue = parseInt(value, 10) // integer only
+      if (isNaN(numberValue)) {
+        onChange('')
+        return
+      }
+      if (numberValue < MIN_VALUE)
+        numberValue = MIN_VALUE
+
+      onChange(numberValue)
+      return
+    }
+    onChange(value)
+  }, [isNumber, onChange])
+
+  const otherOption = (() => {
+    if (isNumber) {
+      return {
+        min: MIN_VALUE,
+      }
+    }
+    return {
+
+    }
+  })()
+  return (
+    <input
+      type={isNumber ? 'number' : 'text'}
+      {...otherOption}
+      value={value}
+      onChange={handleChange}
+      className='flex h-9 w-full py-1 px-2 rounded-lg text-xs leading-normal bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-gray-50 placeholder:text-gray-400'
+      placeholder={placeholder}
+    />
+  )
+}
+export default React.memo(Input)

+ 55 - 0
app/components/database/create/website/jina-reader/base/options-wrap.tsx

@@ -0,0 +1,55 @@
+'use client'
+import { useBoolean } from 'ahooks'
+import type { FC } from 'react'
+import React, { useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+import cn from '@/utils/classnames'
+import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
+import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  className?: string
+  children: React.ReactNode
+  controlFoldOptions?: number
+}
+
+const OptionsWrap: FC<Props> = ({
+  className = '',
+  children,
+  controlFoldOptions,
+}) => {
+  const { t } = useTranslation()
+
+  const [fold, {
+    toggle: foldToggle,
+    setTrue: foldHide,
+  }] = useBoolean(false)
+
+  useEffect(() => {
+    if (controlFoldOptions)
+      foldHide()
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [controlFoldOptions])
+  return (
+    <div className={cn(className, !fold ? 'mb-0' : 'mb-3')}>
+      <div
+        className='flex justify-between items-center h-[26px] py-1 cursor-pointer select-none'
+        onClick={foldToggle}
+      >
+        <div className='flex items-center text-gray-700'>
+          <Settings04 className='mr-1 w-4 h-4' />
+          <div className='text-[13px] font-semibold text-gray-800 uppercase'>{t(`${I18N_PREFIX}.options`)}</div>
+        </div>
+        <ChevronRight className={cn(!fold && 'rotate-90', 'w-4 h-4 text-gray-500')} />
+      </div>
+      {!fold && (
+        <div className='mb-4'>
+          {children}
+        </div>
+      )}
+
+    </div>
+  )
+}
+export default React.memo(OptionsWrap)

+ 48 - 0
app/components/database/create/website/jina-reader/base/url-input.tsx

@@ -0,0 +1,48 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import Input from './input'
+import Button from '@/app/components/base/button'
+
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  isRunning: boolean
+  onRun: (url: string) => void
+}
+
+const UrlInput: FC<Props> = ({
+  isRunning,
+  onRun,
+}) => {
+  const { t } = useTranslation()
+  const [url, setUrl] = useState('')
+  const handleUrlChange = useCallback((url: string | number) => {
+    setUrl(url as string)
+  }, [])
+  const handleOnRun = useCallback(() => {
+    if (isRunning)
+      return
+    onRun(url)
+  }, [isRunning, onRun, url])
+
+  return (
+    <div className='flex items-center justify-between'>
+      <Input
+        value={url}
+        onChange={handleUrlChange}
+        placeholder='https://docs.dify.ai'
+      />
+      <Button
+        variant='primary'
+        onClick={handleOnRun}
+        className='ml-2'
+        loading={isRunning}
+      >
+        {!isRunning ? t(`${I18N_PREFIX}.run`) : ''}
+      </Button>
+    </div>
+  )
+}
+export default React.memo(UrlInput)

+ 40 - 0
app/components/database/create/website/jina-reader/crawled-result-item.tsx

@@ -0,0 +1,40 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import cn from '@/utils/classnames'
+import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets'
+import Checkbox from '@/app/components/base/checkbox'
+
+type Props = {
+  payload: CrawlResultItemType
+  isChecked: boolean
+  isPreview: boolean
+  onCheckChange: (checked: boolean) => void
+  onPreview: () => void
+}
+
+const CrawledResultItem: FC<Props> = ({
+  isPreview,
+  payload,
+  isChecked,
+  onCheckChange,
+  onPreview,
+}) => {
+  const { t } = useTranslation()
+
+  const handleCheckChange = useCallback(() => {
+    onCheckChange(!isChecked)
+  }, [isChecked, onCheckChange])
+  return (
+    <div className={cn(isPreview ? 'border-[#D1E0FF] bg-primary-50 shadow-xs' : 'group hover:bg-gray-100', 'rounded-md px-2 py-[5px] cursor-pointer border border-transparent')}>
+      <div className='flex items-center h-5'>
+        <Checkbox className='group-hover:border-2 group-hover:border-primary-600 mr-2 shrink-0' checked={isChecked} onCheck={handleCheckChange} />
+        <div className='grow w-0 truncate text-sm font-medium text-gray-700' title={payload.title}>{payload.title}</div>
+        <div onClick={onPreview} className='hidden group-hover:flex items-center h-6 px-2 text-xs rounded-md font-medium text-gray-500 uppercase hover:bg-gray-50'>{t('datasetCreation.stepOne.website.preview')}</div>
+      </div>
+      <div className='mt-0.5 truncate pl-6 leading-[18px] text-xs font-normal text-gray-500' title={payload.source_url}>{payload.source_url}</div>
+    </div>
+  )
+}
+export default React.memo(CrawledResultItem)

+ 87 - 0
app/components/database/create/website/jina-reader/crawled-result.tsx

@@ -0,0 +1,87 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import CheckboxWithLabel from './base/checkbox-with-label'
+import CrawledResultItem from './crawled-result-item'
+import cn from '@/utils/classnames'
+import type { CrawlResultItem } from '@/models/datasets'
+
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  className?: string
+  list: CrawlResultItem[]
+  checkedList: CrawlResultItem[]
+  onSelectedChange: (selected: CrawlResultItem[]) => void
+  onPreview: (payload: CrawlResultItem) => void
+  usedTime: number
+}
+
+const CrawledResult: FC<Props> = ({
+  className = '',
+  list,
+  checkedList,
+  onSelectedChange,
+  onPreview,
+  usedTime,
+}) => {
+  const { t } = useTranslation()
+
+  const isCheckAll = checkedList.length === list.length
+
+  const handleCheckedAll = useCallback(() => {
+    if (!isCheckAll)
+      onSelectedChange(list)
+
+    else
+      onSelectedChange([])
+  }, [isCheckAll, list, onSelectedChange])
+
+  const handleItemCheckChange = useCallback((item: CrawlResultItem) => {
+    return (checked: boolean) => {
+      if (checked)
+        onSelectedChange([...checkedList, item])
+
+      else
+        onSelectedChange(checkedList.filter(checkedItem => checkedItem.source_url !== item.source_url))
+    }
+  }, [checkedList, onSelectedChange])
+
+  const [previewIndex, setPreviewIndex] = React.useState<number>(-1)
+  const handlePreview = useCallback((index: number) => {
+    return () => {
+      setPreviewIndex(index)
+      onPreview(list[index])
+    }
+  }, [list, onPreview])
+
+  return (
+    <div className={cn(className, 'border-t border-gray-200')}>
+      <div className='flex items-center justify-between h-[34px] px-4 bg-gray-50 shadow-xs border-b-[0.5px] border-black/8 text-xs font-normal text-gray-700'>
+        <CheckboxWithLabel
+          isChecked={isCheckAll}
+          onChange={handleCheckedAll} label={isCheckAll ? t(`${I18N_PREFIX}.resetAll`) : t(`${I18N_PREFIX}.selectAll`)}
+          labelClassName='!font-medium'
+        />
+        <div>{t(`${I18N_PREFIX}.scrapTimeInfo`, {
+          total: list.length,
+          time: usedTime.toFixed(1),
+        })}</div>
+      </div>
+      <div className='p-2'>
+        {list.map((item, index) => (
+          <CrawledResultItem
+            key={item.source_url}
+            isPreview={index === previewIndex}
+            onPreview={handlePreview(index)}
+            payload={item}
+            isChecked={checkedList.some(checkedItem => checkedItem.source_url === item.source_url)}
+            onCheckChange={handleItemCheckChange(item)}
+          />
+        ))}
+      </div>
+    </div>
+  )
+}
+export default React.memo(CrawledResult)

+ 37 - 0
app/components/database/create/website/jina-reader/crawling.tsx

@@ -0,0 +1,37 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import cn from '@/utils/classnames'
+import { RowStruct } from '@/app/components/base/icons/src/public/other'
+
+type Props = {
+  className?: string
+  crawledNum: number
+  totalNum: number
+}
+
+const Crawling: FC<Props> = ({
+  className = '',
+  crawledNum,
+  totalNum,
+}) => {
+  const { t } = useTranslation()
+
+  return (
+    <div className={cn(className, 'border-t border-gray-200')}>
+      <div className='flex items-center h-[34px] px-4 bg-gray-50 shadow-xs border-b-[0.5px] border-black/8 text-xs font-normal text-gray-700'>
+        {t('datasetCreation.stepOne.website.totalPageScraped')} {crawledNum}/{totalNum}
+      </div>
+
+      <div className='p-2'>
+        {['', '', '', ''].map((item, index) => (
+          <div className='py-[5px]' key={index}>
+            <RowStruct />
+          </div>
+        ))}
+      </div>
+    </div>
+  )
+}
+export default React.memo(Crawling)

+ 42 - 0
app/components/database/create/website/jina-reader/header.tsx

@@ -0,0 +1,42 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
+import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
+
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  onSetting: () => void
+}
+
+const Header: FC<Props> = ({
+  onSetting,
+}) => {
+  const { t } = useTranslation()
+
+  return (
+    <div className='flex h-6 items-center justify-between'>
+      <div className='flex items-center'>
+        <div className='text-base font-medium text-gray-700'>{t(`${I18N_PREFIX}.jinaReaderTitle`)}</div>
+        <div className='ml-2 mr-1 w-px h-3.5 bg-gray-200'></div>
+        <div
+          className='p-1 rounded-md hover:bg-black/5 cursor-pointer'
+          onClick={onSetting}
+        >
+          <Settings01 className='w-3.5 h-3.5 text-gray-500' />
+        </div>
+      </div>
+      <a
+        href='https://jina.ai/reader'
+        target='_blank' rel='noopener noreferrer'
+        className='flex items-center text-xs text-primary-600'
+      >
+        <BookOpen01 className='mr-1 w-3.5 h-3.5 text-primary-600' />
+        {t(`${I18N_PREFIX}.jinaReaderDoc`)}
+      </a>
+    </div>
+  )
+}
+export default React.memo(Header)

+ 232 - 0
app/components/database/create/website/jina-reader/index.tsx

@@ -0,0 +1,232 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback, useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import UrlInput from '../base/url-input'
+import OptionsWrap from '../base/options-wrap'
+import CrawledResult from '../base/crawled-result'
+import Crawling from '../base/crawling'
+import ErrorMessage from '../base/error-message'
+import Header from './header'
+import Options from './options'
+import cn from '@/utils/classnames'
+import { useModalContext } from '@/context/modal-context'
+import Toast from '@/app/components/base/toast'
+import { checkJinaReaderTaskStatus, createJinaReaderTask } from '@/service/datasets'
+import { sleep } from '@/utils'
+import type { CrawlOptions, CrawlResultItem } from '@/models/datasets'
+
+const ERROR_I18N_PREFIX = 'common.errorMsg'
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  onPreview: (payload: CrawlResultItem) => void
+  checkedCrawlResult: CrawlResultItem[]
+  onCheckedCrawlResultChange: (payload: CrawlResultItem[]) => void
+  onJobIdChange: (jobId: string) => void
+  crawlOptions: CrawlOptions
+  onCrawlOptionsChange: (payload: CrawlOptions) => void
+}
+
+enum Step {
+  init = 'init',
+  running = 'running',
+  finished = 'finished',
+}
+
+const JinaReader: FC<Props> = ({
+  onPreview,
+  checkedCrawlResult,
+  onCheckedCrawlResultChange,
+  onJobIdChange,
+  crawlOptions,
+  onCrawlOptionsChange,
+}) => {
+  const { t } = useTranslation()
+  const [step, setStep] = useState<Step>(Step.init)
+  const [controlFoldOptions, setControlFoldOptions] = useState<number>(0)
+  useEffect(() => {
+    if (step !== Step.init)
+      setControlFoldOptions(Date.now())
+  }, [step])
+  const { setShowAccountSettingModal } = useModalContext()
+  const handleSetting = useCallback(() => {
+    setShowAccountSettingModal({
+      payload: 'data-source',
+    })
+  }, [setShowAccountSettingModal])
+
+  const checkValid = useCallback((url: string) => {
+    let errorMsg = ''
+    if (!url) {
+      errorMsg = t(`${ERROR_I18N_PREFIX}.fieldRequired`, {
+        field: 'url',
+      })
+    }
+
+    if (!errorMsg && !((url.startsWith('http://') || url.startsWith('https://'))))
+      errorMsg = t(`${ERROR_I18N_PREFIX}.urlError`)
+
+    if (!errorMsg && (crawlOptions.limit === null || crawlOptions.limit === undefined || crawlOptions.limit === '')) {
+      errorMsg = t(`${ERROR_I18N_PREFIX}.fieldRequired`, {
+        field: t(`${I18N_PREFIX}.limit`),
+      })
+    }
+
+    return {
+      isValid: !errorMsg,
+      errorMsg,
+    }
+  }, [crawlOptions, t])
+
+  const isInit = step === Step.init
+  const isCrawlFinished = step === Step.finished
+  const isRunning = step === Step.running
+  const [crawlResult, setCrawlResult] = useState<{
+    current: number
+    total: number
+    data: CrawlResultItem[]
+    time_consuming: number | string
+  } | undefined>(undefined)
+  const [crawlErrorMessage, setCrawlErrorMessage] = useState('')
+  const showError = isCrawlFinished && crawlErrorMessage
+
+  const waitForCrawlFinished = useCallback(async (jobId: string) => {
+    try {
+      const res = await checkJinaReaderTaskStatus(jobId) as any
+      console.log('res', res)
+      if (res.status === 'completed') {
+        return {
+          isError: false,
+          data: {
+            ...res,
+            total: Math.min(res.total, parseFloat(crawlOptions.limit as string)),
+          },
+        }
+      }
+      if (res.status === 'failed' || !res.status) {
+        return {
+          isError: true,
+          errorMessage: res.message,
+          data: {
+            data: [],
+          },
+        }
+      }
+      // update the progress
+      setCrawlResult({
+        ...res,
+        total: Math.min(res.total, parseFloat(crawlOptions.limit as string)),
+      })
+      onCheckedCrawlResultChange(res.data || []) // default select the crawl result
+      await sleep(2500)
+      return await waitForCrawlFinished(jobId)
+    }
+    catch (e: any) {
+      const errorBody = await e.json()
+      return {
+        isError: true,
+        errorMessage: errorBody.message,
+        data: {
+          data: [],
+        },
+      }
+    }
+  }, [crawlOptions.limit])
+
+  const handleRun = useCallback(async (url: string) => {
+    const { isValid, errorMsg } = checkValid(url)
+    if (!isValid) {
+      Toast.notify({
+        message: errorMsg!,
+        type: 'error',
+      })
+      return
+    }
+    setStep(Step.running)
+    try {
+      const startTime = Date.now()
+      const res = await createJinaReaderTask({
+        url,
+        options: crawlOptions,
+      }) as any
+
+      if (res.data) {
+        const data = {
+          current: 1,
+          total: 1,
+          data: [{
+            title: res.data.title,
+            markdown: res.data.content,
+            description: res.data.description,
+            source_url: res.data.url,
+          }],
+          time_consuming: (Date.now() - startTime) / 1000,
+        }
+        setCrawlResult(data)
+        onCheckedCrawlResultChange(data.data || [])
+        setCrawlErrorMessage('')
+      }
+      else if (res.job_id) {
+        const jobId = res.job_id
+        onJobIdChange(jobId)
+        const { isError, data, errorMessage } = await waitForCrawlFinished(jobId)
+        if (isError) {
+          setCrawlErrorMessage(errorMessage || t(`${I18N_PREFIX}.unknownError`))
+        }
+        else {
+          setCrawlResult(data)
+          onCheckedCrawlResultChange(data.data || []) // default select the crawl result
+          setCrawlErrorMessage('')
+        }
+      }
+    }
+    catch (e) {
+      setCrawlErrorMessage(t(`${I18N_PREFIX}.unknownError`)!)
+      console.log(e)
+    }
+    finally {
+      setStep(Step.finished)
+    }
+  }, [checkValid, crawlOptions, onJobIdChange, t, waitForCrawlFinished])
+
+  return (
+    <div>
+      <Header onSetting={handleSetting} />
+      <div className={cn('mt-2 p-4 pb-0 rounded-xl border border-gray-200')}>
+        <UrlInput onRun={handleRun} isRunning={isRunning} />
+        <OptionsWrap
+          className={cn('mt-4')}
+          controlFoldOptions={controlFoldOptions}
+        >
+          <Options className='mt-2' payload={crawlOptions} onChange={onCrawlOptionsChange} />
+        </OptionsWrap>
+
+        {!isInit && (
+          <div className='mt-3 relative left-[-16px] w-[calc(100%_+_32px)] rounded-b-xl'>
+            {isRunning
+              && <Crawling
+                className='mt-2'
+                crawledNum={crawlResult?.current || 0}
+                totalNum={crawlResult?.total || parseFloat(crawlOptions.limit as string) || 0}
+              />}
+            {showError && (
+              <ErrorMessage className='rounded-b-xl' title={t(`${I18N_PREFIX}.exceptionErrorTitle`)} errorMsg={crawlErrorMessage} />
+            )}
+            {isCrawlFinished && !showError
+              && <CrawledResult
+                className='mb-2'
+                list={crawlResult?.data || []}
+                checkedList={checkedCrawlResult}
+                onSelectedChange={onCheckedCrawlResultChange}
+                onPreview={onPreview}
+                usedTime={parseFloat(crawlResult?.time_consuming as string) || 0}
+              />
+            }
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}
+export default React.memo(JinaReader)

+ 24 - 0
app/components/database/create/website/jina-reader/mock-crawl-result.ts

@@ -0,0 +1,24 @@
+import type { CrawlResultItem } from '@/models/datasets'
+
+const result: CrawlResultItem[] = [
+  {
+    title: 'Start the frontend Docker container separately',
+    markdown: 'Markdown 1',
+    description: 'Description 1',
+    source_url: 'https://example.com/1',
+  },
+  {
+    title: 'Advanced Tool Integration',
+    markdown: 'Markdown 2',
+    description: 'Description 2',
+    source_url: 'https://example.com/2',
+  },
+  {
+    title: 'Local Source Code Start | English | Dify',
+    markdown: 'Markdown 3',
+    description: 'Description 3',
+    source_url: 'https://example.com/3',
+  },
+]
+
+export default result

+ 59 - 0
app/components/database/create/website/jina-reader/options.tsx

@@ -0,0 +1,59 @@
+'use client'
+import type { FC } from 'react'
+import React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import CheckboxWithLabel from '../base/checkbox-with-label'
+import Field from '../base/field'
+import cn from '@/utils/classnames'
+import type { CrawlOptions } from '@/models/datasets'
+
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  className?: string
+  payload: CrawlOptions
+  onChange: (payload: CrawlOptions) => void
+}
+
+const Options: FC<Props> = ({
+  className = '',
+  payload,
+  onChange,
+}) => {
+  const { t } = useTranslation()
+
+  const handleChange = useCallback((key: keyof CrawlOptions) => {
+    return (value: any) => {
+      onChange({
+        ...payload,
+        [key]: value,
+      })
+    }
+  }, [payload, onChange])
+  return (
+    <div className={cn(className, ' space-y-2')}>
+      <CheckboxWithLabel
+        label={t(`${I18N_PREFIX}.crawlSubPage`)}
+        isChecked={payload.crawl_sub_pages}
+        onChange={handleChange('crawl_sub_pages')}
+      />
+      <CheckboxWithLabel
+        label={t(`${I18N_PREFIX}.useSitemap`)}
+        isChecked={payload.use_sitemap}
+        onChange={handleChange('use_sitemap')}
+        tooltip={t(`${I18N_PREFIX}.useSitemapTooltip`) as string}
+      />
+      <div className='flex justify-between space-x-4'>
+        <Field
+          className='grow shrink-0'
+          label={t(`${I18N_PREFIX}.limit`)}
+          value={payload.limit}
+          onChange={handleChange('limit')}
+          isNumber
+          isRequired
+        />
+      </div>
+    </div>
+  )
+}
+export default React.memo(Options)

+ 57 - 0
app/components/database/create/website/no-data.tsx

@@ -0,0 +1,57 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import s from './index.module.css'
+import { Icon3Dots } from '@/app/components/base/icons/src/vender/line/others'
+import Button from '@/app/components/base/button'
+import { DataSourceProvider } from '@/models/common'
+
+const I18N_PREFIX = 'datasetCreation.stepOne.website'
+
+type Props = {
+  onConfig: () => void
+  provider: DataSourceProvider
+}
+
+const NoData: FC<Props> = ({
+  onConfig,
+  provider,
+}) => {
+  const { t } = useTranslation()
+
+  const providerConfig = {
+    [DataSourceProvider.jinaReader]: {
+      emoji: <span className={s.jinaLogo} />,
+      title: t(`${I18N_PREFIX}.jinaReaderNotConfigured`),
+      description: t(`${I18N_PREFIX}.jinaReaderNotConfiguredDescription`),
+    },
+    [DataSourceProvider.fireCrawl]: {
+      emoji: '🔥',
+      title: t(`${I18N_PREFIX}.fireCrawlNotConfigured`),
+      description: t(`${I18N_PREFIX}.fireCrawlNotConfiguredDescription`),
+    },
+  }
+
+  const currentProvider = providerConfig[provider]
+
+  return (
+    <>
+      <div className='max-w-[640px] p-6 rounded-2xl bg-gray-50 mt-4'>
+        <div className='flex w-11 h-11 items-center justify-center bg-gray-50 rounded-xl border-[0.5px] border-gray-100 shadow-lg'>
+          {currentProvider.emoji}
+        </div>
+        <div className='my-2'>
+          <span className='text-gray-700 font-semibold'>{currentProvider.title}<Icon3Dots className='inline relative -top-3 -left-1.5' /></span>
+          <div className='mt-1 pb-3 text-gray-500 text-[13px] font-normal'>
+            {currentProvider.description}
+          </div>
+        </div>
+        <Button variant='primary' onClick={onConfig}>
+          {t(`${I18N_PREFIX}.configure`)}
+        </Button>
+      </div>
+    </>
+  )
+}
+export default React.memo(NoData)

+ 41 - 0
app/components/database/create/website/preview.tsx

@@ -0,0 +1,41 @@
+'use client'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { XMarkIcon } from '@heroicons/react/20/solid'
+import s from '../file-preview/index.module.css'
+import cn from '@/utils/classnames'
+import type { CrawlResultItem } from '@/models/datasets'
+
+type IProps = {
+  payload: CrawlResultItem
+  hidePreview: () => void
+}
+
+const WebsitePreview = ({
+  payload,
+  hidePreview,
+}: IProps) => {
+  const { t } = useTranslation()
+
+  return (
+    <div className={cn(s.filePreview)}>
+      <div className={cn(s.previewHeader)}>
+        <div className={cn(s.title)}>
+          <span>{t('datasetCreation.stepOne.pagePreview')}</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='leading-5 text-sm font-medium text-gray-900 break-words'>
+          {payload.title}
+        </div>
+        <div className='truncate leading-[18px] text-xs font-normal text-gray-500' title={payload.source_url}>{payload.source_url}</div>
+      </div>
+      <div className={cn(s.previewContent)}>
+        <div className={cn(s.fileContent)}>{payload.markdown}</div>
+      </div>
+    </div>
+  )
+}
+
+export default WebsitePreview

+ 10 - 0
app/components/database/documents/assets/atSign.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2427_26665)">
+<path d="M10.6668 5.33333V8.66666C10.6668 9.19709 10.8776 9.7058 11.2526 10.0809C11.6277 10.4559 12.1364 10.6667 12.6668 10.6667C13.1973 10.6667 13.706 10.4559 14.0811 10.0809C14.4561 9.7058 14.6668 9.19709 14.6668 8.66666V7.99999C14.6667 6.49535 14.1577 5.03498 13.2224 3.85635C12.287 2.67772 10.9805 1.85014 9.51526 1.50819C8.04999 1.16624 6.51213 1.33002 5.15173 1.9729C3.79134 2.61579 2.68843 3.69996 2.02234 5.04914C1.35625 6.39832 1.16615 7.93315 1.48295 9.40407C1.79975 10.875 2.60482 12.1955 3.76726 13.1508C4.92969 14.1062 6.38112 14.6402 7.88555 14.6661C9.38997 14.692 10.8589 14.2082 12.0535 13.2933M10.6668 7.99999C10.6668 9.47275 9.47293 10.6667 8.00017 10.6667C6.52741 10.6667 5.3335 9.47275 5.3335 7.99999C5.3335 6.52723 6.52741 5.33333 8.00017 5.33333C9.47293 5.33333 10.6668 6.52723 10.6668 7.99999Z" stroke="#344054" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+<defs>
+<clipPath id="clip0_2427_26665">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 3 - 0
app/components/database/documents/assets/bezierCurve.svg

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path d="M5.42857 3.5L2.57143 8.5M3 9.5H8.9999M9.42857 8.5L6.57143 3.5M1.8 10.5H2.2C2.48003 10.5 2.62004 10.5 2.727 10.4455C2.82108 10.3976 2.89757 10.3211 2.9455 10.227C3 10.12 3 9.98003 3 9.7V9.3C3 9.01997 3 8.87996 2.9455 8.773C2.89757 8.67892 2.82108 8.60243 2.727 8.5545C2.62004 8.5 2.48003 8.5 2.2 8.5H1.8C1.51997 8.5 1.37996 8.5 1.273 8.5545C1.17892 8.60243 1.10243 8.67892 1.0545 8.773C1 8.87996 1 9.01997 1 9.3V9.7C1 9.98003 1 10.12 1.0545 10.227C1.10243 10.3211 1.17892 10.3976 1.273 10.4455C1.37996 10.5 1.51997 10.5 1.8 10.5ZM9.8 10.5H10.2C10.48 10.5 10.62 10.5 10.727 10.4455C10.8211 10.3976 10.8976 10.3211 10.9455 10.227C11 10.12 11 9.98003 11 9.7V9.3C11 9.01997 11 8.87996 10.9455 8.773C10.8976 8.67892 10.8211 8.60243 10.727 8.5545C10.62 8.5 10.48 8.5 10.2 8.5H9.8C9.51997 8.5 9.37996 8.5 9.273 8.5545C9.17892 8.60243 9.10243 8.67892 9.0545 8.773C9 8.87996 9 9.01997 9 9.3V9.7C9 9.98003 9 10.12 9.0545 10.227C9.10243 10.3211 9.17892 10.3976 9.273 10.4455C9.37996 10.5 9.51997 10.5 9.8 10.5ZM5.8 3.5H6.2C6.48003 3.5 6.62004 3.5 6.727 3.4455C6.82108 3.39757 6.89757 3.32108 6.9455 3.227C7 3.12004 7 2.98003 7 2.7V2.3C7 2.01997 7 1.87996 6.9455 1.773C6.89757 1.67892 6.82108 1.60243 6.727 1.5545C6.62004 1.5 6.48003 1.5 6.2 1.5H5.8C5.51997 1.5 5.37996 1.5 5.273 1.5545C5.17892 1.60243 5.10243 1.67892 5.0545 1.773C5 1.87996 5 2.01997 5 2.3V2.7C5 2.98003 5 3.12004 5.0545 3.227C5.10243 3.32108 5.17892 3.39757 5.273 3.4455C5.37996 3.5 5.51997 3.5 5.8 3.5Z" stroke="#667085" stroke-linecap="round" stroke-linejoin="round" />
+  </svg>

+ 3 - 0
app/components/database/documents/assets/bookOpen.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.00016 14L7.93346 13.8999C7.47037 13.2053 7.23882 12.858 6.9329 12.6065C6.66207 12.3839 6.35001 12.2169 6.01457 12.1151C5.63566 12 5.21823 12 4.38338 12H3.46683C2.72009 12 2.34672 12 2.06151 11.8547C1.81063 11.7268 1.60665 11.5229 1.47882 11.272C1.3335 10.9868 1.3335 10.6134 1.3335 9.86667V4.13333C1.3335 3.3866 1.3335 3.01323 1.47882 2.72801C1.60665 2.47713 1.81063 2.27316 2.06151 2.14532C2.34672 2 2.72009 2 3.46683 2H3.7335C5.22697 2 5.97371 2 6.54414 2.29065C7.0459 2.54631 7.45385 2.95426 7.70951 3.45603C8.00016 4.02646 8.00016 4.77319 8.00016 6.26667M8.00016 14V6.26667M8.00016 14L8.06687 13.8999C8.52996 13.2053 8.76151 12.858 9.06743 12.6065C9.33826 12.3839 9.65032 12.2169 9.98576 12.1151C10.3647 12 10.7821 12 11.6169 12H12.5335C13.2802 12 13.6536 12 13.9388 11.8547C14.1897 11.7268 14.3937 11.5229 14.5215 11.272C14.6668 10.9868 14.6668 10.6134 14.6668 9.86667V4.13333C14.6668 3.3866 14.6668 3.01323 14.5215 2.72801C14.3937 2.47713 14.1897 2.27316 13.9388 2.14532C13.6536 2 13.2802 2 12.5335 2H12.2668C10.7734 2 10.0266 2 9.45619 2.29065C8.95442 2.54631 8.54647 2.95426 8.29081 3.45603C8.00016 4.02646 8.00016 4.77319 8.00016 6.26667" stroke="#344054" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 3 - 0
app/components/database/documents/assets/briefcase.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5.3335 14V4.66667C5.3335 4.04669 5.3335 3.7367 5.40164 3.48236C5.58658 2.79218 6.12567 2.25308 6.81586 2.06815C7.07019 2 7.38018 2 8.00016 2C8.62014 2 8.93013 2 9.18447 2.06815C9.87465 2.25308 10.4137 2.79218 10.5987 3.48236C10.6668 3.7367 10.6668 4.04669 10.6668 4.66667V14M3.46683 14H12.5335C13.2802 14 13.6536 14 13.9388 13.8547C14.1897 13.7268 14.3937 13.5229 14.5215 13.272C14.6668 12.9868 14.6668 12.6134 14.6668 11.8667V6.8C14.6668 6.05326 14.6668 5.6799 14.5215 5.39468C14.3937 5.1438 14.1897 4.93982 13.9388 4.81199C13.6536 4.66667 13.2802 4.66667 12.5335 4.66667H3.46683C2.72009 4.66667 2.34672 4.66667 2.06151 4.81199C1.81063 4.93982 1.60665 5.1438 1.47882 5.39468C1.3335 5.6799 1.3335 6.05326 1.3335 6.8V11.8667C1.3335 12.6134 1.3335 12.9868 1.47882 13.272C1.60665 13.5229 1.81063 13.7268 2.06151 13.8547C2.34672 14 2.72009 14 3.46683 14Z" stroke="#344054" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 15 - 0
app/components/database/documents/assets/cardLoading.svg

@@ -0,0 +1,15 @@
+<svg width="232" height="120" viewBox="0 0 232 120" preserveAspectRatio="none" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect y="6" width="232" height="8" rx="3" fill="#EAECF0"/>
+<rect y="26" width="232" height="8" rx="3" fill="#EAECF0"/>
+<rect y="46" width="232" height="8" rx="3" fill="#EAECF0"/>
+<rect y="66" width="232" height="8" rx="3" fill="#EAECF0"/>
+<rect y="86" width="232" height="8" rx="3" fill="#EAECF0"/>
+<g clip-path="url(#clip0_2368_22256)">
+<rect y="106" width="256" height="8" rx="3" fill="#EAECF0"/>
+</g>
+<defs>
+<clipPath id="clip0_2368_22256">
+<rect width="232" height="20" fill="white" transform="translate(0 100)"/>
+</clipPath>
+</defs>
+</svg>

+ 3 - 0
app/components/database/documents/assets/file.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9.33317 1.51306V4.26676C9.33317 4.64012 9.33317 4.82681 9.40583 4.96942C9.46975 5.09486 9.57173 5.19684 9.69718 5.26076C9.83978 5.33342 10.0265 5.33342 10.3998 5.33342H13.1535M10.6665 8.66671H5.33317M10.6665 11.3334H5.33317M6.6665 6.00004H5.33317M9.33317 1.33337H5.8665C4.7464 1.33337 4.18635 1.33337 3.75852 1.55136C3.3822 1.74311 3.07624 2.04907 2.88449 2.42539C2.6665 2.85322 2.6665 3.41327 2.6665 4.53337V11.4667C2.6665 12.5868 2.6665 13.1469 2.88449 13.5747C3.07624 13.951 3.3822 14.257 3.75852 14.4487C4.18635 14.6667 4.7464 14.6667 5.8665 14.6667H10.1332C11.2533 14.6667 11.8133 14.6667 12.2412 14.4487C12.6175 14.257 12.9234 13.951 13.1152 13.5747C13.3332 13.1469 13.3332 12.5868 13.3332 11.4667V5.33337L9.33317 1.33337Z" stroke="#344054" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 10 - 0
app/components/database/documents/assets/globe.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2427_26661)">
+<path d="M1.79133 10.4301L3.06346 9.69574C3.13237 9.65596 3.21323 9.64214 3.29143 9.65678L5.79443 10.1252C6.00013 10.1637 6.19001 10.0054 6.18908 9.7961L6.17934 7.60301C6.17907 7.54342 6.19478 7.48486 6.22484 7.43341L7.48799 5.27085C7.55373 5.1583 7.54784 5.01776 7.47291 4.91111L5.34611 1.88383M12.667 3.23941C9.0003 5.00007 11.0002 7.3334 11.667 7.66674C12.9184 8.29232 14.6586 8.33337 14.6586 8.33337C14.664 8.22294 14.6668 8.11182 14.6668 8.00004C14.6668 4.31814 11.6821 1.33337 8.00016 1.33337C4.31826 1.33337 1.3335 4.31814 1.3335 8.00004C1.3335 11.6819 4.31826 14.6667 8.00016 14.6667C8.11194 14.6667 8.22307 14.664 8.3335 14.6585M11.172 14.6266L9.06083 9.06071L14.6267 11.1719L12.1586 12.1585L11.172 14.6266Z" stroke="#155EEF" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+<defs>
+<clipPath id="clip0_2427_26661">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

Vissa filer visades inte eftersom för många filer har ändrats