fix(knowledge): separate creation management and selection

This commit is contained in:
鲨鱼辣椒 committed 2026-10-13 13:57:35 +08:00
1 parent a589a864d7
commit 8e0e52f30e
11 files changed
+223 -112

No files matched your search

+77 -19
View File
@@ -19,9 +19,7 @@ function Harness({ initial = [], disabled = false, agentSlug = slug }: { initial
return <ConfigurationFields slug={agentSlug} value={value} disabled={disabled} onChange={next => { changes(next); setValue(next); }} />;
}
function choose(id: string) { fireEvent.change(screen.getByRole('combobox', { name: '选择知识库' }), { target: { value: id } }); }
function openUpload() { fireEvent.click(screen.getByText('上传自己的文件')); }
async function openOwn(readyText = '上传第一份文档,让智能体用你的资料回答。') {
openUpload();
await within(screen.getByRole('combobox', { name: '管理的知识库' })).findByRole('option', { name: own.name });
fireEvent.change(screen.getByRole('combobox', { name: '管理的知识库' }), { target: { value: own.kb_id } });
await screen.findByText(readyText);
@@ -47,6 +45,20 @@ it('joins readable IDs to the owned-only catalog and selects multiple libraries
const sharedGroup = await screen.findByRole('group', { name: 'MakeLore知识' });
expect(within(sharedGroup).getByRole('option', { name: '创作指南' })).toBeInTheDocument();
expect(within(screen.getByRole('group', { name: '我的知识库' })).getByRole('option', { name: own.name })).toBeInTheDocument();
const creation = screen.getByRole('region', { name: '上传自己的文件', exact: true });
const management = screen.getByRole('region', { name: '查看我的知识库', exact: true });
const selection = screen.getByRole('region', { name: '选择知识库', exact: true });
expect(within(creation).getByRole('button', { name: '新建知识库', exact: true })).toBeVisible();
expect(within(creation).queryByRole('combobox', { name: '管理的知识库' })).not.toBeInTheDocument();
expect(within(creation).queryByRole('button', { name: '从电脑上传', exact: true })).not.toBeInTheDocument();
expect(within(management).getByRole('combobox', { name: '管理的知识库' })).toBeVisible();
expect(within(management).getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled();
expect(within(management).queryByRole('button', { name: '新建知识库', exact: true })).not.toBeInTheDocument();
expect(within(selection).getByRole('combobox', { name: '选择知识库', exact: true })).toBeVisible();
expect(creation.compareDocumentPosition(management) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(management.compareDocumentPosition(selection) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(screen.queryByText('添加到我的知识库', { exact: true })).not.toBeInTheDocument();
expect(screen.queryByText('选择或新建自己的知识库,再从电脑上传资料。选用会加入右侧配置,保存并应用后生效。', { exact: true })).not.toBeInTheDocument();
expect(screen.getByText('可多选,每次选择一个知识库加入配置。')).toBeVisible();
choose('shared'); choose(own.kb_id); choose('other-shared');
expect(within(screen.getByRole('list', { name: '已选知识库' })).getAllByRole('listitem')).toHaveLength(3);
@@ -60,7 +72,7 @@ it('joins readable IDs to the owned-only catalog and selects multiple libraries
it('never offers shared libraries to document management or enables their upload controls', async () => {
databases = [];
render(<Harness initial={['shared']} />);
await screen.findByText('MakeLore知识 · 只读'); openUpload();
await screen.findByText('MakeLore知识 · 只读');
const manager = screen.getByRole('combobox', { name: '管理的知识库' });
expect(within(manager).queryByRole('option', { name: '创作指南' })).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled();
@@ -76,7 +88,7 @@ it('allows readable binding after an owned-list failure while withholding docume
render(<Harness />);
await screen.findByText('我的知识库目录暂不可用,仍可选用可读取的知识;上传文件请刷新重试。');
expect(screen.queryByRole('group', { name: 'MakeLore知识' })).not.toBeInTheDocument();
choose('shared'); openUpload();
choose('shared');
expect(screen.getByRole('combobox', { name: '管理的知识库' })).toBeDisabled();
expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled();
failed = false;
@@ -99,48 +111,92 @@ it('retains missing IDs for removal during catalog failure instead of treating t
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: [] }));
});
it('enforces the 100-library limit and restores selection after removal', async () => {
it('limits bindings to 100 libraries without preventing browsing or creation', async () => {
const selected = Array.from({ length: 100 }, (_, index) => 'old-' + index);
const original = api.call.getMockImplementation()!;
const created = { kb_id: 'new-own', name: '新资料' };
api.call.mockImplementation((operation, input) => {
if (operation === 'createKnowledge') {
databases.push(created);
catalog.resources.knowledges!.push({ key: created.kb_id, name: created.name });
return Promise.resolve(created);
}
return original(operation, input);
});
render(<Harness initial={selected} />);
await screen.findByRole('group', { name: 'MakeLore知识' });
expect(screen.getByRole('combobox', { name: '选择知识库' })).toBeDisabled();
openUpload(); expect(screen.getByRole('button', { name: '新建知识库' })).toBeDisabled();
await openOwn();
expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeEnabled();
expect(changes).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
fireEvent.change(screen.getByRole('textbox', { name: '知识库名称' }), { target: { value: created.name } });
fireEvent.change(screen.getByRole('combobox', { name: '向量模型' }), { target: { value: 'embedding' } });
expect(screen.getByRole('button', { name: '创建知识库', exact: true })).toBeEnabled();
fireEvent.click(screen.getByRole('button', { name: '创建知识库', exact: true }));
await waitFor(() => expect(screen.getByRole('combobox', { name: '管理的知识库' })).toHaveValue(created.kb_id));
expect(changes).not.toHaveBeenCalled();
expect(screen.getByRole('combobox', { name: '选择知识库' })).toBeDisabled();
fireEvent.click(screen.getByRole('button', { name: '移除知识库 old-0' }));
choose('shared');
await within(screen.getByRole('combobox', { name: '选择知识库' })).findByRole('option', { name: created.name });
choose(created.kb_id);
expect(changes.mock.calls.at(-1)?.[0].knowledges).toHaveLength(100);
expect(changes.mock.calls.at(-1)?.[0].knowledges).toContain('shared');
expect(changes.mock.calls.at(-1)?.[0].knowledges).toContain(created.kb_id);
});
it('selects an existing owned library for uploads and cancelling a file picker does not process a file', async () => {
it('browses an owned library without binding it and only explicit selection changes the draft', async () => {
render(<Harness initial={['shared']} />);
await openOwn();
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: ['shared', own.kb_id] }));
expect(changes).not.toHaveBeenCalled();
expect(screen.queryByRole('button', { name: '移除知识库 ' + own.name })).not.toBeInTheDocument();
api.uploadKnowledge.mockResolvedValue(null);
fireEvent.click(screen.getByRole('button', { name: '从电脑上传', exact: true }));
await waitFor(() => expect(api.uploadKnowledge).toHaveBeenCalledTimes(1));
await waitFor(() => expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeEnabled());
expect(api.call.mock.calls.every(([operation]) => operation !== 'processKnowledge')).toBe(true);
expect(screen.getByRole('button', { name: '移除知识库 创作指南' })).toBeVisible();
expect(changes).not.toHaveBeenCalled();
choose(own.kb_id);
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: ['shared', own.kb_id] }));
});
it('appends a late creation result to the latest bindings after interleaved selection and removal', async () => {
it('keeps an upload failure in the management column while the creation form is open', async () => {
api.uploadKnowledge.mockRejectedValue(new Error('上传结果未确认'));
render(<Harness initial={['shared']} />);
await openOwn();
const creation = screen.getByRole('region', { name: '上传自己的文件', exact: true });
const management = screen.getByRole('region', { name: '查看我的知识库', exact: true });
fireEvent.click(within(creation).getByRole('button', { name: '新建知识库', exact: true }));
fireEvent.change(within(creation).getByRole('textbox', { name: '知识库名称' }), { target: { value: '还没创建的资料' } });
fireEvent.click(within(management).getByRole('button', { name: '从电脑上传', exact: true }));
expect(await within(management).findByRole('alert')).toHaveTextContent('上传结果未确认');
expect(within(creation).queryByRole('alert')).not.toBeInTheDocument();
expect(within(creation).getByRole('textbox', { name: '知识库名称' })).toHaveValue('还没创建的资料');
expect(changes).not.toHaveBeenCalled();
});
it('keeps a late creation separate from interleaved draft changes until explicitly selected', async () => {
catalog.models.push({ id: 'model2', name: '另一个模型' });
let resolve!: (value: CloudKnowledge) => void;
const original = api.call.getMockImplementation()!;
api.call.mockImplementation((operation, input) => operation === 'createKnowledge'
? new Promise(value => { resolve = value; }) : original(operation, input));
render(<Harness initial={['shared']} />);
await screen.findByRole('group', { name: '我的知识库' }); openUpload();
await screen.findByRole('group', { name: '我的知识库' });
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
fireEvent.change(screen.getByRole('textbox', { name: '知识库名称' }), { target: { value: '新资料' } });
fireEvent.change(screen.getByRole('combobox', { name: '向量模型' }), { target: { value: 'embedding' } });
fireEvent.click(screen.getByRole('button', { name: '创建并选用' }));
fireEvent.click(screen.getByRole('button', { name: '创建知识库' }));
choose('other-shared'); fireEvent.change(screen.getByRole('combobox', { name: '模型' }), { target: { value: 'model2' } });
fireEvent.click(screen.getByRole('button', { name: '移除知识库 创作指南' }));
const created = { kb_id: 'new-own', name: '新资料' }; databases.push(created);
catalog.resources.knowledges!.push({ key: created.kb_id, name: created.name });
await act(async () => resolve(created));
await screen.findByRole('button', { name: '移除知识库 新资料' });
await waitFor(() => expect(screen.getByRole('combobox', { name: '管理的知识库' })).toHaveValue(created.kb_id));
expect(screen.queryByRole('button', { name: '移除知识库 新资料' })).not.toBeInTheDocument();
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ model: 'model2', knowledges: ['other-shared'] }));
await within(screen.getByRole('combobox', { name: '选择知识库' })).findByRole('option', { name: created.name });
choose(created.kb_id);
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ model: 'model2', knowledges: ['other-shared', 'new-own'] }));
});
@@ -284,22 +340,24 @@ it('finishes an authorized upload through a failed ownership refresh and does no
expect(api.call.mock.calls.filter(([operation]) => operation === 'processKnowledge')).toHaveLength(1);
});
it('adds an already authorized creation result while the editor is temporarily disabled by saving', async () => {
it('retains an authorized creation for management while saving without altering draft bindings', async () => {
let resolve!: (value: CloudKnowledge) => void;
const original = api.call.getMockImplementation()!;
api.call.mockImplementation((operation, input) => operation === 'createKnowledge'
? new Promise(value => { resolve = value; }) : original(operation, input));
const view = render(<Harness initial={['shared']} />);
await screen.findByRole('group', { name: '我的知识库' }); openUpload();
await screen.findByRole('group', { name: '我的知识库' });
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
fireEvent.change(screen.getByRole('textbox', { name: '知识库名称' }), { target: { value: '新资料' } });
fireEvent.change(screen.getByRole('combobox', { name: '向量模型' }), { target: { value: 'embedding' } });
fireEvent.click(screen.getByRole('button', { name: '创建并选用' }));
fireEvent.click(screen.getByRole('button', { name: '创建知识库' }));
view.rerender(<Harness initial={['shared']} disabled />);
const created = { kb_id: 'new-own', name: '新资料' }; databases.push(created);
catalog.resources.knowledges!.push({ key: created.kb_id, name: created.name });
await act(async () => resolve(created));
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: ['shared', 'new-own'] }));
await waitFor(() => expect(screen.getByRole('combobox', { name: '管理的知识库' })).toHaveValue(created.kb_id));
expect(changes).not.toHaveBeenCalled();
expect(screen.queryByRole('button', { name: '移除知识库 新资料' })).not.toBeInTheDocument();
expect(screen.getByRole('combobox', { name: '选择知识库' })).toBeDisabled();
});
@@ -307,7 +365,7 @@ it('keeps an owned but unusable library manageable without adding an invalid bin
const unusable = { kb_id: 'disabled-embedding', name: '旧资料' }; databases.push(unusable);
render(<Harness initial={['shared', unusable.kb_id]} />);
await screen.findByRole('button', { name: '移除知识库 旧资料' });
expect(screen.getByText('已不可用')).toBeVisible(); openUpload();
expect(screen.getByText('已不可用')).toBeVisible();
const manager = screen.getByRole('combobox', { name: '管理的知识库' });
expect(within(manager).getByRole('option', { name: '旧资料(当前暂不可选用)' })).toBeEnabled();
fireEvent.click(screen.getByRole('button', { name: '移除知识库 旧资料' }));