mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-13 10:15:21 +00:00
chunk bug
This commit is contained in:
1 parent
ac7bbd811f
commit
208d34abc7
6 files changed
+99
-18
No files matched your search
@@ -495,7 +495,8 @@ describe('Chunks', () => {
|
||||
ok: true,
|
||||
json: async () => ({ chunk_id: 'c1-new' }),
|
||||
}));
|
||||
await render({ embedded: true });
|
||||
const onOpenChunkChange = vi.fn();
|
||||
await render({ embedded: true, onOpenChunkChange });
|
||||
await act(async () => tile()!.click());
|
||||
await act(async () => buttonByText('modals.chunk.edit')!.click());
|
||||
// After the save, the open position holds nothing (or another chunk).
|
||||
@@ -509,6 +510,8 @@ describe('Chunks', () => {
|
||||
expect(container.querySelector('h2')?.textContent).toBe('Rewritten');
|
||||
expect(container.textContent).toContain('chunkPositionUnplaced');
|
||||
expect(buttonByLabel('settings.sources.nextChunk')!.disabled).toBe(true);
|
||||
// An embedding host draws the crumb: still open, but with no number.
|
||||
expect(onOpenChunkChange).toHaveBeenLastCalledWith('unplaced');
|
||||
});
|
||||
|
||||
it('deleting the only chunk on the last page lands on the page before', async () => {
|
||||
|
||||
@@ -35,6 +35,13 @@ import { Pagination } from './ui/pagination';
|
||||
/** Chunks per page: divisible by 2, 3 and 4 columns, so a page fills the grid. */
|
||||
const PAGE_SIZE_OPTIONS = [12, 24, 48];
|
||||
|
||||
/**
|
||||
* Where the open chunk is, as reported to an embedding host: its 1-based
|
||||
* position, 'unplaced' while it is open but its place in the list is unknown
|
||||
* (a save moved it off every probed position), or null while the grid shows.
|
||||
*/
|
||||
export type OpenChunkPosition = number | 'unplaced' | null;
|
||||
|
||||
/** Lets the host's crumbs close the open chunk (see TreeBrowser). */
|
||||
export interface ChunksController {
|
||||
closeChunk: () => void;
|
||||
@@ -79,8 +86,8 @@ interface ChunksProps {
|
||||
* (`onOpenChunkChange`) and close it (`controllerRef`).
|
||||
*/
|
||||
embedded?: boolean;
|
||||
/** The open chunk's position (1-based), or null while the grid shows. */
|
||||
onOpenChunkChange?: (position: number | null) => void;
|
||||
/** Where the open chunk is; see {@link OpenChunkPosition}. */
|
||||
onOpenChunkChange?: (position: OpenChunkPosition) => void;
|
||||
controllerRef?: React.MutableRefObject<ChunksController | null>;
|
||||
}
|
||||
|
||||
@@ -296,7 +303,11 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
closeChunk: () => closeChunk(),
|
||||
}));
|
||||
|
||||
const openChunkPosition = openChunk ? openPosition : null;
|
||||
const openChunkPosition: OpenChunkPosition = !openChunk
|
||||
? null
|
||||
: positionLost
|
||||
? 'unplaced'
|
||||
: openPosition;
|
||||
const onOpenChunkChangeRef = useRef(onOpenChunkChange);
|
||||
useEffect(() => {
|
||||
onOpenChunkChangeRef.current = onOpenChunkChange;
|
||||
|
||||
@@ -73,13 +73,13 @@ vi.mock('../Chunks', async () => {
|
||||
default: (props: {
|
||||
embedded?: boolean;
|
||||
documentId: string;
|
||||
onOpenChunkChange?: (position: number | null) => void;
|
||||
onOpenChunkChange?: (position: number | 'unplaced' | null) => void;
|
||||
controllerRef?: { current: { closeChunk: () => boolean } | null };
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState<false | 2 | 'unplaced'>(false);
|
||||
const { onOpenChunkChange, controllerRef } = props;
|
||||
useEffect(() => {
|
||||
onOpenChunkChange?.(open ? 2 : null);
|
||||
onOpenChunkChange?.(open || null);
|
||||
}, [open, onOpenChunkChange]);
|
||||
if (controllerRef) {
|
||||
controllerRef.current = {
|
||||
@@ -92,9 +92,12 @@ vi.mock('../Chunks', async () => {
|
||||
return (
|
||||
<div data-testid="chunks" data-doc={props.documentId}>
|
||||
{props.embedded ? 'embedded' : 'page'}
|
||||
<button type="button" onClick={() => setOpen(true)}>
|
||||
<button type="button" onClick={() => setOpen(2)}>
|
||||
OPEN CHUNK
|
||||
</button>
|
||||
<button type="button" onClick={() => setOpen('unplaced')}>
|
||||
LOSE PLACE
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -463,6 +466,22 @@ describe('GraphSourceView', () => {
|
||||
).find((b) => b.textContent === 'Key Accounts')!;
|
||||
await act(async () => source.click());
|
||||
expect(crumbs()).toEqual(['settings.sources.label', 'Key Accounts']);
|
||||
|
||||
// A saved chunk whose place is unknown keeps an unnumbered crumb, and the
|
||||
// source crumb still closes it.
|
||||
await act(async () => buttonByText('LOSE PLACE')!.click());
|
||||
expect(crumbs()).toEqual([
|
||||
'settings.sources.label',
|
||||
'Key Accounts',
|
||||
'settings.sources.chunkCrumbUnplaced',
|
||||
]);
|
||||
const again = Array.from(
|
||||
container.querySelectorAll<HTMLButtonElement>(
|
||||
'[data-slot="breadcrumb-link"]',
|
||||
),
|
||||
).find((b) => b.textContent === 'Key Accounts')!;
|
||||
await act(async () => again.click());
|
||||
expect(crumbs()).toEqual(['settings.sources.label', 'Key Accounts']);
|
||||
});
|
||||
|
||||
it('uses the connector tree for a connector source', async () => {
|
||||
|
||||
@@ -13,7 +13,10 @@ import { useSelector } from 'react-redux';
|
||||
import userService from '../../api/services/userService';
|
||||
import { selectToken } from '../../preferences/preferenceSlice';
|
||||
import { formatCount } from '../../utils/dateTimeUtils';
|
||||
import Chunks, { type ChunksController } from '../Chunks';
|
||||
import Chunks, {
|
||||
type ChunksController,
|
||||
type OpenChunkPosition,
|
||||
} from '../Chunks';
|
||||
import ConnectorTree from '../ConnectorTree';
|
||||
import FileTree from '../FileTree';
|
||||
import GraphView, { type GraphLoadStatus } from '../GraphView';
|
||||
@@ -80,9 +83,8 @@ export default function GraphSourceView({
|
||||
// header shows it while that tab is open.
|
||||
const [filesCrumbs, setFilesCrumbs] = useState<Crumb[]>([]);
|
||||
// A flat source's open chunk (its crumb), reported by the chunk list.
|
||||
const [openChunkPosition, setOpenChunkPosition] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const [openChunkPosition, setOpenChunkPosition] =
|
||||
useState<OpenChunkPosition>(null);
|
||||
const chunksControllerRef = useRef<ChunksController | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -157,7 +159,10 @@ export default function GraphSourceView({
|
||||
...(openChunkPosition !== null
|
||||
? [
|
||||
{
|
||||
label: t('settings.sources.chunkCrumb', { n: openChunkPosition }),
|
||||
label:
|
||||
openChunkPosition === 'unplaced'
|
||||
? t('settings.sources.chunkCrumbUnplaced')
|
||||
: t('settings.sources.chunkCrumb', { n: openChunkPosition }),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
@@ -42,6 +42,10 @@ vi.mock('../../api/services/userService', () => ({
|
||||
chunks: state.chunks,
|
||||
}),
|
||||
})),
|
||||
updateChunk: vi.fn(async () => ({
|
||||
ok: true,
|
||||
json: async () => ({ chunk_id: 'c1' }),
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -252,6 +256,40 @@ describe('TreeBrowser', () => {
|
||||
expect(crumbs()).toEqual([SOURCES, 'Contracts', 'readme.md']);
|
||||
});
|
||||
|
||||
it('a saved chunk whose place is unknown keeps an unnumbered crumb', async () => {
|
||||
state.chunks = [{ doc_id: 'c1', text: '# Rates', metadata: {} }];
|
||||
await render(NESTED);
|
||||
await openRow('readme.md');
|
||||
await act(async () => tile()!.click());
|
||||
const button = (text: string) =>
|
||||
Array.from(document.body.querySelectorAll('button')).find(
|
||||
(el) => el.textContent?.trim() === text,
|
||||
)!;
|
||||
await act(async () => button('modals.chunk.edit').click());
|
||||
// After the save, the probed positions no longer hold the chunk.
|
||||
state.chunks = [];
|
||||
const field = document.body.querySelector<HTMLTextAreaElement>(
|
||||
'[role="dialog"] textarea',
|
||||
)!;
|
||||
await act(async () => {
|
||||
Object.getOwnPropertyDescriptor(
|
||||
HTMLTextAreaElement.prototype,
|
||||
'value',
|
||||
)!.set!.call(field, '# Rates edited');
|
||||
field.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
});
|
||||
await act(async () => button('modals.chunk.save').click());
|
||||
expect(crumbs()).toEqual([
|
||||
SOURCES,
|
||||
'Contracts',
|
||||
'readme.md',
|
||||
'settings.sources.chunkCrumbUnplaced',
|
||||
]);
|
||||
|
||||
await clickCrumb('readme.md');
|
||||
expect(crumbs()).toEqual([SOURCES, 'Contracts', 'readme.md']);
|
||||
});
|
||||
|
||||
it('a one-file source opens straight on its chunk list', async () => {
|
||||
await render({ 'report.pdf': { type: 'pdf', display_name: 'Report' } });
|
||||
expect(
|
||||
|
||||
@@ -17,7 +17,10 @@ import { Eye, File, Folder } from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
import { EmptyState } from '../ui/empty-state';
|
||||
import { useLoaderState } from '../../hooks';
|
||||
import Chunks, { type ChunksController } from '../Chunks';
|
||||
import Chunks, {
|
||||
type ChunksController,
|
||||
type OpenChunkPosition,
|
||||
} from '../Chunks';
|
||||
import PathHeader, { type Crumb } from './PathHeader';
|
||||
import SourceNavigator from './SourceNavigator';
|
||||
import SkeletonLoader from '../SkeletonLoader';
|
||||
@@ -207,9 +210,8 @@ const TreeBrowser: React.FC<TreeBrowserProps> = ({
|
||||
} | null>(null);
|
||||
const mountedRef = useRef(true);
|
||||
// The open file's open chunk (its crumb), reported by Chunks.
|
||||
const [openChunkPosition, setOpenChunkPosition] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const [openChunkPosition, setOpenChunkPosition] =
|
||||
useState<OpenChunkPosition>(null);
|
||||
const chunksControllerRef = useRef<ChunksController | null>(null);
|
||||
|
||||
useEffect(
|
||||
@@ -587,7 +589,10 @@ const TreeBrowser: React.FC<TreeBrowserProps> = ({
|
||||
...(chunkOpen
|
||||
? [
|
||||
{
|
||||
label: t('settings.sources.chunkCrumb', { n: openChunkPosition }),
|
||||
label:
|
||||
openChunkPosition === 'unplaced'
|
||||
? t('settings.sources.chunkCrumbUnplaced')
|
||||
: t('settings.sources.chunkCrumb', { n: openChunkPosition }),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
Reference in new issue
Block a user