Compare commits

..

6 Commits

Author SHA1 Message Date
yyh
a0aa8cdb45 Merge remote-tracking branch 'origin/main' into feature/task-quadrant-view 2026-01-16 18:20:29 +08:00
yyh
ae8618877b fix(web): quadrant matrix i18n 2026-01-16 18:17:28 +08:00
yyh
1c55602445 fix(web): add calendar icon and DDL label to deadline badge in task-item 2026-01-16 17:24:11 +08:00
yyh
a3f1220d23 feat(web): add fullscreen expand mode to quadrant-matrix component
- Add expand button in header to open FullScreenModal
- Add numbered circles (1-4) to quadrant headers
- Add expanded prop to show full content without line-clamp
- Reorder grid layout: Q1 top-left, Q2 top-right, Q3 bottom-left, Q4 bottom-right
- Remove axis labels for cleaner design
2026-01-16 17:16:13 +08:00
yyh
d62e16b9bb fix(web): improve quadrant-matrix layout and text overflow handling
- Simplify axis label layout with horizontal/vertical arrangement
- Add proper text truncation with line-clamp and tooltips
- Fix overflow issues by adding min-w-0 on flex children
- Move scores inline with task name for compact display
- Add task count badge to quadrant headers
- Reduce maxDisplay to 3 for better density
2026-01-16 16:58:57 +08:00
yyh
13f2a43ccc feat(web): add Eisenhower Matrix visualization component for task quadrants
Add a new quadrant-matrix component that renders tasks in a 2x2 grid based
on importance and urgency scores. Integrate with code-block as a new
'quadrant' language type for markdown rendering.
2026-01-16 16:58:56 +08:00
69 changed files with 692 additions and 6044 deletions

View File

@@ -106,9 +106,8 @@ jobs:
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ./web
run: |
pnpm run lint:ci
# pnpm run lint:report
# continue-on-error: true
pnpm run lint:report
continue-on-error: true
# - name: Annotate Code
# if: steps.changed-files.outputs.any_changed == 'true' && github.event_name == 'pull_request'
@@ -127,6 +126,11 @@ 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

View File

@@ -366,48 +366,3 @@ 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

View File

@@ -1,56 +0,0 @@
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()
})
})
})

View File

@@ -3,7 +3,9 @@ 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'
@@ -103,6 +105,22 @@ 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)
@@ -114,6 +132,9 @@ beforeAll(() => {
})
beforeEach(() => {
vi.clearAllMocks()
resetAccessControlStore()
resetGlobalStore()
mockMutateAsync.mockResolvedValue(undefined)
mockUseUpdateAccessMode.mockReturnValue({
isPending: false,

View File

@@ -189,7 +189,6 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
}
</div>
))}
{isFetchingNextPage && <Loading />}
</div>
</>
)}

View File

@@ -21,15 +21,15 @@ export const AppCardSkeleton = React.memo(({ count = 6 }: AppCardSkeletonProps)
>
<SkeletonContainer className="h-full">
<SkeletonRow>
<SkeletonRectangle className="h-10 w-10 animate-pulse rounded-lg" />
<SkeletonRectangle className="h-10 w-10 rounded-lg" />
<div className="flex flex-1 flex-col gap-1">
<SkeletonRectangle className="h-4 w-2/3 animate-pulse" />
<SkeletonRectangle className="h-3 w-1/3 animate-pulse" />
<SkeletonRectangle className="h-4 w-2/3" />
<SkeletonRectangle className="h-3 w-1/3" />
</div>
</SkeletonRow>
<div className="mt-4 flex flex-col gap-2">
<SkeletonRectangle className="h-3 w-full animate-pulse" />
<SkeletonRectangle className="h-3 w-4/5 animate-pulse" />
<SkeletonRectangle className="h-3 w-full" />
<SkeletonRectangle className="h-3 w-4/5" />
</div>
</SkeletonContainer>
</div>

View File

@@ -248,9 +248,6 @@ const List = () => {
// No apps - show empty state
return <Empty />
})()}
{isFetchingNextPage && (
<AppCardSkeleton count={3} />
)}
</div>
{isCurrentWorkspaceEditor && (

View File

@@ -1,6 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,53 +0,0 @@
{
"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"
}

View File

@@ -1,20 +0,0 @@
// 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

View File

@@ -11,6 +11,5 @@ 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'

View File

@@ -1,25 +1,21 @@
'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 = (props?: ILoadingProps) => {
const { type = 'area', className } = props || {}
const Loading = (
{ type = 'area' }: ILoadingProps = { type: 'area' },
) => {
const { t } = useTranslation()
return (
<div
className={cn(
'flex w-full items-center justify-center',
type === 'app' && 'h-full',
className,
)}
className={`flex w-full items-center justify-center ${type === 'app' ? 'h-full' : ''}`}
role="status"
aria-live="polite"
aria-label={t('loading', { ns: 'appApi' })}
@@ -41,5 +37,4 @@ const Loading = (props?: ILoadingProps) => {
</div>
)
}
export default Loading

View File

@@ -16,6 +16,7 @@ import { Theme } from '@/types/app'
import SVGRenderer from '../svg-gallery' // Assumes svg-gallery.tsx is in /base directory
const Flowchart = dynamic(() => import('@/app/components/base/mermaid'), { ssr: false })
const QuadrantMatrix = dynamic(() => import('@/app/components/base/quadrant-matrix'), { ssr: false })
// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
const capitalizationLanguageNameMap: Record<string, string> = {
@@ -40,6 +41,7 @@ const capitalizationLanguageNameMap: Record<string, string> = {
latex: 'Latex',
svg: 'SVG',
abc: 'ABC',
quadrant: 'Quadrant',
}
const getCorrectCapitalizationLanguageName = (language: string) => {
if (!language)
@@ -409,6 +411,12 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
<MarkdownMusic children={content} />
</ErrorBoundary>
)
case 'quadrant':
return (
<ErrorBoundary>
<QuadrantMatrix content={content} />
</ErrorBoundary>
)
default:
return (
<SyntaxHighlighter

View File

@@ -0,0 +1,153 @@
'use client'
import type { FC } from 'react'
import type { QuadrantData } from './types'
import { RiExpandDiagonalLine } from '@remixicon/react'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import FullScreenModal from '@/app/components/base/fullscreen-modal'
import QuadrantCard from './quadrant-card'
import { isValidQuadrantData, QUADRANT_CONFIGS } from './types'
type QuadrantMatrixProps = {
content: string
}
const QuadrantMatrix: FC<QuadrantMatrixProps> = ({ content }) => {
const { t } = useTranslation()
const [isExpanded, setIsExpanded] = useState(false)
const parsedData = useMemo<QuadrantData | null>(() => {
try {
const trimmed = content.trim()
const data = JSON.parse(trimmed)
if (!isValidQuadrantData(data))
return null
return data
}
catch {
return null
}
}, [content])
const handleExpand = useCallback(() => {
setIsExpanded(true)
}, [])
const handleClose = useCallback(() => {
setIsExpanded(false)
}, [])
if (!parsedData) {
return (
<div className="flex items-center justify-center rounded-xl bg-components-panel-bg-blur p-8">
<div className="text-center text-text-secondary">
<div className="system-md-semibold mb-2">{t('quadrantMatrix.invalidData', { ns: 'app' })}</div>
<div className="text-sm text-text-tertiary">
{t('quadrantMatrix.invalidDataDesc', { ns: 'app' })}
</div>
</div>
</div>
)
}
const totalTasks
= parsedData.q1.length
+ parsedData.q2.length
+ parsedData.q3.length
+ parsedData.q4.length
// Shared grid content component
const renderGrid = (expanded: boolean) => (
<div className="grid grid-cols-2 gap-3">
{/* Row 1: Q1 (Do First), Q2 (Schedule) */}
<QuadrantCard
config={QUADRANT_CONFIGS.q1}
tasks={parsedData.q1}
expanded={expanded}
/>
<QuadrantCard
config={QUADRANT_CONFIGS.q2}
tasks={parsedData.q2}
expanded={expanded}
/>
{/* Row 2: Q3 (Delegate), Q4 (Don't Do) */}
<QuadrantCard
config={QUADRANT_CONFIGS.q3}
tasks={parsedData.q3}
expanded={expanded}
/>
<QuadrantCard
config={QUADRANT_CONFIGS.q4}
tasks={parsedData.q4}
expanded={expanded}
/>
</div>
)
return (
<>
<div className="w-full overflow-hidden rounded-xl bg-components-panel-bg-blur p-4">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<div>
<div className="system-md-semibold text-text-primary">
{t('quadrantMatrix.title', { ns: 'app' })}
</div>
<div className="text-xs text-text-tertiary">
{t('quadrantMatrix.taskCount', { ns: 'app', count: totalTasks })}
</div>
</div>
{/* Legend + Expand Button */}
<div className="flex items-center gap-3">
<div className="flex items-center gap-3 text-[11px] text-text-quaternary">
<span>{t('quadrantMatrix.legend.importance', { ns: 'app' })}</span>
<span>{t('quadrantMatrix.legend.urgency', { ns: 'app' })}</span>
</div>
<ActionButton onClick={handleExpand}>
<RiExpandDiagonalLine className="h-4 w-4" />
</ActionButton>
</div>
</div>
{/* 2x2 Grid */}
{renderGrid(false)}
</div>
{/* Fullscreen Modal */}
<FullScreenModal
open={isExpanded}
onClose={handleClose}
closable
>
<div className="flex h-full flex-col p-6">
{/* Modal Header */}
<div className="mb-6 flex items-center justify-between">
<div>
<div className="text-xl font-semibold text-text-primary">
{t('quadrantMatrix.title', { ns: 'app' })}
</div>
<div className="text-sm text-text-tertiary">
{t('quadrantMatrix.taskCount', { ns: 'app', count: totalTasks })}
</div>
</div>
<div className="flex items-center gap-3 text-sm text-text-quaternary">
<span>{t('quadrantMatrix.legend.importance', { ns: 'app' })}</span>
<span>{t('quadrantMatrix.legend.urgency', { ns: 'app' })}</span>
</div>
</div>
{/* Expanded Grid */}
<div className="min-h-0 flex-1">
{renderGrid(true)}
</div>
</div>
</FullScreenModal>
</>
)
}
export default QuadrantMatrix

View File

@@ -0,0 +1,102 @@
'use client'
import type { FC } from 'react'
import type { QuadrantConfig, Task } from './types'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
import TaskItem from './task-item'
type QuadrantCardProps = {
config: QuadrantConfig
tasks: Task[]
expanded?: boolean
maxDisplay?: number
}
const QuadrantCard: FC<QuadrantCardProps> = ({
config,
tasks,
expanded = false,
maxDisplay = 3,
}) => {
const { t } = useTranslation()
const { number, titleKey, subtitleKey, bgClass, borderClass, titleClass } = config
const displayLimit = expanded ? Infinity : maxDisplay
const displayTasks = tasks.slice(0, displayLimit)
const remainingCount = Math.max(0, tasks.length - displayLimit)
return (
<div
className={cn(
'flex min-w-0 flex-col rounded-xl border p-3',
bgClass,
borderClass,
expanded ? 'min-h-[280px]' : 'min-h-[200px]',
)}
>
{/* Header with numbered circle */}
<div className="mb-2 shrink-0">
<div className="flex items-center gap-2">
{/* Numbered circle */}
<span className={cn(
'flex h-5 w-5 items-center justify-center rounded-full border text-xs font-semibold',
borderClass,
titleClass,
)}
>
{number}
</span>
<span className={cn('system-sm-semibold', titleClass)}>{t(titleKey, { ns: 'app' })}</span>
{tasks.length > 0 && (
<span className="bg-components-badge-bg-gray rounded-full px-1.5 py-0.5 text-[10px] font-medium text-text-tertiary">
{tasks.length}
</span>
)}
</div>
<div className="text-[11px] text-text-tertiary">{t(subtitleKey, { ns: 'app' })}</div>
</div>
{/* Task List */}
<div className={cn(
'flex min-h-0 flex-1 flex-col gap-2',
expanded && 'overflow-y-auto',
)}
>
{displayTasks.length > 0
? (
displayTasks.map((task) => {
const taskKey = [
task.name,
task.deadline ?? 'no-deadline',
task.importance_score,
task.urgency_score,
task.description ?? '',
task.action_advice ?? '',
].join('|')
return (
<TaskItem
key={taskKey}
task={task}
expanded={expanded}
/>
)
})
)
: (
<div className="flex flex-1 items-center justify-center text-xs text-text-quaternary">
{t('quadrantMatrix.noTasks', { ns: 'app' })}
</div>
)}
</div>
{/* More indicator (only in non-expanded mode) */}
{!expanded && remainingCount > 0 && (
<div className="mt-2 shrink-0 text-center text-[11px] text-text-tertiary">
{t('quadrantMatrix.more', { ns: 'app', count: remainingCount })}
</div>
)}
</div>
)
}
export default QuadrantCard

View File

@@ -0,0 +1,88 @@
'use client'
import type { FC } from 'react'
import type { Task } from './types'
import { RiCalendarLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
type TaskItemProps = {
task: Task
expanded?: boolean
showScores?: boolean
}
const TaskItem: FC<TaskItemProps> = ({ task, expanded = false, showScores = true }) => {
const { t } = useTranslation()
const { name, description, deadline, importance_score, urgency_score, action_advice } = task
return (
<div className="group min-w-0 rounded-lg bg-components-panel-bg p-2.5 shadow-xs transition-all hover:shadow-sm">
{/* Header: Task Name + Scores */}
<div className="flex items-start justify-between gap-2">
<div
className={cn(
'system-sm-medium min-w-0 flex-1 text-text-primary',
!expanded && 'truncate',
)}
title={name}
>
{name}
</div>
{showScores && (
<div className="flex shrink-0 items-center gap-1 text-[10px] font-medium">
<span className="text-text-accent">
I:
{importance_score}
</span>
<span className="text-text-warning">
U:
{urgency_score}
</span>
</div>
)}
</div>
{/* Description */}
{description && (
<div className={cn(
'mt-1 text-xs text-text-tertiary',
!expanded && 'line-clamp-2',
)}
>
{description}
</div>
)}
{/* Deadline Badge */}
{deadline && (
<div className="mt-1.5">
<span className="bg-components-badge-bg-gray inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] text-text-tertiary">
<RiCalendarLine className="h-3 w-3" />
<span>
{t('quadrantMatrix.deadline', { ns: 'app' })}
{' '}
{deadline}
</span>
</span>
</div>
)}
{/* Action Advice */}
{action_advice && (
<div className="mt-2 border-t border-divider-subtle pt-2">
<p
className={cn(
'text-xs italic text-text-quaternary',
!expanded && 'line-clamp-2',
)}
title={!expanded ? action_advice : undefined}
>
{action_advice}
</p>
</div>
)}
</div>
)
}
export default TaskItem

View File

@@ -0,0 +1,92 @@
/**
* Type definitions for Eisenhower Matrix (Task Quadrant) visualization
*/
import type { I18nKeysWithPrefix } from '@/types/i18n'
export type Task = {
name: string
description?: string
deadline?: string // YYYY-MM-DD format
importance_score: number // 0-100, based on goal alignment and long-term value
urgency_score: number // 0-100, based on deadline pressure and delay penalty
action_advice?: string // Suggested action for this task
}
export type QuadrantData = {
q1: Task[] // Urgent & Important - Do First
q2: Task[] // Not Urgent & Important - Schedule
q3: Task[] // Urgent & Not Important - Delegate
q4: Task[] // Not Urgent & Not Important - Don't Do
}
type QuadrantKeyBase = I18nKeysWithPrefix<'app', 'quadrantMatrix.q'>
type QuadrantTitleKey = Extract<QuadrantKeyBase, `${string}.title`>
type QuadrantSubtitleKey = Extract<QuadrantKeyBase, `${string}.subtitle`>
export type QuadrantConfig = {
key: 'q1' | 'q2' | 'q3' | 'q4'
number: number
titleKey: QuadrantTitleKey // i18n key for title
subtitleKey: QuadrantSubtitleKey // i18n key for subtitle
bgClass: string
borderClass: string
titleClass: string
}
// Layout based on Eisenhower Matrix:
// Q1 (Do First) - top-left, Q2 (Schedule) - top-right
// Q3 (Delegate) - bottom-left, Q4 (Don't Do) - bottom-right
export const QUADRANT_CONFIGS: Record<string, QuadrantConfig> = {
q1: {
key: 'q1',
number: 1,
titleKey: 'quadrantMatrix.q1.title',
subtitleKey: 'quadrantMatrix.q1.subtitle',
bgClass: 'bg-state-destructive-hover',
borderClass: 'border-state-destructive-border',
titleClass: 'text-text-destructive',
},
q2: {
key: 'q2',
number: 2,
titleKey: 'quadrantMatrix.q2.title',
subtitleKey: 'quadrantMatrix.q2.subtitle',
bgClass: 'bg-state-accent-hover',
borderClass: 'border-state-accent-border',
titleClass: 'text-text-accent',
},
q3: {
key: 'q3',
number: 3,
titleKey: 'quadrantMatrix.q3.title',
subtitleKey: 'quadrantMatrix.q3.subtitle',
bgClass: 'bg-state-warning-hover',
borderClass: 'border-state-warning-border',
titleClass: 'text-text-warning',
},
q4: {
key: 'q4',
number: 4,
titleKey: 'quadrantMatrix.q4.title',
subtitleKey: 'quadrantMatrix.q4.subtitle',
bgClass: 'bg-components-panel-on-panel-item-bg',
borderClass: 'border-divider-regular',
titleClass: 'text-text-tertiary',
},
}
/**
* Validates if the data structure matches QuadrantData interface
*/
export function isValidQuadrantData(data: unknown): data is QuadrantData {
if (typeof data !== 'object' || data === null)
return false
const d = data as Record<string, unknown>
return (
Array.isArray(d.q1)
&& Array.isArray(d.q2)
&& Array.isArray(d.q3)
&& Array.isArray(d.q4)
)
}

View File

@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import type { PreProcessingRule, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
import type { PreProcessingRule } from '@/models/datasets'
import {
RiAlertFill,
RiSearchEyeLine,
@@ -12,7 +12,6 @@ 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'
@@ -53,8 +52,6 @@ type GeneralChunkingOptionsProps = {
onReset: () => void
// Locale
locale: string
summaryIndexSetting?: SummaryIndexSettingType
onSummaryIndexSettingChange?: (payload: SummaryIndexSettingType) => void
}
export const GeneralChunkingOptions: FC<GeneralChunkingOptionsProps> = ({
@@ -77,8 +74,6 @@ export const GeneralChunkingOptions: FC<GeneralChunkingOptionsProps> = ({
onPreview,
onReset,
locale,
summaryIndexSetting,
onSummaryIndexSettingChange,
}) => {
const { t } = useTranslation()
@@ -151,13 +146,6 @@ 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" />

View File

@@ -2,7 +2,7 @@
import type { FC } from 'react'
import type { ParentChildConfig } from '../hooks'
import type { ParentMode, PreProcessingRule, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
import type { ParentMode, PreProcessingRule } from '@/models/datasets'
import { RiSearchEyeLine } from '@remixicon/react'
import Image from 'next/image'
import { useTranslation } from 'react-i18next'
@@ -11,7 +11,6 @@ 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'
@@ -32,8 +31,6 @@ type ParentChildOptionsProps = {
// State
parentChildConfig: ParentChildConfig
rules: PreProcessingRule[]
summaryIndexSetting?: SummaryIndexSettingType
onSummaryIndexSettingChange?: (payload: SummaryIndexSettingType) => void
currentDocForm: ChunkingMode
// Flags
isActive: boolean
@@ -54,7 +51,6 @@ type ParentChildOptionsProps = {
export const ParentChildOptions: FC<ParentChildOptionsProps> = ({
parentChildConfig,
rules,
summaryIndexSetting,
currentDocForm: _currentDocForm,
isActive,
isInUpload,
@@ -66,7 +62,6 @@ export const ParentChildOptions: FC<ParentChildOptionsProps> = ({
onChildDelimiterChange,
onChildMaxLengthChange,
onRuleToggle,
onSummaryIndexSettingChange,
onPreview,
onReset,
}) => {
@@ -188,13 +183,6 @@ export const ParentChildOptions: FC<ParentChildOptionsProps> = ({
</label>
</div>
))}
<div className="mt-3">
<SummaryIndexSetting
entry="create-document"
summaryIndexSetting={summaryIndexSetting}
onSummaryIndexSettingChange={onSummaryIndexSettingChange}
/>
</div>
</div>
</div>
</div>

View File

@@ -14,7 +14,6 @@ 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'
@@ -100,7 +99,6 @@ export const PreviewPanel: FC<PreviewPanelProps> = ({
characterCount={item.content.length}
>
{item.content}
{item.summary && <SummaryLabel summary={item.summary} />}
</ChunkContainer>
))
)}
@@ -133,7 +131,6 @@ export const PreviewPanel: FC<PreviewPanelProps> = ({
)
})}
</FormattedText>
{item.summary && <SummaryLabel summary={item.summary} />}
</ChunkContainer>
)
})

View File

@@ -9,7 +9,6 @@ import type {
CustomFile,
FullDocumentDetail,
ProcessRule,
SummaryIndexSetting as SummaryIndexSettingType,
} from '@/models/datasets'
import type { RetrievalConfig, RETRIEVE_METHOD } from '@/types/app'
import { useCallback } from 'react'
@@ -142,7 +141,6 @@ export const useDocumentCreation = (options: UseDocumentCreationOptions) => {
retrievalConfig: RetrievalConfig,
embeddingModel: DefaultModel,
indexingTechnique: string,
summaryIndexSetting?: SummaryIndexSettingType,
): CreateDocumentReq | null => {
if (isSetting) {
return {
@@ -150,7 +148,6 @@ 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,
@@ -167,7 +164,6 @@ export const useDocumentCreation = (options: UseDocumentCreationOptions) => {
},
indexing_technique: indexingTechnique,
process_rule: processRule,
summary_index_setting: summaryIndexSetting,
doc_form: currentDocForm,
doc_language: docLanguage,
retrieval_model: retrievalConfig,

View File

@@ -1,5 +1,5 @@
import type { ParentMode, PreProcessingRule, ProcessRule, Rules, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
import { useCallback, useRef, useState } from 'react'
import type { ParentMode, PreProcessingRule, ProcessRule, Rules } from '@/models/datasets'
import { useCallback, useState } from 'react'
import { ChunkingMode, ProcessMode } from '@/models/datasets'
import escape from './escape'
import unescape from './unescape'
@@ -39,7 +39,6 @@ export const defaultParentChildConfig: ParentChildConfig = {
export type UseSegmentationStateOptions = {
initialSegmentationType?: ProcessMode
initialSummaryIndexSetting?: SummaryIndexSettingType
}
export const useSegmentationState = (options: UseSegmentationStateOptions = {}) => {
@@ -59,12 +58,6 @@ 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)
@@ -141,7 +134,6 @@ export const useSegmentationState = (options: UseSegmentationStateOptions = {})
},
},
mode: 'hierarchical',
summary_index_setting: summaryIndexSettingRef.current,
} as ProcessRule
}
@@ -155,7 +147,6 @@ export const useSegmentationState = (options: UseSegmentationStateOptions = {})
},
},
mode: segmentationType,
summary_index_setting: summaryIndexSettingRef.current,
} as ProcessRule
}, [rules, parentChildConfig, segmentIdentifier, maxChunkLength, overlap, segmentationType])
@@ -213,8 +204,6 @@ export const useSegmentationState = (options: UseSegmentationStateOptions = {})
defaultConfig,
setDefaultConfig,
toggleRule,
summaryIndexSetting,
handleSummaryIndexSettingChange,
// Parent-child config
parentChildConfig,

View File

@@ -65,7 +65,6 @@ 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,
@@ -157,7 +156,7 @@ const StepTwo: FC<StepTwoProps> = ({
})
if (!isValid)
return
const params = creation.buildCreationParams(currentDocForm, docLanguage, segmentation.getProcessRule(currentDocForm), indexing.retrievalConfig, indexing.embeddingModel, indexing.getIndexingTechnique(), segmentation.summaryIndexSetting)
const params = creation.buildCreationParams(currentDocForm, docLanguage, segmentation.getProcessRule(currentDocForm), indexing.retrievalConfig, indexing.embeddingModel, indexing.getIndexingTechnique())
if (!params)
return
await creation.executeCreation(params, indexing.indexType, indexing.retrievalConfig)
@@ -218,8 +217,6 @@ const StepTwo: FC<StepTwoProps> = ({
onPreview={updatePreview}
onReset={segmentation.resetToDefaults}
locale={locale}
summaryIndexSetting={segmentation.summaryIndexSetting}
onSummaryIndexSettingChange={segmentation.handleSummaryIndexSettingChange}
/>
)}
{showParentChildOption && (
@@ -239,8 +236,6 @@ const StepTwo: FC<StepTwoProps> = ({
onRuleToggle={segmentation.toggleRule}
onPreview={updatePreview}
onReset={segmentation.resetToDefaults}
summaryIndexSetting={segmentation.summaryIndexSetting}
onSummaryIndexSettingChange={segmentation.handleSummaryIndexSettingChange}
/>
)}
<Divider className="my-5" />

View File

@@ -30,12 +30,11 @@ 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, useDocumentSummary } from '@/service/knowledge/use-document'
import { useDocumentArchive, useDocumentBatchRetryIndex, useDocumentDelete, useDocumentDisable, useDocumentEnable } 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'
@@ -219,7 +218,6 @@ 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()
@@ -232,9 +230,6 @@ const DocumentList: FC<IDocumentListProps> = ({
case DocumentActionType.archive:
opApi = archiveDocument
break
case DocumentActionType.summary:
opApi = generateSummary
break
case DocumentActionType.enable:
opApi = enableDocument
break
@@ -414,13 +409,6 @@ 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' })}
@@ -473,7 +461,6 @@ 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)}

View File

@@ -19,7 +19,6 @@ 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'
@@ -32,7 +31,6 @@ import {
useDocumentEnable,
useDocumentPause,
useDocumentResume,
useDocumentSummary,
useDocumentUnArchive,
useSyncDocument,
useSyncWebsite,
@@ -84,7 +82,6 @@ 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'
@@ -110,9 +107,6 @@ const Operations = ({
else
opApi = syncWebsite
break
case 'summary':
opApi = generateSummary
break
case 'pause':
opApi = pauseDocument
break
@@ -226,10 +220,6 @@ 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" />
</>
)}

View File

@@ -6,7 +6,6 @@ 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'
@@ -16,7 +15,6 @@ type IBatchActionProps = {
onBatchEnable: () => void
onBatchDisable: () => void
onBatchDelete: () => Promise<void>
onBatchSummary?: () => void
onArchive?: () => void
onEditMetadata?: () => void
onBatchReIndex?: () => void
@@ -28,7 +26,6 @@ const BatchAction: FC<IBatchActionProps> = ({
selectedIds,
onBatchEnable,
onBatchDisable,
onBatchSummary,
onArchive,
onBatchDelete,
onEditMetadata,
@@ -85,16 +82,7 @@ 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"

View File

@@ -1,26 +0,0 @@
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)

View File

@@ -1,47 +0,0 @@
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)

View File

@@ -1,35 +0,0 @@
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)

View File

@@ -322,7 +322,6 @@ const Completed: FC<ICompletedProps> = ({
answer: string,
keywords: string[],
attachments: FileEntity[],
summary?: string,
needRegenerate = false,
) => {
const params: SegmentUpdater = { content: '', attachment_ids: [] }
@@ -352,9 +351,6 @@ const Completed: FC<ICompletedProps> = ({
params.attachment_ids = attachments.map(item => item.uploadedId!)
}
if (summary)
params.summary = summary
if (needRegenerate)
params.regenerate_child_chunks = needRegenerate
@@ -368,7 +364,6 @@ 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

View File

@@ -19,14 +19,13 @@ 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 }, status?: string }
detail?: SegmentDetailModel & { document?: { name: string } }
onClick?: () => void
onChangeSwitch?: (enabled: boolean, segId?: string) => Promise<void>
onDelete?: (segId: string) => Promise<void>
@@ -44,7 +43,7 @@ type ISegmentCardProps = {
}
const SegmentCard: FC<ISegmentCardProps> = ({
detail = { status: '' },
detail = {},
onClick,
onChangeSwitch,
onDelete,
@@ -68,7 +67,6 @@ const SegmentCard: FC<ISegmentCardProps> = ({
word_count,
hit_count,
answer,
summary,
keywords,
child_chunks = [],
created_at,
@@ -239,11 +237,6 @@ 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} />)}

View File

@@ -25,7 +25,6 @@ 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 = {
@@ -36,7 +35,6 @@ type ISegmentDetailProps = {
a: string,
k: string[],
attachments: FileEntity[],
summary?: string,
needRegenerate?: boolean,
) => void
onCancel: () => void
@@ -59,7 +57,6 @@ 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(),
@@ -94,8 +91,8 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
}, [onCancel])
const handleSave = useCallback(() => {
onUpdate(segInfo?.id || '', question, answer, keywords, attachments, summary, false)
}, [onUpdate, segInfo?.id, question, answer, keywords, attachments, summary])
onUpdate(segInfo?.id || '', question, answer, keywords, attachments)
}, [onUpdate, segInfo?.id, question, answer, keywords, attachments])
const handleRegeneration = useCallback(() => {
setShowRegenerationModal(true)
@@ -114,8 +111,8 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
}, [onCancel, onModalStateChange])
const onConfirmRegeneration = useCallback(() => {
onUpdate(segInfo?.id || '', question, answer, keywords, attachments, summary, true)
}, [onUpdate, segInfo?.id, question, answer, keywords, attachments, summary])
onUpdate(segInfo?.id || '', question, answer, keywords, attachments, true)
}, [onUpdate, segInfo?.id, question, answer, keywords, attachments])
const onAttachmentsChange = useCallback((attachments: FileEntity[]) => {
setAttachments(attachments)
@@ -200,11 +197,6 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
value={attachments}
onChange={onAttachmentsChange}
/>
<SummaryText
value={summary}
onChange={summary => setSummary(summary)}
disabled={!isEditMode}
/>
{isECOIndexing && (
<Keywords
className="w-full"

View File

@@ -1 +1 @@
export type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive' | 'pause' | 'resume' | 'summary'
export type OperationName = 'delete' | 'archive' | 'enable' | 'disable' | 'sync' | 'un_archive' | 'pause' | 'resume'

View File

@@ -12,7 +12,6 @@ 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'
@@ -29,7 +28,7 @@ const ChunkDetailModal = ({
onHide,
}: ChunkDetailModalProps) => {
const { t } = useTranslation()
const { segment, score, child_chunks, files, summary } = payload
const { segment, score, child_chunks, files } = 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
@@ -105,14 +104,11 @@ const ChunkDetailModal = ({
{/* Mask */}
<Mask className="absolute inset-x-0 bottom-0" />
</div>
{(showImages || showKeywords || !!summary) && (
{(showImages || showKeywords) && (
<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>

View File

@@ -7,7 +7,6 @@ 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'
@@ -26,7 +25,7 @@ const ResultItem = ({
payload,
}: ResultItemProps) => {
const { t } = useTranslation()
const { segment, score, child_chunks, files, summary } = payload
const { segment, score, child_chunks, files } = payload
const data = segment
const { position, word_count, content, sign_content, keywords, document } = data
const isParentChildRetrieval = !!(child_chunks && child_chunks.length > 0)
@@ -99,9 +98,6 @@ const ResultItem = ({
))}
</div>
)}
{summary && (
<SummaryLabel summary={summary} className="mt-2" />
)}
</div>
{/* Foot */}
<ResultItemFooter docType={fileType} docTitle={document.name} showDetailModal={showDetailModal} />

View File

@@ -2,7 +2,6 @@
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'
@@ -26,7 +25,6 @@ const Datasets = ({
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
} = useDatasetList({
initialPage: 1,
tag_ids: tags,
@@ -62,7 +60,6 @@ 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>
</>

View File

@@ -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, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
import type { IconInfo } from '@/models/datasets'
import type { AppIconType, RetrievalConfig } from '@/types/app'
import { RiAlertFill } from '@remixicon/react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@@ -33,7 +33,6 @@ 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'
@@ -77,12 +76,6 @@ 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()
@@ -174,7 +167,6 @@ const Form = () => {
},
}),
keyword_number: keywordNumber,
summary_index_setting: summaryIndexSetting,
},
} as any
if (permission === DatasetPermission.partialMembers) {
@@ -356,23 +348,6 @@ 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'
? (

View File

@@ -1,228 +0,0 @@
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)

View File

@@ -33,7 +33,6 @@ const AppNav = () => {
data: appsData,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
refetch,
} = useInfiniteAppList({
page: 1,
@@ -112,7 +111,6 @@ const AppNav = () => {
createText={t('menus.newApp', { ns: 'common' })}
onCreate={openModal}
onLoadMore={handleLoadMore}
isLoadingMore={isFetchingNextPage}
/>
<CreateAppModal
show={showNewAppDialog}

View File

@@ -23,7 +23,6 @@ const DatasetNav = () => {
data: datasetList,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useDatasetList({
initialPage: 1,
limit: 30,
@@ -94,7 +93,6 @@ const DatasetNav = () => {
createText={t('menus.newDataset', { ns: 'common' })}
onCreate={() => router.push(createRoute)}
onLoadMore={handleLoadMore}
isLoadingMore={isFetchingNextPage}
/>
)
}

View File

@@ -30,7 +30,6 @@ const Nav = ({
createText,
onCreate,
onLoadMore,
isLoadingMore,
isApp,
}: INavProps) => {
const setAppDetail = useAppStore(state => state.setAppDetail)
@@ -82,7 +81,6 @@ const Nav = ({
createText={createText}
onCreate={onCreate}
onLoadMore={onLoadMore}
isLoadingMore={isLoadingMore}
/>
</>
)

View File

@@ -14,7 +14,6 @@ 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'
@@ -35,10 +34,9 @@ export type INavSelectorProps = {
isApp?: boolean
onCreate: (state: string) => void
onLoadMore?: () => void
isLoadingMore?: boolean
}
const NavSelector = ({ curNav, navigationItems, createText, isApp, onCreate, onLoadMore, isLoadingMore }: INavSelectorProps) => {
const NavSelector = ({ curNav, navigationItems, createText, isApp, onCreate, onLoadMore }: INavSelectorProps) => {
const { t } = useTranslation()
const router = useRouter()
const { isCurrentWorkspaceEditor } = useAppContext()
@@ -108,11 +106,6 @@ 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">

View File

@@ -19,7 +19,6 @@ const ListWrapper = ({
marketplaceCollections,
marketplaceCollectionPluginsMap,
isLoading,
isFetchingNextPage,
page,
} = useMarketplaceData()
@@ -54,11 +53,6 @@ const ListWrapper = ({
/>
)
}
{
isFetchingNextPage && (
<Loading className="my-3" />
)
}
</div>
)
}

View File

@@ -33,7 +33,7 @@ export function useMarketplaceData() {
}, [isSearchMode, searchPluginText, activePluginType, filterPluginTags, sort])
const pluginsQuery = useMarketplacePlugins(queryParams)
const { hasNextPage, fetchNextPage, isFetching, isFetchingNextPage } = pluginsQuery
const { hasNextPage, fetchNextPage, isFetching } = pluginsQuery
const handlePageChange = useCallback(() => {
if (hasNextPage && !isFetching)
@@ -50,6 +50,5 @@ export function useMarketplaceData() {
pluginsTotal: pluginsQuery.data?.pages[0]?.total,
page: pluginsQuery.data?.pages.length || 1,
isLoading: collectionsQuery.isLoading || pluginsQuery.isLoading,
isFetchingNextPage,
}
}

View File

@@ -144,6 +144,17 @@ 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))

View File

@@ -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,17 +107,12 @@ const PluginsPanel = () => {
<div className="w-full">
<List pluginList={filteredList || []} />
</div>
{!isLastPage && (
<div className="flex justify-center py-4">
{isFetching
? <Loading className="size-8" />
: (
<Button onClick={loadNextPage}>
{t('common.loadMore', { ns: 'workflow' })}
</Button>
)}
</div>
{!isLastPage && !isFetching && (
<Button onClick={loadNextPage}>
{t('common.loadMore', { ns: 'workflow' })}
</Button>
)}
{isFetching && <div className="system-md-semibold text-text-secondary">{t('detail.loading', { ns: 'appLog' })}</div>}
</div>
)
: (

View File

@@ -134,6 +134,13 @@ 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()
@@ -221,6 +228,13 @@ describe('useReadmePanelStore', () => {
// ReadmeEntrance Component Tests
// ================================
describe('ReadmeEntrance', () => {
beforeEach(() => {
vi.clearAllMocks()
// Reset store state
const { setCurrentPluginDetail } = useReadmePanelStore.getState()
setCurrentPluginDetail()
})
// ================================
// Rendering Tests
// ================================
@@ -403,6 +417,11 @@ describe('ReadmeEntrance', () => {
// ================================
describe('ReadmePanel', () => {
beforeEach(() => {
vi.clearAllMocks()
// Reset store state
const { setCurrentPluginDetail } = useReadmePanelStore.getState()
setCurrentPluginDetail()
// Reset mock
mockUsePluginReadme.mockReturnValue({
data: null,
isLoading: false,

View File

@@ -1,11 +1,10 @@
import type { GeneralChunk, ParentChildChunk, QAChunk } from './types'
import type { 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'
@@ -15,7 +14,7 @@ import { QAItemType } from './types'
type ChunkCardProps = {
chunkType: ChunkingMode
parentMode?: ParentMode
content: ParentChildChunk | QAChunk | GeneralChunk
content: string | string[] | QAChunk
positionId?: string | number
wordCount: number
}
@@ -34,7 +33,7 @@ const ChunkCard = (props: ChunkCardProps) => {
const contentElement = useMemo(() => {
if (chunkType === ChunkingMode.parentChild) {
return (content as ParentChildChunk).child_contents.map((child, index) => {
return (content as string[]).map((child, index) => {
const indexForLabel = index + 1
return (
<PreviewSlice
@@ -58,17 +57,7 @@ const ChunkCard = (props: ChunkCardProps) => {
)
}
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
return content as string
}, [content, chunkType])
return (
@@ -84,7 +73,6 @@ const ChunkCard = (props: ChunkCardProps) => {
</div>
)}
<div className="body-md-regular text-text-secondary">{contentElement}</div>
{summaryElement && <SummaryLabel summary={summaryElement} />}
</div>
)
}

View File

@@ -10,13 +10,13 @@ import { QAItemType } from './types'
// Test Data Factories
// =============================================================================
const createGeneralChunks = (overrides: GeneralChunks = []): GeneralChunks => {
const createGeneralChunks = (overrides: string[] = []): GeneralChunks => {
if (overrides.length > 0)
return overrides
return [
{ 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.' },
'This is the first chunk of text content.',
'This is the second chunk with different content.',
'Third chunk here with more text.',
]
}
@@ -152,7 +152,7 @@ describe('ChunkCard', () => {
render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="This is plain text content."
wordCount={27}
positionId={1}
/>,
@@ -196,7 +196,7 @@ describe('ChunkCard', () => {
<ChunkCard
chunkType={ChunkingMode.parentChild}
parentMode="paragraph"
content={createParentChildChunk({ child_contents: childContents })}
content={childContents}
wordCount={50}
positionId={1}
/>,
@@ -218,7 +218,7 @@ describe('ChunkCard', () => {
<ChunkCard
chunkType={ChunkingMode.parentChild}
parentMode="paragraph"
content={createParentChildChunk({ child_contents: ['Child content'] })}
content={['Child content']}
wordCount={13}
positionId={1}
/>,
@@ -234,7 +234,7 @@ describe('ChunkCard', () => {
<ChunkCard
chunkType={ChunkingMode.parentChild}
parentMode="full-doc"
content={createParentChildChunk({ child_contents: ['Child content'] })}
content={['Child content']}
wordCount={13}
positionId={1}
/>,
@@ -250,7 +250,7 @@ describe('ChunkCard', () => {
render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="Text content"
wordCount={12}
positionId={5}
/>,
@@ -268,7 +268,7 @@ describe('ChunkCard', () => {
render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="Some content"
wordCount={1234}
positionId={1}
/>,
@@ -283,7 +283,7 @@ describe('ChunkCard', () => {
render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="Some content"
wordCount={100}
positionId={1}
/>,
@@ -299,7 +299,7 @@ describe('ChunkCard', () => {
<ChunkCard
chunkType={ChunkingMode.parentChild}
parentMode="full-doc"
content={createParentChildChunk({ child_contents: ['Child'] })}
content={['Child']}
wordCount={500}
positionId={1}
/>,
@@ -317,7 +317,7 @@ describe('ChunkCard', () => {
render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="Content"
wordCount={7}
positionId={42}
/>,
@@ -332,7 +332,7 @@ describe('ChunkCard', () => {
render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="Content"
wordCount={7}
positionId="99"
/>,
@@ -347,7 +347,7 @@ describe('ChunkCard', () => {
render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="Content"
wordCount={7}
positionId={3}
/>,
@@ -366,7 +366,7 @@ describe('ChunkCard', () => {
<ChunkCard
chunkType={ChunkingMode.parentChild}
parentMode="paragraph"
content={createParentChildChunk({ child_contents: ['Child'] })}
content={['Child']}
wordCount={5}
positionId={1}
/>,
@@ -380,7 +380,7 @@ describe('ChunkCard', () => {
<ChunkCard
chunkType={ChunkingMode.parentChild}
parentMode="full-doc"
content={createParentChildChunk({ child_contents: ['Child'] })}
content={['Child']}
wordCount={5}
positionId={1}
/>,
@@ -395,7 +395,7 @@ describe('ChunkCard', () => {
const { rerender } = render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="Initial content"
wordCount={15}
positionId={1}
/>,
@@ -408,7 +408,7 @@ describe('ChunkCard', () => {
rerender(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="Updated content"
wordCount={15}
positionId={1}
/>,
@@ -424,7 +424,7 @@ describe('ChunkCard', () => {
const { rerender } = render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content="Text content"
wordCount={12}
positionId={1}
/>,
@@ -458,7 +458,7 @@ describe('ChunkCard', () => {
<ChunkCard
chunkType={ChunkingMode.parentChild}
parentMode="paragraph"
content={createParentChildChunk({ child_contents: [] })}
content={[]}
wordCount={0}
positionId={1}
/>,
@@ -495,7 +495,7 @@ describe('ChunkCard', () => {
render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content={longContent}
wordCount={10000}
positionId={1}
/>,
@@ -510,7 +510,7 @@ describe('ChunkCard', () => {
render(
<ChunkCard
chunkType={ChunkingMode.text}
content={createGeneralChunks()[0]}
content=""
wordCount={0}
positionId={1}
/>,
@@ -546,9 +546,9 @@ describe('ChunkCardList', () => {
)
// Assert
expect(screen.getByText(chunks[0].content)).toBeInTheDocument()
expect(screen.getByText(chunks[1].content)).toBeInTheDocument()
expect(screen.getByText(chunks[2].content)).toBeInTheDocument()
expect(screen.getByText(chunks[0])).toBeInTheDocument()
expect(screen.getByText(chunks[1])).toBeInTheDocument()
expect(screen.getByText(chunks[2])).toBeInTheDocument()
})
it('should render parent-child chunks correctly', () => {
@@ -594,10 +594,7 @@ describe('ChunkCardList', () => {
describe('Memoization - chunkList', () => {
it('should extract chunks from GeneralChunks for text mode', () => {
// Arrange
const chunks: GeneralChunks = [
{ content: 'Chunk 1' },
{ content: 'Chunk 2' },
]
const chunks: GeneralChunks = ['Chunk 1', 'Chunk 2']
// Act
render(
@@ -656,7 +653,7 @@ describe('ChunkCardList', () => {
it('should update chunkList when chunkInfo changes', () => {
// Arrange
const initialChunks = createGeneralChunks([{ content: 'Initial chunk' }])
const initialChunks = createGeneralChunks(['Initial chunk'])
const { rerender } = render(
<ChunkCardList
@@ -669,7 +666,7 @@ describe('ChunkCardList', () => {
expect(screen.getByText('Initial chunk')).toBeInTheDocument()
// Act - update chunks
const updatedChunks = createGeneralChunks([{ content: 'Updated chunk' }])
const updatedChunks = createGeneralChunks(['Updated chunk'])
rerender(
<ChunkCardList
chunkType={ChunkingMode.text}
@@ -687,7 +684,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([{ content: 'Hello' }])
const chunks = createGeneralChunks(['Hello'])
// Act
render(
@@ -750,11 +747,7 @@ describe('ChunkCardList', () => {
describe('Position ID', () => {
it('should assign 1-based position IDs to chunks', () => {
// Arrange
const chunks = createGeneralChunks([
{ content: 'First' },
{ content: 'Second' },
{ content: 'Third' },
])
const chunks = createGeneralChunks(['First', 'Second', 'Third'])
// Act
render(
@@ -775,7 +768,7 @@ describe('ChunkCardList', () => {
describe('Custom className', () => {
it('should apply custom className to container', () => {
// Arrange
const chunks = createGeneralChunks([{ content: 'Test' }])
const chunks = createGeneralChunks(['Test'])
// Act
const { container } = render(
@@ -792,7 +785,7 @@ describe('ChunkCardList', () => {
it('should merge custom className with default classes', () => {
// Arrange
const chunks = createGeneralChunks([{ content: 'Test' }])
const chunks = createGeneralChunks(['Test'])
// Act
const { container } = render(
@@ -812,7 +805,7 @@ describe('ChunkCardList', () => {
it('should render without className prop', () => {
// Arrange
const chunks = createGeneralChunks([{ content: 'Test' }])
const chunks = createGeneralChunks(['Test'])
// Act
const { container } = render(
@@ -867,7 +860,7 @@ describe('ChunkCardList', () => {
it('should not use parentMode for text type', () => {
// Arrange
const chunks = createGeneralChunks([{ content: 'Text' }])
const chunks = createGeneralChunks(['Text'])
// Act
render(
@@ -944,7 +937,7 @@ describe('ChunkCardList', () => {
it('should handle single item in chunks', () => {
// Arrange
const chunks = createGeneralChunks([{ content: 'Single chunk' }])
const chunks = createGeneralChunks(['Single chunk'])
// Act
render(
@@ -961,7 +954,7 @@ describe('ChunkCardList', () => {
it('should handle large number of chunks', () => {
// Arrange
const chunks = Array.from({ length: 100 }, (_, i) => ({ content: `Chunk number ${i + 1}` }))
const chunks = Array.from({ length: 100 }, (_, i) => `Chunk number ${i + 1}`)
// Act
render(
@@ -982,11 +975,8 @@ describe('ChunkCardList', () => {
describe('Key Generation', () => {
it('should generate unique keys for chunks', () => {
// Arrange - chunks with same content
const chunks = createGeneralChunks([
{ content: 'Same content' },
{ content: 'Same content' },
{ content: 'Same content' },
])
const chunks = createGeneralChunks(['Same content', 'Same content', 'Same content'])
// Act
const { container } = render(
<ChunkCardList
@@ -1016,9 +1006,9 @@ describe('ChunkCardList Integration', () => {
it('should render complete text chunking workflow', () => {
// Arrange
const textChunks = createGeneralChunks([
{ content: 'First paragraph of the document.' },
{ content: 'Second paragraph with more information.' },
{ content: 'Final paragraph concluding the content.' },
'First paragraph of the document.',
'Second paragraph with more information.',
'Final paragraph concluding the content.',
])
// Act
@@ -1114,7 +1104,7 @@ describe('ChunkCardList Integration', () => {
describe('Type Switching', () => {
it('should handle switching from text to QA type', () => {
// Arrange
const textChunks = createGeneralChunks([{ content: 'Text content' }])
const textChunks = createGeneralChunks(['Text content'])
const qaChunks = createQAChunks()
const { rerender } = render(
@@ -1142,7 +1132,7 @@ describe('ChunkCardList Integration', () => {
it('should handle switching from text to parent-child type', () => {
// Arrange
const textChunks = createGeneralChunks([{ content: 'Simple text' }])
const textChunks = createGeneralChunks(['Simple text'])
const parentChildChunks = createParentChildChunks()
const { rerender } = render(

View File

@@ -1,4 +1,4 @@
import type { ChunkInfo, GeneralChunk, GeneralChunks, ParentChildChunk, ParentChildChunks, QAChunk, QAChunks } from './types'
import type { ChunkInfo, 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, chunkType])
}, [chunkInfo])
const getWordCount = (seg: GeneralChunk | ParentChildChunk | QAChunk) => {
const getWordCount = (seg: string | 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 GeneralChunk).content.length
return (seg as string).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={seg}
content={chunkType === ChunkingMode.parentChild ? (seg as ParentChildChunk).child_contents : (seg as string | QAChunk)}
wordCount={wordCount}
positionId={index + 1}
/>

View File

@@ -1,12 +1,8 @@
export type GeneralChunk = {
content: string
summary?: string
}
export type GeneralChunks = GeneralChunk[]
export type GeneralChunks = string[]
export type ParentChildChunk = {
child_contents: string[]
parent_content: string
parent_summary?: string
parent_mode: string
}

View File

@@ -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 GeneralChunks
const result = formatPreviewChunks(outputs) as string[]
// RAG_PIPELINE_PREVIEW_CHUNK_NUM is mocked to 5
expect(result).toHaveLength(5)

View File

@@ -5,17 +5,13 @@ 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({
content: chunk.content,
summary: chunk.summary,
})
chunkInfo.push(chunk.content)
})
return chunkInfo
@@ -24,7 +20,6 @@ const formatGeneralChunks = (outputs: any) => {
type ParentChildChunkPreview = {
content: string
child_chunks: string[]
summary?: string
}
const formatParentChildChunks = (outputs: any, parentMode: ParentMode) => {
@@ -37,7 +32,6 @@ 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,
})

View File

@@ -1,7 +1,6 @@
import type {
KnowledgeBaseNodeType,
RerankingModel,
SummaryIndexSetting,
} from '../types'
import type { ValueSelector } from '@/app/components/workflow/types'
import { produce } from 'immer'
@@ -247,16 +246,6 @@ 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,
@@ -271,6 +260,5 @@ export const useConfig = (id: string) => {
handleScoreThresholdChange,
handleScoreThresholdEnabledChange,
handleInputVariableChange,
handleSummaryIndexSettingChange,
}
}

View File

@@ -7,7 +7,6 @@ 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'
@@ -52,7 +51,6 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
handleScoreThresholdChange,
handleScoreThresholdEnabledChange,
handleInputVariableChange,
handleSummaryIndexSettingChange,
} = useConfig(id)
const filterVar = useCallback((variable: Var) => {
@@ -169,22 +167,6 @@ 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}

View File

@@ -42,12 +42,6 @@ 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
@@ -58,5 +52,4 @@ export type KnowledgeBaseNodeType = CommonNodeType & {
retrieval_model: RetrievalSetting
_embeddingModelList?: Model[]
_rerankModelList?: Model[]
summary_index_setting?: SummaryIndexSetting
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,15 +9,30 @@ 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-extra/no-direct-set-state-in-use-effect': 'error',
'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',
},
},
nextjs: true,
@@ -25,7 +40,7 @@ export default antfu(
typescript: {
overrides: {
'ts/consistent-type-definitions': ['error', 'type'],
'ts/no-explicit-any': 'error',
'ts/no-explicit-any': 'warn',
},
},
test: {
@@ -39,11 +54,6 @@ export default antfu(
},
},
},
{
rules: {
'node/prefer-global/process': 'off',
},
},
{
files: ['**/*.ts', '**/*.tsx'],
settings: {
@@ -52,6 +62,32 @@ 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
@@ -142,19 +178,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'],

View File

@@ -196,6 +196,24 @@
"publishApp.notSet": "Not set",
"publishApp.notSetDesc": "Currently nobody can access the web app. Please set permissions.",
"publishApp.title": "Who can access web app",
"quadrantMatrix.deadline": "DDL:",
"quadrantMatrix.invalidData": "Invalid Quadrant Data",
"quadrantMatrix.invalidDataDesc": "Expected JSON format with q1, q2, q3, q4 arrays",
"quadrantMatrix.legend.importance": "I = Importance",
"quadrantMatrix.legend.urgency": "U = Urgency",
"quadrantMatrix.more": "+{{count}} more",
"quadrantMatrix.noTasks": "No tasks",
"quadrantMatrix.q1.subtitle": "Urgent & Important",
"quadrantMatrix.q1.title": "Do First",
"quadrantMatrix.q2.subtitle": "Important & Not Urgent",
"quadrantMatrix.q2.title": "Schedule",
"quadrantMatrix.q3.subtitle": "Urgent & Not Important",
"quadrantMatrix.q3.title": "Delegate",
"quadrantMatrix.q4.subtitle": "Not Urgent & Not Important",
"quadrantMatrix.q4.title": "Don't Do",
"quadrantMatrix.taskCount_one": "{{count}} task prioritized",
"quadrantMatrix.taskCount_other": "{{count}} tasks prioritized",
"quadrantMatrix.title": "Eisenhower Matrix",
"removeOriginal": "Delete the original app",
"roadmap": "See our roadmap",
"showMyCreatedAppsOnly": "Created by me",

View File

@@ -30,7 +30,6 @@
"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",
@@ -75,9 +74,6 @@
"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",
@@ -332,7 +328,5 @@
"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: "
}

View File

@@ -39,12 +39,6 @@
"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"
}

View File

@@ -196,6 +196,24 @@
"publishApp.notSet": "未设置",
"publishApp.notSetDesc": "当前任何人都无法访问 Web 应用。请设置访问权限。",
"publishApp.title": "谁可以访问 web 应用",
"quadrantMatrix.deadline": "截止:",
"quadrantMatrix.invalidData": "无效的象限数据",
"quadrantMatrix.invalidDataDesc": "需要包含 q1, q2, q3, q4 数组的 JSON 格式",
"quadrantMatrix.legend.importance": "I = 重要性",
"quadrantMatrix.legend.urgency": "U = 紧急性",
"quadrantMatrix.more": "+{{count}} 更多",
"quadrantMatrix.noTasks": "暂无任务",
"quadrantMatrix.q1.subtitle": "紧急且重要",
"quadrantMatrix.q1.title": "立即执行",
"quadrantMatrix.q2.subtitle": "重要但不紧急",
"quadrantMatrix.q2.title": "计划安排",
"quadrantMatrix.q3.subtitle": "紧急但不重要",
"quadrantMatrix.q3.title": "委派他人",
"quadrantMatrix.q4.subtitle": "不紧急也不重要",
"quadrantMatrix.q4.title": "不要做",
"quadrantMatrix.taskCount_one": "{{count}} 个任务已排序",
"quadrantMatrix.taskCount_other": "{{count}} 个任务已排序",
"quadrantMatrix.title": "艾森豪威尔矩阵",
"removeOriginal": "删除原应用",
"roadmap": "产品路线图",
"showMyCreatedAppsOnly": "我创建的",

View File

@@ -30,7 +30,6 @@
"list.action.pause": "暂停",
"list.action.resume": "恢复",
"list.action.settings": "分段设置",
"list.action.summary": "生成摘要",
"list.action.sync": "同步",
"list.action.unarchive": "撤销归档",
"list.action.uploadFile": "上传新文件",
@@ -75,9 +74,6 @@
"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": "名称",
@@ -332,7 +328,5 @@
"segment.searchResults_one": "搜索结果",
"segment.searchResults_other": "搜索结果",
"segment.searchResults_zero": "搜索结果",
"segment.summary": "摘要",
"segment.summaryPlaceholder": "写一个简短的摘要,以便更好地检索…",
"segment.vectorHash": "向量哈希:"
}

View File

@@ -39,12 +39,6 @@
"form.retrievalSettings": "检索设置",
"form.save": "保存",
"form.searchModel": "搜索模型",
"form.summaryAutoGen": "摘要自动生成",
"form.summaryAutoGenEnableTip": "启用后,将自动为新添加的文档生成摘要。已有的文档仍可以手动摘要。",
"form.summaryAutoGenTip": "将自动为新添加的文档生成摘要。已有的文档仍可以手动摘要。",
"form.summaryInstructions": "指令",
"form.summaryInstructionsPlaceholder": "描述自动生成摘要的规则或风格…",
"form.summaryModel": "摘要模型",
"form.upgradeHighQualityTip": "一旦升级为高质量模式,将无法切换回经济模式。",
"title": "知识库设置"
}

View File

@@ -42,13 +42,6 @@ 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
@@ -95,7 +88,6 @@ 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 = {
@@ -233,7 +225,7 @@ export type IndexingEstimateResponse = {
total_price: number
currency: string
total_segments: number
preview: Array<{ content: string, child_chunks: string[], summary?: string }>
preview: Array<{ content: string, child_chunks: string[] }>
qa_preview?: QA[]
}
@@ -270,7 +262,6 @@ export type ProcessRuleResponse = {
mode: ProcessMode
rules: Rules
limits: Limits
summary_index_setting?: SummaryIndexSetting
}
export type Rules = {
@@ -401,7 +392,6 @@ export type InitialDocumentDetail = {
total_segments?: number
doc_form: ChunkingMode
doc_language: string
summary_index_status?: string
}
export type SimpleDocumentDetail = InitialDocumentDetail & {
@@ -435,7 +425,6 @@ export type DocumentReq = {
doc_form: ChunkingMode
doc_language: string
process_rule: ProcessRule
summary_index_setting?: SummaryIndexSetting
}
export type CreateDocumentReq = DocumentReq & {
@@ -478,7 +467,6 @@ export type NotionPage = {
export type ProcessRule = {
mode: ProcessMode
rules: Rules
summary_index_setting?: SummaryIndexSetting
}
export type createDocumentResponse = {
@@ -587,7 +575,6 @@ export type SegmentDetailModel = {
error: string | null
stopped_at: number
answer?: string
summary?: string
child_chunks?: ChildChunkDetail[]
updated_at: number
attachments: Attachment[]
@@ -631,7 +618,6 @@ export type HitTesting = {
tsne_position: TsnePosition
child_chunks: HitTestingChildChunk[] | null
files: Attachment[]
summary?: string
}
export type ExternalKnowledgeBaseHitTesting = {
@@ -711,7 +697,6 @@ export type RelatedAppResponse = {
export type SegmentUpdater = {
content: string
answer?: string
summary?: string
keywords?: string[]
regenerate_child_chunks?: boolean
attachment_ids?: string[]
@@ -793,7 +778,6 @@ export enum DocumentActionType {
archive = 'archive',
unArchive = 'un_archive',
delete = 'delete',
summary = 'summary',
}
export type UpdateDocumentBatchParams = {

View File

@@ -28,7 +28,6 @@
"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",

View File

@@ -107,18 +107,6 @@ 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) => {

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
"incremental": true,
"target": "es2022",
"target": "es2015",
"jsx": "preserve",
"lib": [
"dom",

View File

@@ -85,10 +85,6 @@ 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')