mirror of
https://github.com/langgenius/dify.git
synced 2026-01-19 21:44:07 +00:00
Compare commits
16 Commits
feature/ta
...
feat/summa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5293fbe8ba | ||
|
|
22974ea6b0 | ||
|
|
62c3f14570 | ||
|
|
7b66bbc35a | ||
|
|
77366f33a4 | ||
|
|
e3b0918dd9 | ||
|
|
d97f2df85c | ||
|
|
fde8efa4a2 | ||
|
|
f02adc26e5 | ||
|
|
1126a2aa95 | ||
|
|
7c3ce7b1e6 | ||
|
|
830a7fb034 | ||
|
|
01a7dbcee8 | ||
|
|
4fe8d2491e | ||
|
|
5c2ae922bc | ||
|
|
13eec13a14 |
10
.github/workflows/style.yml
vendored
10
.github/workflows/style.yml
vendored
@@ -106,8 +106,9 @@ jobs:
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
working-directory: ./web
|
||||
run: |
|
||||
pnpm run lint:report
|
||||
continue-on-error: true
|
||||
pnpm run lint:ci
|
||||
# pnpm run lint:report
|
||||
# continue-on-error: true
|
||||
|
||||
# - name: Annotate Code
|
||||
# if: steps.changed-files.outputs.any_changed == 'true' && github.event_name == 'pull_request'
|
||||
@@ -126,11 +127,6 @@ jobs:
|
||||
working-directory: ./web
|
||||
run: pnpm run knip
|
||||
|
||||
- name: Web build check
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
working-directory: ./web
|
||||
run: pnpm run build
|
||||
|
||||
superlinter:
|
||||
name: SuperLinter
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
45
.github/workflows/web-tests.yml
vendored
45
.github/workflows/web-tests.yml
vendored
@@ -366,3 +366,48 @@ jobs:
|
||||
path: web/coverage
|
||||
retention-days: 30
|
||||
if-no-files-found: error
|
||||
|
||||
web-build:
|
||||
name: Web Build
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v47
|
||||
with:
|
||||
files: |
|
||||
web/**
|
||||
.github/workflows/web-tests.yml
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
package_json_file: web/package.json
|
||||
run_install: false
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v6
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
cache-dependency-path: ./web/pnpm-lock.yaml
|
||||
|
||||
- name: Web dependencies
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
working-directory: ./web
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Web build check
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
working-directory: ./web
|
||||
run: pnpm run build
|
||||
|
||||
56
web/__mocks__/zustand.ts
Normal file
56
web/__mocks__/zustand.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type * as ZustandExportedTypes from 'zustand'
|
||||
import { act } from '@testing-library/react'
|
||||
|
||||
export * from 'zustand'
|
||||
|
||||
const { create: actualCreate, createStore: actualCreateStore }
|
||||
// eslint-disable-next-line antfu/no-top-level-await
|
||||
= await vi.importActual<typeof ZustandExportedTypes>('zustand')
|
||||
|
||||
export const storeResetFns = new Set<() => void>()
|
||||
|
||||
const createUncurried = <T>(
|
||||
stateCreator: ZustandExportedTypes.StateCreator<T>,
|
||||
) => {
|
||||
const store = actualCreate(stateCreator)
|
||||
const initialState = store.getInitialState()
|
||||
storeResetFns.add(() => {
|
||||
store.setState(initialState, true)
|
||||
})
|
||||
return store
|
||||
}
|
||||
|
||||
export const create = (<T>(
|
||||
stateCreator: ZustandExportedTypes.StateCreator<T>,
|
||||
) => {
|
||||
return typeof stateCreator === 'function'
|
||||
? createUncurried(stateCreator)
|
||||
: createUncurried
|
||||
}) as typeof ZustandExportedTypes.create
|
||||
|
||||
const createStoreUncurried = <T>(
|
||||
stateCreator: ZustandExportedTypes.StateCreator<T>,
|
||||
) => {
|
||||
const store = actualCreateStore(stateCreator)
|
||||
const initialState = store.getInitialState()
|
||||
storeResetFns.add(() => {
|
||||
store.setState(initialState, true)
|
||||
})
|
||||
return store
|
||||
}
|
||||
|
||||
export const createStore = (<T>(
|
||||
stateCreator: ZustandExportedTypes.StateCreator<T>,
|
||||
) => {
|
||||
return typeof stateCreator === 'function'
|
||||
? createStoreUncurried(stateCreator)
|
||||
: createStoreUncurried
|
||||
}) as typeof ZustandExportedTypes.createStore
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
storeResetFns.forEach((resetFn) => {
|
||||
resetFn()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3,9 +3,7 @@ import type { App } from '@/types/app'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import useAccessControlStore from '@/context/access-control-store'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { AccessMode, SubjectType } from '@/models/access-control'
|
||||
import { defaultSystemFeatures } from '@/types/feature'
|
||||
import Toast from '../../base/toast'
|
||||
import AccessControlDialog from './access-control-dialog'
|
||||
import AccessControlItem from './access-control-item'
|
||||
@@ -105,22 +103,6 @@ const memberSubject: Subject = {
|
||||
accountData: baseMember,
|
||||
} as Subject
|
||||
|
||||
const resetAccessControlStore = () => {
|
||||
useAccessControlStore.setState({
|
||||
appId: '',
|
||||
specificGroups: [],
|
||||
specificMembers: [],
|
||||
currentMenu: AccessMode.SPECIFIC_GROUPS_MEMBERS,
|
||||
selectedGroupsForBreadcrumb: [],
|
||||
})
|
||||
}
|
||||
|
||||
const resetGlobalStore = () => {
|
||||
useGlobalPublicStore.setState({
|
||||
systemFeatures: defaultSystemFeatures,
|
||||
})
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
class MockIntersectionObserver {
|
||||
observe = vi.fn(() => undefined)
|
||||
@@ -132,9 +114,6 @@ beforeAll(() => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
resetAccessControlStore()
|
||||
resetGlobalStore()
|
||||
mockMutateAsync.mockResolvedValue(undefined)
|
||||
mockUseUpdateAccessMode.mockReturnValue({
|
||||
isPending: false,
|
||||
|
||||
@@ -189,6 +189,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
{isFetchingNextPage && <Loading />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -21,15 +21,15 @@ export const AppCardSkeleton = React.memo(({ count = 6 }: AppCardSkeletonProps)
|
||||
>
|
||||
<SkeletonContainer className="h-full">
|
||||
<SkeletonRow>
|
||||
<SkeletonRectangle className="h-10 w-10 rounded-lg" />
|
||||
<SkeletonRectangle className="h-10 w-10 animate-pulse rounded-lg" />
|
||||
<div className="flex flex-1 flex-col gap-1">
|
||||
<SkeletonRectangle className="h-4 w-2/3" />
|
||||
<SkeletonRectangle className="h-3 w-1/3" />
|
||||
<SkeletonRectangle className="h-4 w-2/3 animate-pulse" />
|
||||
<SkeletonRectangle className="h-3 w-1/3 animate-pulse" />
|
||||
</div>
|
||||
</SkeletonRow>
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<SkeletonRectangle className="h-3 w-full" />
|
||||
<SkeletonRectangle className="h-3 w-4/5" />
|
||||
<SkeletonRectangle className="h-3 w-full animate-pulse" />
|
||||
<SkeletonRectangle className="h-3 w-4/5 animate-pulse" />
|
||||
</div>
|
||||
</SkeletonContainer>
|
||||
</div>
|
||||
|
||||
@@ -248,6 +248,9 @@ const List = () => {
|
||||
// No apps - show empty state
|
||||
return <Empty />
|
||||
})()}
|
||||
{isFetchingNextPage && (
|
||||
<AppCardSkeleton count={3} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isCurrentWorkspaceEditor && (
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 7.33337V2.66671H4.00002V13.3334H8.00002C8.36821 13.3334 8.66669 13.6319 8.66669 14C8.66669 14.3682 8.36821 14.6667 8.00002 14.6667H3.33335C2.96516 14.6667 2.66669 14.3682 2.66669 14V2.00004C2.66669 1.63185 2.96516 1.33337 3.33335 1.33337H12.6667C13.0349 1.33337 13.3334 1.63185 13.3334 2.00004V7.33337C13.3334 7.70156 13.0349 8.00004 12.6667 8.00004C12.2985 8.00004 12 7.70156 12 7.33337Z" fill="#354052"/>
|
||||
<path d="M10 4.00004C10.3682 4.00004 10.6667 4.29852 10.6667 4.66671C10.6667 5.0349 10.3682 5.33337 10 5.33337H6.00002C5.63183 5.33337 5.33335 5.0349 5.33335 4.66671C5.33335 4.29852 5.63183 4.00004 6.00002 4.00004H10Z" fill="#354052"/>
|
||||
<path d="M8.00002 6.66671C8.36821 6.66671 8.66669 6.96518 8.66669 7.33337C8.66669 7.70156 8.36821 8.00004 8.00002 8.00004H6.00002C5.63183 8.00004 5.33335 7.70156 5.33335 7.33337C5.33335 6.96518 5.63183 6.66671 6.00002 6.66671H8.00002Z" fill="#354052"/>
|
||||
<path d="M12.827 10.7902L12.3624 9.58224C12.3048 9.43231 12.1607 9.33337 12 9.33337C11.8394 9.33337 11.6953 9.43231 11.6376 9.58224L11.173 10.7902C11.1054 10.9662 10.9662 11.1054 10.7902 11.173L9.58222 11.6376C9.43229 11.6953 9.33335 11.8394 9.33335 12C9.33335 12.1607 9.43229 12.3048 9.58222 12.3624L10.7902 12.827C10.9662 12.8947 11.1054 13.0338 11.173 13.2099L11.6376 14.4178C11.6953 14.5678 11.8394 14.6667 12 14.6667C12.1607 14.6667 12.3048 14.5678 12.3624 14.4178L12.827 13.2099C12.8947 13.0338 13.0338 12.8947 13.2099 12.827L14.4178 12.3624C14.5678 12.3048 14.6667 12.1607 14.6667 12C14.6667 11.8394 14.5678 11.6953 14.4178 11.6376L13.2099 11.173C13.0338 11.1054 12.8947 10.9662 12.827 10.7902Z" fill="#354052"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M12 7.33337V2.66671H4.00002V13.3334H8.00002C8.36821 13.3334 8.66669 13.6319 8.66669 14C8.66669 14.3682 8.36821 14.6667 8.00002 14.6667H3.33335C2.96516 14.6667 2.66669 14.3682 2.66669 14V2.00004C2.66669 1.63185 2.96516 1.33337 3.33335 1.33337H12.6667C13.0349 1.33337 13.3334 1.63185 13.3334 2.00004V7.33337C13.3334 7.70156 13.0349 8.00004 12.6667 8.00004C12.2985 8.00004 12 7.70156 12 7.33337Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M10 4.00004C10.3682 4.00004 10.6667 4.29852 10.6667 4.66671C10.6667 5.0349 10.3682 5.33337 10 5.33337H6.00002C5.63183 5.33337 5.33335 5.0349 5.33335 4.66671C5.33335 4.29852 5.63183 4.00004 6.00002 4.00004H10Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M8.00002 6.66671C8.36821 6.66671 8.66669 6.96518 8.66669 7.33337C8.66669 7.70156 8.36821 8.00004 8.00002 8.00004H6.00002C5.63183 8.00004 5.33335 7.70156 5.33335 7.33337C5.33335 6.96518 5.63183 6.66671 6.00002 6.66671H8.00002Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M12.827 10.7902L12.3624 9.58224C12.3048 9.43231 12.1607 9.33337 12 9.33337C11.8394 9.33337 11.6953 9.43231 11.6376 9.58224L11.173 10.7902C11.1054 10.9662 10.9662 11.1054 10.7902 11.173L9.58222 11.6376C9.43229 11.6953 9.33335 11.8394 9.33335 12C9.33335 12.1607 9.43229 12.3048 9.58222 12.3624L10.7902 12.827C10.9662 12.8947 11.1054 13.0338 11.173 13.2099L11.6376 14.4178C11.6953 14.5678 11.8394 14.6667 12 14.6667C12.1607 14.6667 12.3048 14.5678 12.3624 14.4178L12.827 13.2099C12.8947 13.0338 13.0338 12.8947 13.2099 12.827L14.4178 12.3624C14.5678 12.3048 14.6667 12.1607 14.6667 12C14.6667 11.8394 14.5678 11.6953 14.4178 11.6376L13.2099 11.173C13.0338 11.1054 12.8947 10.9662 12.827 10.7902Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "SearchLinesSparkle"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './SearchLinesSparkle.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'SearchLinesSparkle'
|
||||
|
||||
export default Icon
|
||||
@@ -11,5 +11,6 @@ export { default as HighQuality } from './HighQuality'
|
||||
export { default as HybridSearch } from './HybridSearch'
|
||||
export { default as ParentChildChunk } from './ParentChildChunk'
|
||||
export { default as QuestionAndAnswer } from './QuestionAndAnswer'
|
||||
export { default as SearchLinesSparkle } from './SearchLinesSparkle'
|
||||
export { default as SearchMenu } from './SearchMenu'
|
||||
export { default as VectorSearch } from './VectorSearch'
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { cn } from '@/utils/classnames'
|
||||
import './style.css'
|
||||
|
||||
type ILoadingProps = {
|
||||
type?: 'area' | 'app'
|
||||
className?: string
|
||||
}
|
||||
const Loading = (
|
||||
{ type = 'area' }: ILoadingProps = { type: 'area' },
|
||||
) => {
|
||||
|
||||
const Loading = (props?: ILoadingProps) => {
|
||||
const { type = 'area', className } = props || {}
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex w-full items-center justify-center ${type === 'app' ? 'h-full' : ''}`}
|
||||
className={cn(
|
||||
'flex w-full items-center justify-center',
|
||||
type === 'app' && 'h-full',
|
||||
className,
|
||||
)}
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label={t('loading', { ns: 'appApi' })}
|
||||
@@ -37,4 +41,5 @@ const Loading = (
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Loading
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import type { PreProcessingRule } from '@/models/datasets'
|
||||
import type { PreProcessingRule, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
|
||||
import {
|
||||
RiAlertFill,
|
||||
RiSearchEyeLine,
|
||||
@@ -12,6 +12,7 @@ import Button from '@/app/components/base/button'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import SummaryIndexSetting from '@/app/components/datasets/settings/summary-index-setting'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
import { ChunkingMode } from '@/models/datasets'
|
||||
import SettingCog from '../../assets/setting-gear-mod.svg'
|
||||
@@ -52,6 +53,8 @@ type GeneralChunkingOptionsProps = {
|
||||
onReset: () => void
|
||||
// Locale
|
||||
locale: string
|
||||
summaryIndexSetting?: SummaryIndexSettingType
|
||||
onSummaryIndexSettingChange?: (payload: SummaryIndexSettingType) => void
|
||||
}
|
||||
|
||||
export const GeneralChunkingOptions: FC<GeneralChunkingOptionsProps> = ({
|
||||
@@ -74,6 +77,8 @@ export const GeneralChunkingOptions: FC<GeneralChunkingOptionsProps> = ({
|
||||
onPreview,
|
||||
onReset,
|
||||
locale,
|
||||
summaryIndexSetting,
|
||||
onSummaryIndexSettingChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -146,6 +151,13 @@ export const GeneralChunkingOptions: FC<GeneralChunkingOptionsProps> = ({
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-3">
|
||||
<SummaryIndexSetting
|
||||
entry="create-document"
|
||||
summaryIndexSetting={summaryIndexSetting}
|
||||
onSummaryIndexSettingChange={onSummaryIndexSettingChange}
|
||||
/>
|
||||
</div>
|
||||
{IS_CE_EDITION && (
|
||||
<>
|
||||
<Divider type="horizontal" className="my-4 bg-divider-subtle" />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { FC } from 'react'
|
||||
import type { ParentChildConfig } from '../hooks'
|
||||
import type { ParentMode, PreProcessingRule } from '@/models/datasets'
|
||||
import type { ParentMode, PreProcessingRule, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
|
||||
import { RiSearchEyeLine } from '@remixicon/react'
|
||||
import Image from 'next/image'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -11,6 +11,7 @@ import Checkbox from '@/app/components/base/checkbox'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { ParentChildChunk } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
import RadioCard from '@/app/components/base/radio-card'
|
||||
import SummaryIndexSetting from '@/app/components/datasets/settings/summary-index-setting'
|
||||
import { ChunkingMode } from '@/models/datasets'
|
||||
import FileList from '../../assets/file-list-3-fill.svg'
|
||||
import Note from '../../assets/note-mod.svg'
|
||||
@@ -31,6 +32,8 @@ type ParentChildOptionsProps = {
|
||||
// State
|
||||
parentChildConfig: ParentChildConfig
|
||||
rules: PreProcessingRule[]
|
||||
summaryIndexSetting?: SummaryIndexSettingType
|
||||
onSummaryIndexSettingChange?: (payload: SummaryIndexSettingType) => void
|
||||
currentDocForm: ChunkingMode
|
||||
// Flags
|
||||
isActive: boolean
|
||||
@@ -51,6 +54,7 @@ type ParentChildOptionsProps = {
|
||||
export const ParentChildOptions: FC<ParentChildOptionsProps> = ({
|
||||
parentChildConfig,
|
||||
rules,
|
||||
summaryIndexSetting,
|
||||
currentDocForm: _currentDocForm,
|
||||
isActive,
|
||||
isInUpload,
|
||||
@@ -62,6 +66,7 @@ export const ParentChildOptions: FC<ParentChildOptionsProps> = ({
|
||||
onChildDelimiterChange,
|
||||
onChildMaxLengthChange,
|
||||
onRuleToggle,
|
||||
onSummaryIndexSettingChange,
|
||||
onPreview,
|
||||
onReset,
|
||||
}) => {
|
||||
@@ -183,6 +188,13 @@ export const ParentChildOptions: FC<ParentChildOptionsProps> = ({
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-3">
|
||||
<SummaryIndexSetting
|
||||
entry="create-document"
|
||||
summaryIndexSetting={summaryIndexSetting}
|
||||
onSummaryIndexSettingChange={onSummaryIndexSettingChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ChunkingMode } from '@/models/datasets'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { ChunkContainer, QAPreview } from '../../../chunk'
|
||||
import PreviewDocumentPicker from '../../../common/document-picker/preview-document-picker'
|
||||
import SummaryLabel from '../../../documents/detail/completed/common/summary-label'
|
||||
import { PreviewSlice } from '../../../formatted-text/flavours/preview-slice'
|
||||
import { FormattedText } from '../../../formatted-text/formatted'
|
||||
import PreviewContainer from '../../../preview/container'
|
||||
@@ -99,6 +100,7 @@ export const PreviewPanel: FC<PreviewPanelProps> = ({
|
||||
characterCount={item.content.length}
|
||||
>
|
||||
{item.content}
|
||||
{item.summary && <SummaryLabel summary={item.summary} />}
|
||||
</ChunkContainer>
|
||||
))
|
||||
)}
|
||||
@@ -131,6 +133,7 @@ export const PreviewPanel: FC<PreviewPanelProps> = ({
|
||||
)
|
||||
})}
|
||||
</FormattedText>
|
||||
{item.summary && <SummaryLabel summary={item.summary} />}
|
||||
</ChunkContainer>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
CustomFile,
|
||||
FullDocumentDetail,
|
||||
ProcessRule,
|
||||
SummaryIndexSetting as SummaryIndexSettingType,
|
||||
} from '@/models/datasets'
|
||||
import type { RetrievalConfig, RETRIEVE_METHOD } from '@/types/app'
|
||||
import { useCallback } from 'react'
|
||||
@@ -141,6 +142,7 @@ export const useDocumentCreation = (options: UseDocumentCreationOptions) => {
|
||||
retrievalConfig: RetrievalConfig,
|
||||
embeddingModel: DefaultModel,
|
||||
indexingTechnique: string,
|
||||
summaryIndexSetting?: SummaryIndexSettingType,
|
||||
): CreateDocumentReq | null => {
|
||||
if (isSetting) {
|
||||
return {
|
||||
@@ -148,6 +150,7 @@ export const useDocumentCreation = (options: UseDocumentCreationOptions) => {
|
||||
doc_form: currentDocForm,
|
||||
doc_language: docLanguage,
|
||||
process_rule: processRule,
|
||||
summary_index_setting: summaryIndexSetting,
|
||||
retrieval_model: retrievalConfig,
|
||||
embedding_model: embeddingModel.model,
|
||||
embedding_model_provider: embeddingModel.provider,
|
||||
@@ -164,6 +167,7 @@ export const useDocumentCreation = (options: UseDocumentCreationOptions) => {
|
||||
},
|
||||
indexing_technique: indexingTechnique,
|
||||
process_rule: processRule,
|
||||
summary_index_setting: summaryIndexSetting,
|
||||
doc_form: currentDocForm,
|
||||
doc_language: docLanguage,
|
||||
retrieval_model: retrievalConfig,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ParentMode, PreProcessingRule, ProcessRule, Rules } from '@/models/datasets'
|
||||
import { useCallback, useState } from 'react'
|
||||
import type { ParentMode, PreProcessingRule, ProcessRule, Rules, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { ChunkingMode, ProcessMode } from '@/models/datasets'
|
||||
import escape from './escape'
|
||||
import unescape from './unescape'
|
||||
@@ -39,6 +39,7 @@ export const defaultParentChildConfig: ParentChildConfig = {
|
||||
|
||||
export type UseSegmentationStateOptions = {
|
||||
initialSegmentationType?: ProcessMode
|
||||
initialSummaryIndexSetting?: SummaryIndexSettingType
|
||||
}
|
||||
|
||||
export const useSegmentationState = (options: UseSegmentationStateOptions = {}) => {
|
||||
@@ -58,6 +59,12 @@ export const useSegmentationState = (options: UseSegmentationStateOptions = {})
|
||||
// Pre-processing rules
|
||||
const [rules, setRules] = useState<PreProcessingRule[]>([])
|
||||
const [defaultConfig, setDefaultConfig] = useState<Rules>()
|
||||
const [summaryIndexSetting, setSummaryIndexSetting] = useState<SummaryIndexSettingType | undefined>()
|
||||
const summaryIndexSettingRef = useRef<SummaryIndexSettingType | undefined>(summaryIndexSetting)
|
||||
const handleSummaryIndexSettingChange = useCallback((payload: SummaryIndexSettingType) => {
|
||||
setSummaryIndexSetting({ ...summaryIndexSettingRef.current, ...payload })
|
||||
summaryIndexSettingRef.current = { ...summaryIndexSettingRef.current, ...payload }
|
||||
}, [])
|
||||
|
||||
// Parent-child config
|
||||
const [parentChildConfig, setParentChildConfig] = useState<ParentChildConfig>(defaultParentChildConfig)
|
||||
@@ -134,6 +141,7 @@ export const useSegmentationState = (options: UseSegmentationStateOptions = {})
|
||||
},
|
||||
},
|
||||
mode: 'hierarchical',
|
||||
summary_index_setting: summaryIndexSettingRef.current,
|
||||
} as ProcessRule
|
||||
}
|
||||
|
||||
@@ -147,6 +155,7 @@ export const useSegmentationState = (options: UseSegmentationStateOptions = {})
|
||||
},
|
||||
},
|
||||
mode: segmentationType,
|
||||
summary_index_setting: summaryIndexSettingRef.current,
|
||||
} as ProcessRule
|
||||
}, [rules, parentChildConfig, segmentIdentifier, maxChunkLength, overlap, segmentationType])
|
||||
|
||||
@@ -204,6 +213,8 @@ export const useSegmentationState = (options: UseSegmentationStateOptions = {})
|
||||
defaultConfig,
|
||||
setDefaultConfig,
|
||||
toggleRule,
|
||||
summaryIndexSetting,
|
||||
handleSummaryIndexSettingChange,
|
||||
|
||||
// Parent-child config
|
||||
parentChildConfig,
|
||||
|
||||
@@ -65,6 +65,7 @@ const StepTwo: FC<StepTwoProps> = ({
|
||||
// Custom hooks
|
||||
const segmentation = useSegmentationState({
|
||||
initialSegmentationType: currentDataset?.doc_form === ChunkingMode.parentChild ? ProcessMode.parentChild : ProcessMode.general,
|
||||
initialSummaryIndexSetting: currentDataset?.summary_index_setting,
|
||||
})
|
||||
const indexing = useIndexingConfig({
|
||||
initialIndexType: propsIndexingType,
|
||||
@@ -156,7 +157,7 @@ const StepTwo: FC<StepTwoProps> = ({
|
||||
})
|
||||
if (!isValid)
|
||||
return
|
||||
const params = creation.buildCreationParams(currentDocForm, docLanguage, segmentation.getProcessRule(currentDocForm), indexing.retrievalConfig, indexing.embeddingModel, indexing.getIndexingTechnique())
|
||||
const params = creation.buildCreationParams(currentDocForm, docLanguage, segmentation.getProcessRule(currentDocForm), indexing.retrievalConfig, indexing.embeddingModel, indexing.getIndexingTechnique(), segmentation.summaryIndexSetting)
|
||||
if (!params)
|
||||
return
|
||||
await creation.executeCreation(params, indexing.indexType, indexing.retrievalConfig)
|
||||
@@ -217,6 +218,8 @@ const StepTwo: FC<StepTwoProps> = ({
|
||||
onPreview={updatePreview}
|
||||
onReset={segmentation.resetToDefaults}
|
||||
locale={locale}
|
||||
summaryIndexSetting={segmentation.summaryIndexSetting}
|
||||
onSummaryIndexSettingChange={segmentation.handleSummaryIndexSettingChange}
|
||||
/>
|
||||
)}
|
||||
{showParentChildOption && (
|
||||
@@ -236,6 +239,8 @@ const StepTwo: FC<StepTwoProps> = ({
|
||||
onRuleToggle={segmentation.toggleRule}
|
||||
onPreview={updatePreview}
|
||||
onReset={segmentation.resetToDefaults}
|
||||
summaryIndexSetting={segmentation.summaryIndexSetting}
|
||||
onSummaryIndexSettingChange={segmentation.handleSummaryIndexSettingChange}
|
||||
/>
|
||||
)}
|
||||
<Divider className="my-5" />
|
||||
|
||||
@@ -30,11 +30,12 @@ import { useDatasetDetailContextWithSelector as useDatasetDetailContext } from '
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { ChunkingMode, DataSourceType, DocumentActionType } from '@/models/datasets'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
import { useDocumentArchive, useDocumentBatchRetryIndex, useDocumentDelete, useDocumentDisable, useDocumentEnable } from '@/service/knowledge/use-document'
|
||||
import { useDocumentArchive, useDocumentBatchRetryIndex, useDocumentDelete, useDocumentDisable, useDocumentEnable, useDocumentSummary } from '@/service/knowledge/use-document'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import BatchAction from '../detail/completed/common/batch-action'
|
||||
import SummaryStatus from '../detail/completed/common/summary-status'
|
||||
import StatusItem from '../status-item'
|
||||
import s from '../style.module.css'
|
||||
import Operations from './operations'
|
||||
@@ -218,6 +219,7 @@ const DocumentList: FC<IDocumentListProps> = ({
|
||||
onSelectedIdChange(uniq([...selectedIds, ...localDocs.map(doc => doc.id)]))
|
||||
}, [isAllSelected, localDocs, onSelectedIdChange, selectedIds])
|
||||
const { mutateAsync: archiveDocument } = useDocumentArchive()
|
||||
const { mutateAsync: generateSummary } = useDocumentSummary()
|
||||
const { mutateAsync: enableDocument } = useDocumentEnable()
|
||||
const { mutateAsync: disableDocument } = useDocumentDisable()
|
||||
const { mutateAsync: deleteDocument } = useDocumentDelete()
|
||||
@@ -230,6 +232,9 @@ const DocumentList: FC<IDocumentListProps> = ({
|
||||
case DocumentActionType.archive:
|
||||
opApi = archiveDocument
|
||||
break
|
||||
case DocumentActionType.summary:
|
||||
opApi = generateSummary
|
||||
break
|
||||
case DocumentActionType.enable:
|
||||
opApi = enableDocument
|
||||
break
|
||||
@@ -409,6 +414,13 @@ const DocumentList: FC<IDocumentListProps> = ({
|
||||
>
|
||||
<span className="grow-1 truncate text-sm">{doc.name}</span>
|
||||
</Tooltip>
|
||||
{
|
||||
doc.summary_index_status && (
|
||||
<div className="ml-1 hidden shrink-0 group-hover:flex">
|
||||
<SummaryStatus status={doc.summary_index_status} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="hidden shrink-0 group-hover:ml-auto group-hover:flex">
|
||||
<Tooltip
|
||||
popupContent={t('list.table.rename', { ns: 'datasetDocuments' })}
|
||||
@@ -461,6 +473,7 @@ const DocumentList: FC<IDocumentListProps> = ({
|
||||
className="absolute bottom-16 left-0 z-20"
|
||||
selectedIds={selectedIds}
|
||||
onArchive={handleAction(DocumentActionType.archive)}
|
||||
onBatchSummary={handleAction(DocumentActionType.summary)}
|
||||
onBatchEnable={handleAction(DocumentActionType.enable)}
|
||||
onBatchDisable={handleAction(DocumentActionType.disable)}
|
||||
onBatchDelete={handleAction(DocumentActionType.delete)}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
import CustomPopover from '@/app/components/base/popover'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
useDocumentEnable,
|
||||
useDocumentPause,
|
||||
useDocumentResume,
|
||||
useDocumentSummary,
|
||||
useDocumentUnArchive,
|
||||
useSyncDocument,
|
||||
useSyncWebsite,
|
||||
@@ -82,6 +84,7 @@ const Operations = ({
|
||||
const { mutateAsync: deleteDocument } = useDocumentDelete()
|
||||
const { mutateAsync: syncDocument } = useSyncDocument()
|
||||
const { mutateAsync: syncWebsite } = useSyncWebsite()
|
||||
const { mutateAsync: generateSummary } = useDocumentSummary()
|
||||
const { mutateAsync: pauseDocument } = useDocumentPause()
|
||||
const { mutateAsync: resumeDocument } = useDocumentResume()
|
||||
const isListScene = scene === 'list'
|
||||
@@ -107,6 +110,9 @@ const Operations = ({
|
||||
else
|
||||
opApi = syncWebsite
|
||||
break
|
||||
case 'summary':
|
||||
opApi = generateSummary
|
||||
break
|
||||
case 'pause':
|
||||
opApi = pauseDocument
|
||||
break
|
||||
@@ -220,6 +226,10 @@ const Operations = ({
|
||||
<span className={s.actionName}>{t('list.action.sync', { ns: 'datasetDocuments' })}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={s.actionItem} onClick={() => onOperate('summary')}>
|
||||
<SearchLinesSparkle className="h-4 w-4 text-text-tertiary" />
|
||||
<span className={s.actionName}>{t('list.action.summary', { ns: 'datasetDocuments' })}</span>
|
||||
</div>
|
||||
<Divider className="my-1" />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'batchAction'
|
||||
@@ -15,6 +16,7 @@ type IBatchActionProps = {
|
||||
onBatchEnable: () => void
|
||||
onBatchDisable: () => void
|
||||
onBatchDelete: () => Promise<void>
|
||||
onBatchSummary?: () => void
|
||||
onArchive?: () => void
|
||||
onEditMetadata?: () => void
|
||||
onBatchReIndex?: () => void
|
||||
@@ -26,6 +28,7 @@ const BatchAction: FC<IBatchActionProps> = ({
|
||||
selectedIds,
|
||||
onBatchEnable,
|
||||
onBatchDisable,
|
||||
onBatchSummary,
|
||||
onArchive,
|
||||
onBatchDelete,
|
||||
onEditMetadata,
|
||||
@@ -82,7 +85,16 @@ const BatchAction: FC<IBatchActionProps> = ({
|
||||
<span className="px-0.5">{t('metadata.metadata', { ns: 'dataset' })}</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onBatchSummary && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="gap-x-0.5 px-3"
|
||||
onClick={onBatchSummary}
|
||||
>
|
||||
<SearchLinesSparkle className="size-4" />
|
||||
<span className="px-0.5">{t('list.action.summary', { ns: 'datasetDocuments' })}</span>
|
||||
</Button>
|
||||
)}
|
||||
{onArchive && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type SummaryLabelProps = {
|
||||
summary?: string
|
||||
className?: string
|
||||
}
|
||||
const SummaryLabel = ({
|
||||
summary,
|
||||
className,
|
||||
}: SummaryLabelProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className={cn('space-y-1', className)}>
|
||||
<div className="system-xs-medium-uppercase mt-2 flex items-center justify-between text-text-tertiary">
|
||||
{t('segment.summary', { ns: 'datasetDocuments' })}
|
||||
<div className="ml-2 h-px grow bg-divider-regular"></div>
|
||||
</div>
|
||||
<div className="body-xs-regular text-text-tertiary">{summary}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(SummaryLabel)
|
||||
@@ -0,0 +1,47 @@
|
||||
import { memo, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type SummaryStatusProps = {
|
||||
status: string
|
||||
}
|
||||
|
||||
const SummaryStatus = ({ status }: SummaryStatusProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const tip = useMemo(() => {
|
||||
if (status === 'COMPLETED') {
|
||||
return t('list.summary.ready', { ns: 'datasetDocuments' })
|
||||
}
|
||||
if (status === 'GENERATING') {
|
||||
return t('list.summary.generatingSummary', { ns: 'datasetDocuments' })
|
||||
}
|
||||
return ''
|
||||
}, [status, t])
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={tip}
|
||||
>
|
||||
{
|
||||
status === 'COMPLETED' && (
|
||||
<Badge>
|
||||
<SearchLinesSparkle className="h-3 w-3" />
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
{
|
||||
status === 'GENERATING' && (
|
||||
<Badge className="border-text-accent-secondary text-text-accent-secondary">
|
||||
<SearchLinesSparkle className="mr-0.5 h-3 w-3" />
|
||||
<span>{t('list.summary.generating', { ns: 'datasetDocuments' })}</span>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(SummaryStatus)
|
||||
@@ -0,0 +1,35 @@
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type SummaryTextProps = {
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
const SummaryText = ({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: SummaryTextProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="system-xs-medium-uppercase text-text-tertiary">{t('segment.summary', { ns: 'datasetDocuments' })}</div>
|
||||
<Textarea
|
||||
className={cn(
|
||||
'body-sm-regular w-full resize-none bg-transparent leading-6 text-text-secondary outline-none',
|
||||
)}
|
||||
placeholder={t('segment.summaryPlaceholder', { ns: 'datasetDocuments' })}
|
||||
minRows={1}
|
||||
value={value ?? ''}
|
||||
onChange={e => onChange?.(e.target.value)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(SummaryText)
|
||||
@@ -322,6 +322,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
answer: string,
|
||||
keywords: string[],
|
||||
attachments: FileEntity[],
|
||||
summary?: string,
|
||||
needRegenerate = false,
|
||||
) => {
|
||||
const params: SegmentUpdater = { content: '', attachment_ids: [] }
|
||||
@@ -351,6 +352,9 @@ const Completed: FC<ICompletedProps> = ({
|
||||
params.attachment_ids = attachments.map(item => item.uploadedId!)
|
||||
}
|
||||
|
||||
if (summary)
|
||||
params.summary = summary
|
||||
|
||||
if (needRegenerate)
|
||||
params.regenerate_child_chunks = needRegenerate
|
||||
|
||||
@@ -364,6 +368,7 @@ const Completed: FC<ICompletedProps> = ({
|
||||
if (seg.id === segmentId) {
|
||||
seg.answer = res.data.answer
|
||||
seg.content = res.data.content
|
||||
seg.summary = res.data.summary
|
||||
seg.sign_content = res.data.sign_content
|
||||
seg.keywords = res.data.keywords
|
||||
seg.attachments = res.data.attachments
|
||||
|
||||
@@ -19,13 +19,14 @@ import { useDocumentContext } from '../../context'
|
||||
import ChildSegmentList from '../child-segment-list'
|
||||
import Dot from '../common/dot'
|
||||
import { SegmentIndexTag } from '../common/segment-index-tag'
|
||||
import SummaryLabel from '../common/summary-label'
|
||||
import Tag from '../common/tag'
|
||||
import ParentChunkCardSkeleton from '../skeleton/parent-chunk-card-skeleton'
|
||||
import ChunkContent from './chunk-content'
|
||||
|
||||
type ISegmentCardProps = {
|
||||
loading: boolean
|
||||
detail?: SegmentDetailModel & { document?: { name: string } }
|
||||
detail?: SegmentDetailModel & { document?: { name: string }, status?: string }
|
||||
onClick?: () => void
|
||||
onChangeSwitch?: (enabled: boolean, segId?: string) => Promise<void>
|
||||
onDelete?: (segId: string) => Promise<void>
|
||||
@@ -43,7 +44,7 @@ type ISegmentCardProps = {
|
||||
}
|
||||
|
||||
const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
detail = {},
|
||||
detail = { status: '' },
|
||||
onClick,
|
||||
onChangeSwitch,
|
||||
onDelete,
|
||||
@@ -67,6 +68,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
word_count,
|
||||
hit_count,
|
||||
answer,
|
||||
summary,
|
||||
keywords,
|
||||
child_chunks = [],
|
||||
created_at,
|
||||
@@ -237,6 +239,11 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
className={contentOpacity}
|
||||
/>
|
||||
{images.length > 0 && <ImageList images={images} size="md" className="py-1" />}
|
||||
{
|
||||
summary && (
|
||||
<SummaryLabel summary={summary} className="mt-2" />
|
||||
)
|
||||
}
|
||||
{isGeneralMode && (
|
||||
<div className={cn('flex flex-wrap items-center gap-2 py-1.5', contentOpacity)}>
|
||||
{keywords?.map(keyword => <Tag key={keyword} text={keyword} />)}
|
||||
|
||||
@@ -25,6 +25,7 @@ import Dot from './common/dot'
|
||||
import Keywords from './common/keywords'
|
||||
import RegenerationModal from './common/regeneration-modal'
|
||||
import { SegmentIndexTag } from './common/segment-index-tag'
|
||||
import SummaryText from './common/summary-text'
|
||||
import { useSegmentListContext } from './index'
|
||||
|
||||
type ISegmentDetailProps = {
|
||||
@@ -35,6 +36,7 @@ type ISegmentDetailProps = {
|
||||
a: string,
|
||||
k: string[],
|
||||
attachments: FileEntity[],
|
||||
summary?: string,
|
||||
needRegenerate?: boolean,
|
||||
) => void
|
||||
onCancel: () => void
|
||||
@@ -57,6 +59,7 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const [question, setQuestion] = useState(isEditMode ? segInfo?.content || '' : segInfo?.sign_content || '')
|
||||
const [answer, setAnswer] = useState(segInfo?.answer || '')
|
||||
const [summary, setSummary] = useState(segInfo?.summary || '')
|
||||
const [attachments, setAttachments] = useState<FileEntity[]>(() => {
|
||||
return segInfo?.attachments?.map(item => ({
|
||||
id: uuid4(),
|
||||
@@ -91,8 +94,8 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
|
||||
}, [onCancel])
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
onUpdate(segInfo?.id || '', question, answer, keywords, attachments)
|
||||
}, [onUpdate, segInfo?.id, question, answer, keywords, attachments])
|
||||
onUpdate(segInfo?.id || '', question, answer, keywords, attachments, summary, false)
|
||||
}, [onUpdate, segInfo?.id, question, answer, keywords, attachments, summary])
|
||||
|
||||
const handleRegeneration = useCallback(() => {
|
||||
setShowRegenerationModal(true)
|
||||
@@ -111,8 +114,8 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
|
||||
}, [onCancel, onModalStateChange])
|
||||
|
||||
const onConfirmRegeneration = useCallback(() => {
|
||||
onUpdate(segInfo?.id || '', question, answer, keywords, attachments, true)
|
||||
}, [onUpdate, segInfo?.id, question, answer, keywords, attachments])
|
||||
onUpdate(segInfo?.id || '', question, answer, keywords, attachments, summary, true)
|
||||
}, [onUpdate, segInfo?.id, question, answer, keywords, attachments, summary])
|
||||
|
||||
const onAttachmentsChange = useCallback((attachments: FileEntity[]) => {
|
||||
setAttachments(attachments)
|
||||
@@ -197,6 +200,11 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
|
||||
value={attachments}
|
||||
onChange={onAttachmentsChange}
|
||||
/>
|
||||
<SummaryText
|
||||
value={summary}
|
||||
onChange={summary => setSummary(summary)}
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
{isECOIndexing && (
|
||||
<Keywords
|
||||
className="w-full"
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive' | 'pause' | 'resume'
|
||||
export type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive' | 'pause' | 'resume' | 'summary'
|
||||
|
||||
@@ -12,6 +12,7 @@ import { cn } from '@/utils/classnames'
|
||||
import ImageList from '../../common/image-list'
|
||||
import Dot from '../../documents/detail/completed/common/dot'
|
||||
import { SegmentIndexTag } from '../../documents/detail/completed/common/segment-index-tag'
|
||||
import SummaryText from '../../documents/detail/completed/common/summary-text'
|
||||
import ChildChunksItem from './child-chunks-item'
|
||||
import Mask from './mask'
|
||||
import Score from './score'
|
||||
@@ -28,7 +29,7 @@ const ChunkDetailModal = ({
|
||||
onHide,
|
||||
}: ChunkDetailModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { segment, score, child_chunks, files } = payload
|
||||
const { segment, score, child_chunks, files, summary } = payload
|
||||
const { position, content, sign_content, keywords, document, answer } = segment
|
||||
const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0)
|
||||
const extension = document.name.split('.').slice(-1)[0] as FileAppearanceTypeEnum
|
||||
@@ -104,11 +105,14 @@ const ChunkDetailModal = ({
|
||||
{/* Mask */}
|
||||
<Mask className="absolute inset-x-0 bottom-0" />
|
||||
</div>
|
||||
{(showImages || showKeywords) && (
|
||||
{(showImages || showKeywords || !!summary) && (
|
||||
<div className="flex flex-col gap-y-3 pt-3">
|
||||
{showImages && (
|
||||
<ImageList images={images} size="md" className="py-1" />
|
||||
)}
|
||||
{!!summary && (
|
||||
<SummaryText value={summary} disabled />
|
||||
)}
|
||||
{showKeywords && (
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<div className="text-xs font-medium uppercase text-text-tertiary">{t(`${i18nPrefix}keyword`, { ns: 'datasetHitTesting' })}</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import SummaryLabel from '@/app/components/datasets/documents/detail/completed/common/summary-label'
|
||||
import Tag from '@/app/components/datasets/documents/detail/completed/common/tag'
|
||||
import { extensionToFileType } from '@/app/components/datasets/hit-testing/utils/extension-to-file-type'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@@ -25,7 +26,7 @@ const ResultItem = ({
|
||||
payload,
|
||||
}: ResultItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { segment, score, child_chunks, files } = payload
|
||||
const { segment, score, child_chunks, files, summary } = payload
|
||||
const data = segment
|
||||
const { position, word_count, content, sign_content, keywords, document } = data
|
||||
const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0)
|
||||
@@ -98,6 +99,9 @@ const ResultItem = ({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{summary && (
|
||||
<SummaryLabel summary={summary} className="mt-2" />
|
||||
)}
|
||||
</div>
|
||||
{/* Foot */}
|
||||
<ResultItemFooter docType={fileType} docTitle={document.name} showDetailModal={showDetailModal} />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { useDatasetList, useInvalidDatasetList } from '@/service/knowledge/use-dataset'
|
||||
import DatasetCard from './dataset-card'
|
||||
@@ -25,6 +26,7 @@ const Datasets = ({
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
} = useDatasetList({
|
||||
initialPage: 1,
|
||||
tag_ids: tags,
|
||||
@@ -60,6 +62,7 @@ const Datasets = ({
|
||||
{datasetList?.pages.map(({ data: datasets }) => datasets.map(dataset => (
|
||||
<DatasetCard key={dataset.id} dataset={dataset} onSuccess={invalidDatasetList} />),
|
||||
))}
|
||||
{isFetchingNextPage && <Loading />}
|
||||
<div ref={anchorRef} className="h-0" />
|
||||
</nav>
|
||||
</>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
|
||||
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { Member } from '@/models/common'
|
||||
import type { IconInfo } from '@/models/datasets'
|
||||
import type { IconInfo, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
|
||||
import type { AppIconType, RetrievalConfig } from '@/types/app'
|
||||
import { RiAlertFill } from '@remixicon/react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
@@ -33,6 +33,7 @@ import RetrievalSettings from '../../external-knowledge-base/create/RetrievalSet
|
||||
import ChunkStructure from '../chunk-structure'
|
||||
import IndexMethod from '../index-method'
|
||||
import PermissionSelector from '../permission-selector'
|
||||
import SummaryIndexSetting from '../summary-index-setting'
|
||||
import { checkShowMultiModalTip } from '../utils'
|
||||
|
||||
const rowClass = 'flex gap-x-1'
|
||||
@@ -76,6 +77,12 @@ const Form = () => {
|
||||
model: '',
|
||||
},
|
||||
)
|
||||
const [summaryIndexSetting, setSummaryIndexSetting] = useState(currentDataset?.summary_index_setting)
|
||||
const summaryIndexSettingRef = useRef(currentDataset?.summary_index_setting)
|
||||
const handleSummaryIndexSettingChange = useCallback((payload: SummaryIndexSettingType) => {
|
||||
setSummaryIndexSetting({ ...summaryIndexSettingRef.current, ...payload })
|
||||
summaryIndexSettingRef.current = { ...summaryIndexSettingRef.current, ...payload }
|
||||
}, [])
|
||||
const { data: rerankModelList } = useModelList(ModelTypeEnum.rerank)
|
||||
const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
|
||||
const { data: membersData } = useMembers()
|
||||
@@ -167,6 +174,7 @@ const Form = () => {
|
||||
},
|
||||
}),
|
||||
keyword_number: keywordNumber,
|
||||
summary_index_setting: summaryIndexSetting,
|
||||
},
|
||||
} as any
|
||||
if (permission === DatasetPermission.partialMembers) {
|
||||
@@ -348,6 +356,23 @@ const Form = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
indexMethod === IndexingType.QUALIFIED
|
||||
&& [ChunkingMode.text, ChunkingMode.parentChild].includes(currentDataset?.doc_form as ChunkingMode)
|
||||
&& (
|
||||
<>
|
||||
<Divider
|
||||
type="horizontal"
|
||||
className="my-1 h-px bg-divider-subtle"
|
||||
/>
|
||||
<SummaryIndexSetting
|
||||
entry="dataset-settings"
|
||||
summaryIndexSetting={summaryIndexSetting}
|
||||
onSummaryIndexSettingChange={handleSummaryIndexSettingChange}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{/* Retrieval Method Config */}
|
||||
{currentDataset?.provider === 'external'
|
||||
? (
|
||||
|
||||
228
web/app/components/datasets/settings/summary-index-setting.tsx
Normal file
228
web/app/components/datasets/settings/summary-index-setting.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import type { ChangeEvent } from 'react'
|
||||
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
|
||||
type SummaryIndexSettingProps = {
|
||||
entry?: 'knowledge-base' | 'dataset-settings' | 'create-document'
|
||||
summaryIndexSetting?: SummaryIndexSettingType
|
||||
onSummaryIndexSettingChange?: (payload: SummaryIndexSettingType) => void
|
||||
readonly?: boolean
|
||||
}
|
||||
const SummaryIndexSetting = ({
|
||||
entry = 'knowledge-base',
|
||||
summaryIndexSetting,
|
||||
onSummaryIndexSettingChange,
|
||||
readonly = false,
|
||||
}: SummaryIndexSettingProps) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
data: textGenerationModelList,
|
||||
} = useModelList(ModelTypeEnum.textGeneration)
|
||||
const summaryIndexModelConfig = useMemo(() => {
|
||||
if (!summaryIndexSetting?.model_name || !summaryIndexSetting?.model_provider_name)
|
||||
return undefined
|
||||
|
||||
return {
|
||||
providerName: summaryIndexSetting?.model_provider_name,
|
||||
modelName: summaryIndexSetting?.model_name,
|
||||
}
|
||||
}, [summaryIndexSetting?.model_name, summaryIndexSetting?.model_provider_name])
|
||||
|
||||
const handleSummaryIndexEnableChange = useCallback((value: boolean) => {
|
||||
onSummaryIndexSettingChange?.({
|
||||
enable: value,
|
||||
})
|
||||
}, [onSummaryIndexSettingChange])
|
||||
|
||||
const handleSummaryIndexModelChange = useCallback((model: DefaultModel) => {
|
||||
onSummaryIndexSettingChange?.({
|
||||
model_provider_name: model.provider,
|
||||
model_name: model.model,
|
||||
})
|
||||
}, [onSummaryIndexSettingChange])
|
||||
|
||||
const handleSummaryIndexPromptChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
onSummaryIndexSettingChange?.({
|
||||
summary_prompt: e.target.value,
|
||||
})
|
||||
}, [onSummaryIndexSettingChange])
|
||||
|
||||
if (entry === 'knowledge-base') {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex h-6 items-center justify-between">
|
||||
<div className="system-sm-semibold-uppercase flex items-center text-text-secondary">
|
||||
{t('form.summaryAutoGen', { ns: 'datasetSettings' })}
|
||||
<Tooltip
|
||||
triggerClassName="ml-1 h-4 w-4 shrink-0"
|
||||
popupContent={t('form.summaryAutoGenTip', { ns: 'datasetSettings' })}
|
||||
>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Switch
|
||||
defaultValue={summaryIndexSetting?.enable ?? false}
|
||||
onChange={handleSummaryIndexEnableChange}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
summaryIndexSetting?.enable && (
|
||||
<div>
|
||||
<div className="system-xs-medium-uppercase mb-1.5 mt-2 flex h-6 items-center text-text-tertiary">
|
||||
{t('form.summaryModel', { ns: 'datasetSettings' })}
|
||||
</div>
|
||||
<ModelSelector
|
||||
defaultModel={summaryIndexModelConfig && { provider: summaryIndexModelConfig.providerName, model: summaryIndexModelConfig.modelName }}
|
||||
modelList={textGenerationModelList}
|
||||
onSelect={handleSummaryIndexModelChange}
|
||||
readonly={readonly}
|
||||
showDeprecatedWarnIcon
|
||||
/>
|
||||
<div className="system-xs-medium-uppercase mt-3 flex h-6 items-center text-text-tertiary">
|
||||
{t('form.summaryInstructions', { ns: 'datasetSettings' })}
|
||||
</div>
|
||||
<Textarea
|
||||
value={summaryIndexSetting?.summary_prompt ?? ''}
|
||||
onChange={handleSummaryIndexPromptChange}
|
||||
disabled={readonly}
|
||||
placeholder={t('form.summaryInstructionsPlaceholder', { ns: 'datasetSettings' })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (entry === 'dataset-settings') {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-x-1">
|
||||
<div className="flex h-7 w-[180px] shrink-0 items-center pt-1">
|
||||
<div className="system-sm-semibold text-text-secondary">
|
||||
{t('form.summaryAutoGen', { ns: 'datasetSettings' })}
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-1.5">
|
||||
<div className="system-sm-semibold flex items-center text-text-secondary">
|
||||
<Switch
|
||||
className="mr-2"
|
||||
defaultValue={summaryIndexSetting?.enable ?? false}
|
||||
onChange={handleSummaryIndexEnableChange}
|
||||
size="md"
|
||||
/>
|
||||
{
|
||||
summaryIndexSetting?.enable ? t('list.status.enabled', { ns: 'datasetDocuments' }) : t('list.status.disabled', { ns: 'datasetDocuments' })
|
||||
}
|
||||
</div>
|
||||
<div className="system-sm-regular mt-2 text-text-tertiary">
|
||||
{
|
||||
summaryIndexSetting?.enable && t('form.summaryAutoGenTip', { ns: 'datasetSettings' })
|
||||
}
|
||||
{
|
||||
!summaryIndexSetting?.enable && t('form.summaryAutoGenEnableTip', { ns: 'datasetSettings' })
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
summaryIndexSetting?.enable && (
|
||||
<>
|
||||
<div className="flex gap-x-1">
|
||||
<div className="flex h-7 w-[180px] shrink-0 items-center pt-1">
|
||||
<div className="system-sm-medium text-text-tertiary">
|
||||
{t('form.summaryModel', { ns: 'datasetSettings' })}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<ModelSelector
|
||||
defaultModel={summaryIndexModelConfig && { provider: summaryIndexModelConfig.providerName, model: summaryIndexModelConfig.modelName }}
|
||||
modelList={textGenerationModelList}
|
||||
onSelect={handleSummaryIndexModelChange}
|
||||
readonly={readonly}
|
||||
showDeprecatedWarnIcon
|
||||
triggerClassName="h-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="flex h-7 w-[180px] shrink-0 items-center pt-1">
|
||||
<div className="system-sm-medium text-text-tertiary">
|
||||
{t('form.summaryInstructions', { ns: 'datasetSettings' })}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<Textarea
|
||||
value={summaryIndexSetting?.summary_prompt ?? ''}
|
||||
onChange={handleSummaryIndexPromptChange}
|
||||
disabled={readonly}
|
||||
placeholder={t('form.summaryInstructionsPlaceholder', { ns: 'datasetSettings' })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex h-6 items-center">
|
||||
<Switch
|
||||
className="mr-2"
|
||||
defaultValue={summaryIndexSetting?.enable ?? false}
|
||||
onChange={handleSummaryIndexEnableChange}
|
||||
size="md"
|
||||
/>
|
||||
<div className="system-sm-semibold text-text-secondary">
|
||||
{t('form.summaryAutoGen', { ns: 'datasetSettings' })}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
summaryIndexSetting?.enable && (
|
||||
<>
|
||||
<div>
|
||||
<div className="system-sm-medium mb-1.5 flex h-6 items-center text-text-secondary">
|
||||
{t('form.summaryModel', { ns: 'datasetSettings' })}
|
||||
</div>
|
||||
<ModelSelector
|
||||
defaultModel={summaryIndexModelConfig && { provider: summaryIndexModelConfig.providerName, model: summaryIndexModelConfig.modelName }}
|
||||
modelList={textGenerationModelList}
|
||||
onSelect={handleSummaryIndexModelChange}
|
||||
readonly={readonly}
|
||||
showDeprecatedWarnIcon
|
||||
triggerClassName="h-8"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="system-sm-medium mb-1.5 flex h-6 items-center text-text-secondary">
|
||||
{t('form.summaryInstructions', { ns: 'datasetSettings' })}
|
||||
</div>
|
||||
<Textarea
|
||||
value={summaryIndexSetting?.summary_prompt ?? ''}
|
||||
onChange={handleSummaryIndexPromptChange}
|
||||
disabled={readonly}
|
||||
placeholder={t('form.summaryInstructionsPlaceholder', { ns: 'datasetSettings' })}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default memo(SummaryIndexSetting)
|
||||
@@ -33,6 +33,7 @@ const AppNav = () => {
|
||||
data: appsData,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
refetch,
|
||||
} = useInfiniteAppList({
|
||||
page: 1,
|
||||
@@ -111,6 +112,7 @@ const AppNav = () => {
|
||||
createText={t('menus.newApp', { ns: 'common' })}
|
||||
onCreate={openModal}
|
||||
onLoadMore={handleLoadMore}
|
||||
isLoadingMore={isFetchingNextPage}
|
||||
/>
|
||||
<CreateAppModal
|
||||
show={showNewAppDialog}
|
||||
|
||||
@@ -23,6 +23,7 @@ const DatasetNav = () => {
|
||||
data: datasetList,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
} = useDatasetList({
|
||||
initialPage: 1,
|
||||
limit: 30,
|
||||
@@ -93,6 +94,7 @@ const DatasetNav = () => {
|
||||
createText={t('menus.newDataset', { ns: 'common' })}
|
||||
onCreate={() => router.push(createRoute)}
|
||||
onLoadMore={handleLoadMore}
|
||||
isLoadingMore={isFetchingNextPage}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ const Nav = ({
|
||||
createText,
|
||||
onCreate,
|
||||
onLoadMore,
|
||||
isLoadingMore,
|
||||
isApp,
|
||||
}: INavProps) => {
|
||||
const setAppDetail = useAppStore(state => state.setAppDetail)
|
||||
@@ -81,6 +82,7 @@ const Nav = ({
|
||||
createText={createText}
|
||||
onCreate={onCreate}
|
||||
onLoadMore={onLoadMore}
|
||||
isLoadingMore={isLoadingMore}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { AppTypeIcon } from '@/app/components/app/type-selector'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
@@ -34,9 +35,10 @@ export type INavSelectorProps = {
|
||||
isApp?: boolean
|
||||
onCreate: (state: string) => void
|
||||
onLoadMore?: () => void
|
||||
isLoadingMore?: boolean
|
||||
}
|
||||
|
||||
const NavSelector = ({ curNav, navigationItems, createText, isApp, onCreate, onLoadMore }: INavSelectorProps) => {
|
||||
const NavSelector = ({ curNav, navigationItems, createText, isApp, onCreate, onLoadMore, isLoadingMore }: INavSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||
@@ -106,6 +108,11 @@ const NavSelector = ({ curNav, navigationItems, createText, isApp, onCreate, onL
|
||||
</MenuItem>
|
||||
))
|
||||
}
|
||||
{isLoadingMore && (
|
||||
<div className="flex justify-center py-2">
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isApp && isCurrentWorkspaceEditor && (
|
||||
<MenuItem as="div" className="w-full p-1">
|
||||
|
||||
@@ -19,6 +19,7 @@ const ListWrapper = ({
|
||||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
isLoading,
|
||||
isFetchingNextPage,
|
||||
page,
|
||||
} = useMarketplaceData()
|
||||
|
||||
@@ -53,6 +54,11 @@ const ListWrapper = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
isFetchingNextPage && (
|
||||
<Loading className="my-3" />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function useMarketplaceData() {
|
||||
}, [isSearchMode, searchPluginText, activePluginType, filterPluginTags, sort])
|
||||
|
||||
const pluginsQuery = useMarketplacePlugins(queryParams)
|
||||
const { hasNextPage, fetchNextPage, isFetching } = pluginsQuery
|
||||
const { hasNextPage, fetchNextPage, isFetching, isFetchingNextPage } = pluginsQuery
|
||||
|
||||
const handlePageChange = useCallback(() => {
|
||||
if (hasNextPage && !isFetching)
|
||||
@@ -50,5 +50,6 @@ export function useMarketplaceData() {
|
||||
pluginsTotal: pluginsQuery.data?.pages[0]?.total,
|
||||
page: pluginsQuery.data?.pages.length || 1,
|
||||
isLoading: collectionsQuery.isLoading || pluginsQuery.isLoading,
|
||||
isFetchingNextPage,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,17 +144,6 @@ describe('constant.ts - Type Definitions', () => {
|
||||
|
||||
// ==================== store.ts Tests ====================
|
||||
describe('store.ts - Zustand Store', () => {
|
||||
beforeEach(() => {
|
||||
// Reset store to initial state
|
||||
const { setState } = useStore
|
||||
setState({
|
||||
tagList: [],
|
||||
categoryList: [],
|
||||
showTagManagementModal: false,
|
||||
showCategoryManagementModal: false,
|
||||
})
|
||||
})
|
||||
|
||||
describe('Initial State', () => {
|
||||
it('should have empty tagList initially', () => {
|
||||
const { result } = renderHook(() => useStore(state => state.tagList))
|
||||
|
||||
@@ -5,11 +5,11 @@ import { useDebounceFn } from 'ahooks'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { renderI18nObject } from '@/i18n-config'
|
||||
import { useInstalledLatestVersion, useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import Loading from '../../base/loading'
|
||||
import { PluginSource } from '../types'
|
||||
import { usePluginPageContext } from './context'
|
||||
import Empty from './empty'
|
||||
@@ -107,12 +107,17 @@ const PluginsPanel = () => {
|
||||
<div className="w-full">
|
||||
<List pluginList={filteredList || []} />
|
||||
</div>
|
||||
{!isLastPage && !isFetching && (
|
||||
<Button onClick={loadNextPage}>
|
||||
{t('common.loadMore', { ns: 'workflow' })}
|
||||
</Button>
|
||||
{!isLastPage && (
|
||||
<div className="flex justify-center py-4">
|
||||
{isFetching
|
||||
? <Loading className="size-8" />
|
||||
: (
|
||||
<Button onClick={loadNextPage}>
|
||||
{t('common.loadMore', { ns: 'workflow' })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isFetching && <div className="system-md-semibold text-text-secondary">{t('detail.loading', { ns: 'appLog' })}</div>}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
|
||||
@@ -134,13 +134,6 @@ describe('BUILTIN_TOOLS_ARRAY', () => {
|
||||
// Store Tests
|
||||
// ================================
|
||||
describe('useReadmePanelStore', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Reset store state before each test
|
||||
const { setCurrentPluginDetail } = useReadmePanelStore.getState()
|
||||
setCurrentPluginDetail()
|
||||
})
|
||||
|
||||
describe('Initial State', () => {
|
||||
it('should have undefined currentPluginDetail initially', () => {
|
||||
const { currentPluginDetail } = useReadmePanelStore.getState()
|
||||
@@ -228,13 +221,6 @@ describe('useReadmePanelStore', () => {
|
||||
// ReadmeEntrance Component Tests
|
||||
// ================================
|
||||
describe('ReadmeEntrance', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Reset store state
|
||||
const { setCurrentPluginDetail } = useReadmePanelStore.getState()
|
||||
setCurrentPluginDetail()
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Rendering Tests
|
||||
// ================================
|
||||
@@ -417,11 +403,6 @@ describe('ReadmeEntrance', () => {
|
||||
// ================================
|
||||
describe('ReadmePanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Reset store state
|
||||
const { setCurrentPluginDetail } = useReadmePanelStore.getState()
|
||||
setCurrentPluginDetail()
|
||||
// Reset mock
|
||||
mockUsePluginReadme.mockReturnValue({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { QAChunk } from './types'
|
||||
import type { GeneralChunk, ParentChildChunk, QAChunk } from './types'
|
||||
import type { ParentMode } from '@/models/datasets'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Dot from '@/app/components/datasets/documents/detail/completed/common/dot'
|
||||
import SegmentIndexTag from '@/app/components/datasets/documents/detail/completed/common/segment-index-tag'
|
||||
import SummaryLabel from '@/app/components/datasets/documents/detail/completed/common/summary-label'
|
||||
import { PreviewSlice } from '@/app/components/datasets/formatted-text/flavours/preview-slice'
|
||||
import { ChunkingMode } from '@/models/datasets'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
@@ -14,7 +15,7 @@ import { QAItemType } from './types'
|
||||
type ChunkCardProps = {
|
||||
chunkType: ChunkingMode
|
||||
parentMode?: ParentMode
|
||||
content: string | string[] | QAChunk
|
||||
content: ParentChildChunk | QAChunk | GeneralChunk
|
||||
positionId?: string | number
|
||||
wordCount: number
|
||||
}
|
||||
@@ -33,7 +34,7 @@ const ChunkCard = (props: ChunkCardProps) => {
|
||||
|
||||
const contentElement = useMemo(() => {
|
||||
if (chunkType === ChunkingMode.parentChild) {
|
||||
return (content as string[]).map((child, index) => {
|
||||
return (content as ParentChildChunk).child_contents.map((child, index) => {
|
||||
const indexForLabel = index + 1
|
||||
return (
|
||||
<PreviewSlice
|
||||
@@ -57,7 +58,17 @@ const ChunkCard = (props: ChunkCardProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
return content as string
|
||||
return (content as GeneralChunk).content
|
||||
}, [content, chunkType])
|
||||
|
||||
const summaryElement = useMemo(() => {
|
||||
if (chunkType === ChunkingMode.parentChild) {
|
||||
return (content as ParentChildChunk).parent_summary
|
||||
}
|
||||
if (chunkType === ChunkingMode.text) {
|
||||
return (content as GeneralChunk).summary
|
||||
}
|
||||
return null
|
||||
}, [content, chunkType])
|
||||
|
||||
return (
|
||||
@@ -73,6 +84,7 @@ const ChunkCard = (props: ChunkCardProps) => {
|
||||
</div>
|
||||
)}
|
||||
<div className="body-md-regular text-text-secondary">{contentElement}</div>
|
||||
{summaryElement && <SummaryLabel summary={summaryElement} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import { QAItemType } from './types'
|
||||
// Test Data Factories
|
||||
// =============================================================================
|
||||
|
||||
const createGeneralChunks = (overrides: string[] = []): GeneralChunks => {
|
||||
const createGeneralChunks = (overrides: GeneralChunks = []): GeneralChunks => {
|
||||
if (overrides.length > 0)
|
||||
return overrides
|
||||
return [
|
||||
'This is the first chunk of text content.',
|
||||
'This is the second chunk with different content.',
|
||||
'Third chunk here with more text.',
|
||||
{ content: 'This is the first chunk of text content.' },
|
||||
{ content: 'This is the second chunk with different content.' },
|
||||
{ content: 'Third chunk here with more text.' },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ describe('ChunkCard', () => {
|
||||
render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="This is plain text content."
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={27}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -196,7 +196,7 @@ describe('ChunkCard', () => {
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.parentChild}
|
||||
parentMode="paragraph"
|
||||
content={childContents}
|
||||
content={createParentChildChunk({ child_contents: childContents })}
|
||||
wordCount={50}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -218,7 +218,7 @@ describe('ChunkCard', () => {
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.parentChild}
|
||||
parentMode="paragraph"
|
||||
content={['Child content']}
|
||||
content={createParentChildChunk({ child_contents: ['Child content'] })}
|
||||
wordCount={13}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -234,7 +234,7 @@ describe('ChunkCard', () => {
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.parentChild}
|
||||
parentMode="full-doc"
|
||||
content={['Child content']}
|
||||
content={createParentChildChunk({ child_contents: ['Child content'] })}
|
||||
wordCount={13}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -250,7 +250,7 @@ describe('ChunkCard', () => {
|
||||
render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="Text content"
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={12}
|
||||
positionId={5}
|
||||
/>,
|
||||
@@ -268,7 +268,7 @@ describe('ChunkCard', () => {
|
||||
render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="Some content"
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={1234}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -283,7 +283,7 @@ describe('ChunkCard', () => {
|
||||
render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="Some content"
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={100}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -299,7 +299,7 @@ describe('ChunkCard', () => {
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.parentChild}
|
||||
parentMode="full-doc"
|
||||
content={['Child']}
|
||||
content={createParentChildChunk({ child_contents: ['Child'] })}
|
||||
wordCount={500}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -317,7 +317,7 @@ describe('ChunkCard', () => {
|
||||
render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="Content"
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={7}
|
||||
positionId={42}
|
||||
/>,
|
||||
@@ -332,7 +332,7 @@ describe('ChunkCard', () => {
|
||||
render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="Content"
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={7}
|
||||
positionId="99"
|
||||
/>,
|
||||
@@ -347,7 +347,7 @@ describe('ChunkCard', () => {
|
||||
render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="Content"
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={7}
|
||||
positionId={3}
|
||||
/>,
|
||||
@@ -366,7 +366,7 @@ describe('ChunkCard', () => {
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.parentChild}
|
||||
parentMode="paragraph"
|
||||
content={['Child']}
|
||||
content={createParentChildChunk({ child_contents: ['Child'] })}
|
||||
wordCount={5}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -380,7 +380,7 @@ describe('ChunkCard', () => {
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.parentChild}
|
||||
parentMode="full-doc"
|
||||
content={['Child']}
|
||||
content={createParentChildChunk({ child_contents: ['Child'] })}
|
||||
wordCount={5}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -395,7 +395,7 @@ describe('ChunkCard', () => {
|
||||
const { rerender } = render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="Initial content"
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={15}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -408,7 +408,7 @@ describe('ChunkCard', () => {
|
||||
rerender(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="Updated content"
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={15}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -424,7 +424,7 @@ describe('ChunkCard', () => {
|
||||
const { rerender } = render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content="Text content"
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={12}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -458,7 +458,7 @@ describe('ChunkCard', () => {
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.parentChild}
|
||||
parentMode="paragraph"
|
||||
content={[]}
|
||||
content={createParentChildChunk({ child_contents: [] })}
|
||||
wordCount={0}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -495,7 +495,7 @@ describe('ChunkCard', () => {
|
||||
render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content={longContent}
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={10000}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -510,7 +510,7 @@ describe('ChunkCard', () => {
|
||||
render(
|
||||
<ChunkCard
|
||||
chunkType={ChunkingMode.text}
|
||||
content=""
|
||||
content={createGeneralChunks()[0]}
|
||||
wordCount={0}
|
||||
positionId={1}
|
||||
/>,
|
||||
@@ -546,9 +546,9 @@ describe('ChunkCardList', () => {
|
||||
)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText(chunks[0])).toBeInTheDocument()
|
||||
expect(screen.getByText(chunks[1])).toBeInTheDocument()
|
||||
expect(screen.getByText(chunks[2])).toBeInTheDocument()
|
||||
expect(screen.getByText(chunks[0].content)).toBeInTheDocument()
|
||||
expect(screen.getByText(chunks[1].content)).toBeInTheDocument()
|
||||
expect(screen.getByText(chunks[2].content)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render parent-child chunks correctly', () => {
|
||||
@@ -594,7 +594,10 @@ describe('ChunkCardList', () => {
|
||||
describe('Memoization - chunkList', () => {
|
||||
it('should extract chunks from GeneralChunks for text mode', () => {
|
||||
// Arrange
|
||||
const chunks: GeneralChunks = ['Chunk 1', 'Chunk 2']
|
||||
const chunks: GeneralChunks = [
|
||||
{ content: 'Chunk 1' },
|
||||
{ content: 'Chunk 2' },
|
||||
]
|
||||
|
||||
// Act
|
||||
render(
|
||||
@@ -653,7 +656,7 @@ describe('ChunkCardList', () => {
|
||||
|
||||
it('should update chunkList when chunkInfo changes', () => {
|
||||
// Arrange
|
||||
const initialChunks = createGeneralChunks(['Initial chunk'])
|
||||
const initialChunks = createGeneralChunks([{ content: 'Initial chunk' }])
|
||||
|
||||
const { rerender } = render(
|
||||
<ChunkCardList
|
||||
@@ -666,7 +669,7 @@ describe('ChunkCardList', () => {
|
||||
expect(screen.getByText('Initial chunk')).toBeInTheDocument()
|
||||
|
||||
// Act - update chunks
|
||||
const updatedChunks = createGeneralChunks(['Updated chunk'])
|
||||
const updatedChunks = createGeneralChunks([{ content: 'Updated chunk' }])
|
||||
rerender(
|
||||
<ChunkCardList
|
||||
chunkType={ChunkingMode.text}
|
||||
@@ -684,7 +687,7 @@ describe('ChunkCardList', () => {
|
||||
describe('Word Count Calculation', () => {
|
||||
it('should calculate word count for text chunks using string length', () => {
|
||||
// Arrange - "Hello" has 5 characters
|
||||
const chunks = createGeneralChunks(['Hello'])
|
||||
const chunks = createGeneralChunks([{ content: 'Hello' }])
|
||||
|
||||
// Act
|
||||
render(
|
||||
@@ -747,7 +750,11 @@ describe('ChunkCardList', () => {
|
||||
describe('Position ID', () => {
|
||||
it('should assign 1-based position IDs to chunks', () => {
|
||||
// Arrange
|
||||
const chunks = createGeneralChunks(['First', 'Second', 'Third'])
|
||||
const chunks = createGeneralChunks([
|
||||
{ content: 'First' },
|
||||
{ content: 'Second' },
|
||||
{ content: 'Third' },
|
||||
])
|
||||
|
||||
// Act
|
||||
render(
|
||||
@@ -768,7 +775,7 @@ describe('ChunkCardList', () => {
|
||||
describe('Custom className', () => {
|
||||
it('should apply custom className to container', () => {
|
||||
// Arrange
|
||||
const chunks = createGeneralChunks(['Test'])
|
||||
const chunks = createGeneralChunks([{ content: 'Test' }])
|
||||
|
||||
// Act
|
||||
const { container } = render(
|
||||
@@ -785,7 +792,7 @@ describe('ChunkCardList', () => {
|
||||
|
||||
it('should merge custom className with default classes', () => {
|
||||
// Arrange
|
||||
const chunks = createGeneralChunks(['Test'])
|
||||
const chunks = createGeneralChunks([{ content: 'Test' }])
|
||||
|
||||
// Act
|
||||
const { container } = render(
|
||||
@@ -805,7 +812,7 @@ describe('ChunkCardList', () => {
|
||||
|
||||
it('should render without className prop', () => {
|
||||
// Arrange
|
||||
const chunks = createGeneralChunks(['Test'])
|
||||
const chunks = createGeneralChunks([{ content: 'Test' }])
|
||||
|
||||
// Act
|
||||
const { container } = render(
|
||||
@@ -860,7 +867,7 @@ describe('ChunkCardList', () => {
|
||||
|
||||
it('should not use parentMode for text type', () => {
|
||||
// Arrange
|
||||
const chunks = createGeneralChunks(['Text'])
|
||||
const chunks = createGeneralChunks([{ content: 'Text' }])
|
||||
|
||||
// Act
|
||||
render(
|
||||
@@ -937,7 +944,7 @@ describe('ChunkCardList', () => {
|
||||
|
||||
it('should handle single item in chunks', () => {
|
||||
// Arrange
|
||||
const chunks = createGeneralChunks(['Single chunk'])
|
||||
const chunks = createGeneralChunks([{ content: 'Single chunk' }])
|
||||
|
||||
// Act
|
||||
render(
|
||||
@@ -954,7 +961,7 @@ describe('ChunkCardList', () => {
|
||||
|
||||
it('should handle large number of chunks', () => {
|
||||
// Arrange
|
||||
const chunks = Array.from({ length: 100 }, (_, i) => `Chunk number ${i + 1}`)
|
||||
const chunks = Array.from({ length: 100 }, (_, i) => ({ content: `Chunk number ${i + 1}` }))
|
||||
|
||||
// Act
|
||||
render(
|
||||
@@ -975,8 +982,11 @@ describe('ChunkCardList', () => {
|
||||
describe('Key Generation', () => {
|
||||
it('should generate unique keys for chunks', () => {
|
||||
// Arrange - chunks with same content
|
||||
const chunks = createGeneralChunks(['Same content', 'Same content', 'Same content'])
|
||||
|
||||
const chunks = createGeneralChunks([
|
||||
{ content: 'Same content' },
|
||||
{ content: 'Same content' },
|
||||
{ content: 'Same content' },
|
||||
])
|
||||
// Act
|
||||
const { container } = render(
|
||||
<ChunkCardList
|
||||
@@ -1006,9 +1016,9 @@ describe('ChunkCardList Integration', () => {
|
||||
it('should render complete text chunking workflow', () => {
|
||||
// Arrange
|
||||
const textChunks = createGeneralChunks([
|
||||
'First paragraph of the document.',
|
||||
'Second paragraph with more information.',
|
||||
'Final paragraph concluding the content.',
|
||||
{ content: 'First paragraph of the document.' },
|
||||
{ content: 'Second paragraph with more information.' },
|
||||
{ content: 'Final paragraph concluding the content.' },
|
||||
])
|
||||
|
||||
// Act
|
||||
@@ -1104,7 +1114,7 @@ describe('ChunkCardList Integration', () => {
|
||||
describe('Type Switching', () => {
|
||||
it('should handle switching from text to QA type', () => {
|
||||
// Arrange
|
||||
const textChunks = createGeneralChunks(['Text content'])
|
||||
const textChunks = createGeneralChunks([{ content: 'Text content' }])
|
||||
const qaChunks = createQAChunks()
|
||||
|
||||
const { rerender } = render(
|
||||
@@ -1132,7 +1142,7 @@ describe('ChunkCardList Integration', () => {
|
||||
|
||||
it('should handle switching from text to parent-child type', () => {
|
||||
// Arrange
|
||||
const textChunks = createGeneralChunks(['Simple text'])
|
||||
const textChunks = createGeneralChunks([{ content: 'Simple text' }])
|
||||
const parentChildChunks = createParentChildChunks()
|
||||
|
||||
const { rerender } = render(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChunkInfo, GeneralChunks, ParentChildChunk, ParentChildChunks, QAChunk, QAChunks } from './types'
|
||||
import type { ChunkInfo, GeneralChunk, GeneralChunks, ParentChildChunk, ParentChildChunks, QAChunk, QAChunks } from './types'
|
||||
import type { ParentMode } from '@/models/datasets'
|
||||
import { useMemo } from 'react'
|
||||
import { ChunkingMode } from '@/models/datasets'
|
||||
@@ -21,13 +21,13 @@ export const ChunkCardList = (props: ChunkCardListProps) => {
|
||||
if (chunkType === ChunkingMode.parentChild)
|
||||
return (chunkInfo as ParentChildChunks).parent_child_chunks
|
||||
return (chunkInfo as QAChunks).qa_chunks
|
||||
}, [chunkInfo])
|
||||
}, [chunkInfo, chunkType])
|
||||
|
||||
const getWordCount = (seg: string | ParentChildChunk | QAChunk) => {
|
||||
const getWordCount = (seg: GeneralChunk | ParentChildChunk | QAChunk) => {
|
||||
if (chunkType === ChunkingMode.parentChild)
|
||||
return (seg as ParentChildChunk).parent_content.length
|
||||
return (seg as ParentChildChunk).parent_content?.length
|
||||
if (chunkType === ChunkingMode.text)
|
||||
return (seg as string).length
|
||||
return (seg as GeneralChunk).content.length
|
||||
return (seg as QAChunk).question.length + (seg as QAChunk).answer.length
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export const ChunkCardList = (props: ChunkCardListProps) => {
|
||||
key={`${chunkType}-${index}`}
|
||||
chunkType={chunkType}
|
||||
parentMode={parentMode}
|
||||
content={chunkType === ChunkingMode.parentChild ? (seg as ParentChildChunk).child_contents : (seg as string | QAChunk)}
|
||||
content={seg}
|
||||
wordCount={wordCount}
|
||||
positionId={index + 1}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
export type GeneralChunks = string[]
|
||||
|
||||
export type GeneralChunk = {
|
||||
content: string
|
||||
summary?: string
|
||||
}
|
||||
export type GeneralChunks = GeneralChunk[]
|
||||
export type ParentChildChunk = {
|
||||
child_contents: string[]
|
||||
parent_content: string
|
||||
parent_summary?: string
|
||||
parent_mode: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { GeneralChunks } from '@/app/components/rag-pipeline/components/chunk-card-list/types'
|
||||
import type { WorkflowRunningData } from '@/app/components/workflow/types'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import { ChunkingMode } from '@/models/datasets'
|
||||
|
||||
import Header from './header'
|
||||
// Import components after mocks
|
||||
import TestRunPanel from './index'
|
||||
@@ -836,7 +836,7 @@ describe('formatPreviewChunks', () => {
|
||||
it('should limit to RAG_PIPELINE_PREVIEW_CHUNK_NUM chunks', () => {
|
||||
const manyChunks = Array.from({ length: 10 }, (_, i) => `chunk${i}`)
|
||||
const outputs = createMockGeneralOutputs(manyChunks)
|
||||
const result = formatPreviewChunks(outputs) as string[]
|
||||
const result = formatPreviewChunks(outputs) as GeneralChunks
|
||||
|
||||
// RAG_PIPELINE_PREVIEW_CHUNK_NUM is mocked to 5
|
||||
expect(result).toHaveLength(5)
|
||||
|
||||
@@ -5,13 +5,17 @@ import { ChunkingMode } from '@/models/datasets'
|
||||
|
||||
type GeneralChunkPreview = {
|
||||
content: string
|
||||
summary?: string
|
||||
}
|
||||
|
||||
const formatGeneralChunks = (outputs: any) => {
|
||||
const chunkInfo: GeneralChunks = []
|
||||
const chunks = outputs.preview as GeneralChunkPreview[]
|
||||
chunks.slice(0, RAG_PIPELINE_PREVIEW_CHUNK_NUM).forEach((chunk) => {
|
||||
chunkInfo.push(chunk.content)
|
||||
chunkInfo.push({
|
||||
content: chunk.content,
|
||||
summary: chunk.summary,
|
||||
})
|
||||
})
|
||||
|
||||
return chunkInfo
|
||||
@@ -20,6 +24,7 @@ const formatGeneralChunks = (outputs: any) => {
|
||||
type ParentChildChunkPreview = {
|
||||
content: string
|
||||
child_chunks: string[]
|
||||
summary?: string
|
||||
}
|
||||
|
||||
const formatParentChildChunks = (outputs: any, parentMode: ParentMode) => {
|
||||
@@ -32,6 +37,7 @@ const formatParentChildChunks = (outputs: any, parentMode: ParentMode) => {
|
||||
chunks.slice(0, RAG_PIPELINE_PREVIEW_CHUNK_NUM).forEach((chunk) => {
|
||||
chunkInfo.parent_child_chunks?.push({
|
||||
parent_content: chunk.content,
|
||||
parent_summary: chunk.summary,
|
||||
child_contents: chunk.child_chunks,
|
||||
parent_mode: parentMode,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
KnowledgeBaseNodeType,
|
||||
RerankingModel,
|
||||
SummaryIndexSetting,
|
||||
} from '../types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import { produce } from 'immer'
|
||||
@@ -246,6 +247,16 @@ export const useConfig = (id: string) => {
|
||||
})
|
||||
}, [handleNodeDataUpdate])
|
||||
|
||||
const handleSummaryIndexSettingChange = useCallback((summaryIndexSetting: SummaryIndexSetting) => {
|
||||
const nodeData = getNodeData()
|
||||
handleNodeDataUpdate({
|
||||
summary_index_setting: {
|
||||
...nodeData?.data.summary_index_setting,
|
||||
...summaryIndexSetting,
|
||||
},
|
||||
})
|
||||
}, [handleNodeDataUpdate, getNodeData])
|
||||
|
||||
return {
|
||||
handleChunkStructureChange,
|
||||
handleIndexMethodChange,
|
||||
@@ -260,5 +271,6 @@ export const useConfig = (id: string) => {
|
||||
handleScoreThresholdChange,
|
||||
handleScoreThresholdEnabledChange,
|
||||
handleInputVariableChange,
|
||||
handleSummaryIndexSettingChange,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SummaryIndexSetting from '@/app/components/datasets/settings/summary-index-setting'
|
||||
import { checkShowMultiModalTip } from '@/app/components/datasets/settings/utils'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
@@ -51,6 +52,7 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
|
||||
handleScoreThresholdChange,
|
||||
handleScoreThresholdEnabledChange,
|
||||
handleInputVariableChange,
|
||||
handleSummaryIndexSettingChange,
|
||||
} = useConfig(id)
|
||||
|
||||
const filterVar = useCallback((variable: Var) => {
|
||||
@@ -167,6 +169,22 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
|
||||
<div className="pt-1">
|
||||
<Split className="h-[1px]" />
|
||||
</div>
|
||||
{
|
||||
data.indexing_technique === IndexMethodEnum.QUALIFIED
|
||||
&& [ChunkStructureEnum.general, ChunkStructureEnum.parent_child].includes(data.chunk_structure)
|
||||
&& (
|
||||
<>
|
||||
<SummaryIndexSetting
|
||||
summaryIndexSetting={data.summary_index_setting}
|
||||
onSummaryIndexSettingChange={handleSummaryIndexSettingChange}
|
||||
readonly={nodesReadOnly}
|
||||
/>
|
||||
<div className="pt-1">
|
||||
<Split className="h-[1px]" />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<RetrievalSetting
|
||||
indexMethod={data.indexing_technique}
|
||||
searchMethod={data.retrieval_model.search_method}
|
||||
|
||||
@@ -42,6 +42,12 @@ export type RetrievalSetting = {
|
||||
score_threshold: number
|
||||
reranking_mode?: RerankingModeEnum
|
||||
}
|
||||
export type SummaryIndexSetting = {
|
||||
enable?: boolean
|
||||
model_name?: string
|
||||
model_provider_name?: string
|
||||
summary_prompt?: string
|
||||
}
|
||||
export type KnowledgeBaseNodeType = CommonNodeType & {
|
||||
index_chunk_variable_selector: string[]
|
||||
chunk_structure?: ChunkStructureEnum
|
||||
@@ -52,4 +58,5 @@ export type KnowledgeBaseNodeType = CommonNodeType & {
|
||||
retrieval_model: RetrievalSetting
|
||||
_embeddingModelList?: Model[]
|
||||
_rerankModelList?: Model[]
|
||||
summary_index_setting?: SummaryIndexSetting
|
||||
}
|
||||
|
||||
5107
web/eslint-suppressions.json
Normal file
5107
web/eslint-suppressions.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,30 +9,15 @@ import difyI18n from './eslint-rules/index.js'
|
||||
export default antfu(
|
||||
{
|
||||
react: {
|
||||
reactCompiler: true,
|
||||
overrides: {
|
||||
'react/no-context-provider': 'off',
|
||||
'react/no-forward-ref': 'off',
|
||||
'react/no-use-context': 'off',
|
||||
'react/prefer-namespace-import': 'error',
|
||||
|
||||
// React Compiler rules
|
||||
// Set to warn for gradual adoption
|
||||
'react-hooks/config': 'warn',
|
||||
'react-hooks/error-boundaries': 'warn',
|
||||
'react-hooks/component-hook-factories': 'warn',
|
||||
'react-hooks/gating': 'warn',
|
||||
'react-hooks/globals': 'warn',
|
||||
'react-hooks/immutability': 'warn',
|
||||
'react-hooks/preserve-manual-memoization': 'warn',
|
||||
'react-hooks/purity': 'warn',
|
||||
'react-hooks/refs': 'warn',
|
||||
// prefer react-hooks-extra/no-direct-set-state-in-use-effect
|
||||
'react-hooks/set-state-in-effect': 'off',
|
||||
'react-hooks/set-state-in-render': 'warn',
|
||||
'react-hooks/static-components': 'warn',
|
||||
'react-hooks/unsupported-syntax': 'warn',
|
||||
'react-hooks/use-memo': 'warn',
|
||||
'react-hooks/incompatible-library': 'warn',
|
||||
'react-hooks-extra/no-direct-set-state-in-use-effect': 'error',
|
||||
},
|
||||
},
|
||||
nextjs: true,
|
||||
@@ -40,7 +25,7 @@ export default antfu(
|
||||
typescript: {
|
||||
overrides: {
|
||||
'ts/consistent-type-definitions': ['error', 'type'],
|
||||
'ts/no-explicit-any': 'warn',
|
||||
'ts/no-explicit-any': 'error',
|
||||
},
|
||||
},
|
||||
test: {
|
||||
@@ -54,6 +39,11 @@ export default antfu(
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'node/prefer-global/process': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
settings: {
|
||||
@@ -62,32 +52,6 @@ export default antfu(
|
||||
},
|
||||
},
|
||||
},
|
||||
// downgrade some rules from error to warn for gradual adoption
|
||||
// we should fix these in following pull requests
|
||||
{
|
||||
// @keep-sorted
|
||||
rules: {
|
||||
'next/inline-script-id': 'warn',
|
||||
'no-console': 'warn',
|
||||
'no-irregular-whitespace': 'warn',
|
||||
'node/prefer-global/buffer': 'warn',
|
||||
'node/prefer-global/process': 'warn',
|
||||
'react/no-create-ref': 'warn',
|
||||
'react/no-missing-key': 'warn',
|
||||
'react/no-nested-component-definitions': 'warn',
|
||||
'regexp/no-dupe-disjunctions': 'warn',
|
||||
'regexp/no-super-linear-backtracking': 'warn',
|
||||
'regexp/no-unused-capturing-group': 'warn',
|
||||
'regexp/no-useless-assertions': 'warn',
|
||||
'regexp/no-useless-quantifier': 'warn',
|
||||
'style/multiline-ternary': 'warn',
|
||||
'test/no-identical-title': 'warn',
|
||||
'test/prefer-hooks-in-order': 'warn',
|
||||
'ts/no-empty-object-type': 'warn',
|
||||
'unicorn/prefer-number-properties': 'warn',
|
||||
'unused-imports/no-unused-vars': 'warn',
|
||||
},
|
||||
},
|
||||
storybook.configs['flat/recommended'],
|
||||
...pluginQuery.configs['flat/recommended'],
|
||||
// sonar
|
||||
@@ -178,19 +142,19 @@ export default antfu(
|
||||
},
|
||||
},
|
||||
// dify i18n namespace migration
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
ignores: ['eslint-rules/**', 'i18n/**', 'i18n-config/**'],
|
||||
plugins: {
|
||||
'dify-i18n': difyI18n,
|
||||
},
|
||||
rules: {
|
||||
// 'dify-i18n/no-as-any-in-t': ['error', { mode: 'all' }],
|
||||
'dify-i18n/no-as-any-in-t': 'error',
|
||||
// 'dify-i18n/no-legacy-namespace-prefix': 'error',
|
||||
// 'dify-i18n/require-ns-option': 'error',
|
||||
},
|
||||
},
|
||||
// {
|
||||
// files: ['**/*.ts', '**/*.tsx'],
|
||||
// ignores: ['eslint-rules/**', 'i18n/**', 'i18n-config/**'],
|
||||
// plugins: {
|
||||
// 'dify-i18n': difyI18n,
|
||||
// },
|
||||
// rules: {
|
||||
// // 'dify-i18n/no-as-any-in-t': ['error', { mode: 'all' }],
|
||||
// 'dify-i18n/no-as-any-in-t': 'error',
|
||||
// // 'dify-i18n/no-legacy-namespace-prefix': 'error',
|
||||
// // 'dify-i18n/require-ns-option': 'error',
|
||||
// },
|
||||
// },
|
||||
// i18n JSON validation rules
|
||||
{
|
||||
files: ['i18n/**/*.json'],
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"list.action.pause": "Pause",
|
||||
"list.action.resume": "Resume",
|
||||
"list.action.settings": "Chunking Settings",
|
||||
"list.action.summary": "Generate summary",
|
||||
"list.action.sync": "Sync",
|
||||
"list.action.unarchive": "Unarchive",
|
||||
"list.action.uploadFile": "Upload new file",
|
||||
@@ -74,6 +75,9 @@
|
||||
"list.status.indexing": "Indexing",
|
||||
"list.status.paused": "Paused",
|
||||
"list.status.queuing": "Queuing",
|
||||
"list.summary.generating": "Generating...",
|
||||
"list.summary.generatingSummary": "Generating summary",
|
||||
"list.summary.ready": "Summary ready",
|
||||
"list.table.header.action": "ACTION",
|
||||
"list.table.header.chunkingMode": "CHUNKING MODE",
|
||||
"list.table.header.fileName": "NAME",
|
||||
@@ -328,5 +332,7 @@
|
||||
"segment.searchResults_one": "RESULT",
|
||||
"segment.searchResults_other": "RESULTS",
|
||||
"segment.searchResults_zero": "RESULT",
|
||||
"segment.summary": "SUMMARY",
|
||||
"segment.summaryPlaceholder": "Write a brief summary for better retrieval…",
|
||||
"segment.vectorHash": "Vector hash: "
|
||||
}
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
"form.retrievalSettings": "Retrieval Settings",
|
||||
"form.save": "Save",
|
||||
"form.searchModel": "Search model",
|
||||
"form.summaryAutoGen": "Summary Auto-Gen",
|
||||
"form.summaryAutoGenEnableTip": "Once enabled, summaries will be generated automatically for newly added documents. Existing documents can still be summarized manually.",
|
||||
"form.summaryAutoGenTip": "Summaries are automatically generated for newly added documents. Existing documents can still be summarized manually.",
|
||||
"form.summaryInstructions": "Instructions",
|
||||
"form.summaryInstructionsPlaceholder": "Describe the rules or style for auto-generated summaries…",
|
||||
"form.summaryModel": "Summary Model",
|
||||
"form.upgradeHighQualityTip": "Once upgrading to High Quality mode, reverting to Economical mode is not available",
|
||||
"title": "Knowledge settings"
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"list.action.pause": "暂停",
|
||||
"list.action.resume": "恢复",
|
||||
"list.action.settings": "分段设置",
|
||||
"list.action.summary": "生成摘要",
|
||||
"list.action.sync": "同步",
|
||||
"list.action.unarchive": "撤销归档",
|
||||
"list.action.uploadFile": "上传新文件",
|
||||
@@ -74,6 +75,9 @@
|
||||
"list.status.indexing": "索引中",
|
||||
"list.status.paused": "已暂停",
|
||||
"list.status.queuing": "排队中",
|
||||
"list.summary.generating": "生成中...",
|
||||
"list.summary.generatingSummary": "生成摘要中",
|
||||
"list.summary.ready": "摘要已生成",
|
||||
"list.table.header.action": "操作",
|
||||
"list.table.header.chunkingMode": "分段模式",
|
||||
"list.table.header.fileName": "名称",
|
||||
@@ -328,5 +332,7 @@
|
||||
"segment.searchResults_one": "搜索结果",
|
||||
"segment.searchResults_other": "搜索结果",
|
||||
"segment.searchResults_zero": "搜索结果",
|
||||
"segment.summary": "摘要",
|
||||
"segment.summaryPlaceholder": "写一个简短的摘要,以便更好地检索…",
|
||||
"segment.vectorHash": "向量哈希:"
|
||||
}
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
"form.retrievalSettings": "检索设置",
|
||||
"form.save": "保存",
|
||||
"form.searchModel": "搜索模型",
|
||||
"form.summaryAutoGen": "摘要自动生成",
|
||||
"form.summaryAutoGenEnableTip": "启用后,将自动为新添加的文档生成摘要。已有的文档仍可以手动摘要。",
|
||||
"form.summaryAutoGenTip": "将自动为新添加的文档生成摘要。已有的文档仍可以手动摘要。",
|
||||
"form.summaryInstructions": "指令",
|
||||
"form.summaryInstructionsPlaceholder": "描述自动生成摘要的规则或风格…",
|
||||
"form.summaryModel": "摘要模型",
|
||||
"form.upgradeHighQualityTip": "一旦升级为高质量模式,将无法切换回经济模式。",
|
||||
"title": "知识库设置"
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@ export type IconInfo = {
|
||||
icon_url?: string
|
||||
}
|
||||
|
||||
export type SummaryIndexSetting = {
|
||||
enable?: boolean
|
||||
model_name?: string
|
||||
model_provider_name?: string
|
||||
summary_prompt?: string
|
||||
}
|
||||
|
||||
export type DataSet = {
|
||||
id: string
|
||||
name: string
|
||||
@@ -88,6 +95,7 @@ export type DataSet = {
|
||||
runtime_mode: 'rag_pipeline' | 'general'
|
||||
enable_api: boolean // Indicates if the service API is enabled
|
||||
is_multimodal: boolean // Indicates if the dataset supports multimodal
|
||||
summary_index_setting?: SummaryIndexSetting
|
||||
}
|
||||
|
||||
export type ExternalAPIItem = {
|
||||
@@ -225,7 +233,7 @@ export type IndexingEstimateResponse = {
|
||||
total_price: number
|
||||
currency: string
|
||||
total_segments: number
|
||||
preview: Array<{ content: string, child_chunks: string[] }>
|
||||
preview: Array<{ content: string, child_chunks: string[], summary?: string }>
|
||||
qa_preview?: QA[]
|
||||
}
|
||||
|
||||
@@ -262,6 +270,7 @@ export type ProcessRuleResponse = {
|
||||
mode: ProcessMode
|
||||
rules: Rules
|
||||
limits: Limits
|
||||
summary_index_setting?: SummaryIndexSetting
|
||||
}
|
||||
|
||||
export type Rules = {
|
||||
@@ -392,6 +401,7 @@ export type InitialDocumentDetail = {
|
||||
total_segments?: number
|
||||
doc_form: ChunkingMode
|
||||
doc_language: string
|
||||
summary_index_status?: string
|
||||
}
|
||||
|
||||
export type SimpleDocumentDetail = InitialDocumentDetail & {
|
||||
@@ -425,6 +435,7 @@ export type DocumentReq = {
|
||||
doc_form: ChunkingMode
|
||||
doc_language: string
|
||||
process_rule: ProcessRule
|
||||
summary_index_setting?: SummaryIndexSetting
|
||||
}
|
||||
|
||||
export type CreateDocumentReq = DocumentReq & {
|
||||
@@ -467,6 +478,7 @@ export type NotionPage = {
|
||||
export type ProcessRule = {
|
||||
mode: ProcessMode
|
||||
rules: Rules
|
||||
summary_index_setting?: SummaryIndexSetting
|
||||
}
|
||||
|
||||
export type createDocumentResponse = {
|
||||
@@ -575,6 +587,7 @@ export type SegmentDetailModel = {
|
||||
error: string | null
|
||||
stopped_at: number
|
||||
answer?: string
|
||||
summary?: string
|
||||
child_chunks?: ChildChunkDetail[]
|
||||
updated_at: number
|
||||
attachments: Attachment[]
|
||||
@@ -618,6 +631,7 @@ export type HitTesting = {
|
||||
tsne_position: TsnePosition
|
||||
child_chunks: HitTestingChildChunk[] | null
|
||||
files: Attachment[]
|
||||
summary?: string
|
||||
}
|
||||
|
||||
export type ExternalKnowledgeBaseHitTesting = {
|
||||
@@ -697,6 +711,7 @@ export type RelatedAppResponse = {
|
||||
export type SegmentUpdater = {
|
||||
content: string
|
||||
answer?: string
|
||||
summary?: string
|
||||
keywords?: string[]
|
||||
regenerate_child_chunks?: boolean
|
||||
attachment_ids?: string[]
|
||||
@@ -778,6 +793,7 @@ export enum DocumentActionType {
|
||||
archive = 'archive',
|
||||
unArchive = 'un_archive',
|
||||
delete = 'delete',
|
||||
summary = 'summary',
|
||||
}
|
||||
|
||||
export type UpdateDocumentBatchParams = {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"build:docker": "next build && node scripts/optimize-standalone.js",
|
||||
"start": "node ./scripts/copy-and-start.mjs",
|
||||
"lint": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache",
|
||||
"lint:ci": "pnpm lint --concurrency 3",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"lint:quiet": "pnpm lint --quiet",
|
||||
"lint:complexity": "pnpm lint --rule 'complexity: [error, {max: 15}]' --quiet",
|
||||
|
||||
@@ -107,6 +107,18 @@ export const useSyncDocument = () => {
|
||||
})
|
||||
}
|
||||
|
||||
export const useDocumentSummary = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ datasetId, documentIds, documentId }: UpdateDocumentBatchParams) => {
|
||||
return post<CommonResponse>(`/datasets/${datasetId}/documents/generate-summary`, {
|
||||
body: {
|
||||
document_list: documentId ? [documentId] : documentIds!,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useSyncWebsite = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ datasetId, documentId }: UpdateDocumentBatchParams) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"target": "es2015",
|
||||
"target": "es2022",
|
||||
"jsx": "preserve",
|
||||
"lib": [
|
||||
"dom",
|
||||
|
||||
@@ -85,6 +85,10 @@ afterEach(() => {
|
||||
// mock next/image to avoid width/height requirements for data URLs
|
||||
vi.mock('next/image')
|
||||
|
||||
// mock zustand - auto-resets all stores after each test
|
||||
// Based on official Zustand testing guide: https://zustand.docs.pmnd.rs/guides/testing
|
||||
vi.mock('zustand')
|
||||
|
||||
// mock react-i18next
|
||||
vi.mock('react-i18next', async () => {
|
||||
const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next')
|
||||
|
||||
Reference in New Issue
Block a user