fix: 完善知识库向量模型配置提示与刷新

This commit is contained in:
2026-09-12 20:35:11 +08:00
parent a0b869a333
commit 3507f70e11
5 changed files with 181 additions and 9 deletions

View File

@@ -12,6 +12,7 @@ test('personal cloud Agent creation, draft save and leave protection in Electron
BrowserWindow.getAllWindows()[0].setSize(1366, 768);
let draft: Record<string, unknown> | null = null;
let recent: unknown = null;
let knowledgeCatalogLoads = 0;
let limits: { request_limit_points: string | null; daily_limit_points: string | null } = { request_limit_points: null, daily_limit_points: null };
const jobs: Record<string, unknown>[] = [];
const configuration = { model: '', tools: [], knowledges: [], mcps: [], skills: [], preload_skills: [], subagents: [], tool_approval_mode: 'default', max_execution_steps: 40, max_output_tokens: 4096, max_run_seconds: 600 };
@@ -37,7 +38,7 @@ test('personal cloud Agent creation, draft save and leave protection in Electron
if (path.endsWith('/actions')) {
const input = body.input ?? {};
if (body.operation === 'catalog') return result({ models: [{ id: 'test-model', name: '创作模型' }], resources: {}, pricing: null });
if (body.operation === 'knowledge') return result({ databases: [], models: [] });
if (body.operation === 'knowledge') return result({ databases: [], models: ++knowledgeCatalogLoads === 1 ? [] : [{ id: 'fixture:test-embedding', name: '资料向量模型' }] });
if (body.operation === 'resources') return result({ mcps: [], skills: [], subagents: [] });
if (body.operation === 'budget' || body.operation === 'saveBudget') {
if (body.operation === 'saveBudget') limits = input;
@@ -90,6 +91,22 @@ test('personal cloud Agent creation, draft save and leave protection in Electron
await page.getByRole('button', { name: '保存草稿', exact: true }).click();
await expect(page.getByRole('button', { name: '已保存', exact: true })).toBeVisible();
await expect(page.getByTestId('titlebar-page-title')).toHaveText('智能体工作区');
await page.getByRole('button', { name: '知识', exact: true }).click();
await page.getByText('管理我的知识文档', { exact: true }).click();
await page.getByRole('button', { name: '新建知识库', exact: true }).click();
await expect(page.getByLabel('向量模型', { exact: true })).toBeDisabled();
await page.getByText('管理员配置说明', { exact: true }).click();
await expect(page.getByText(/Yuxi 后台/)).toContainText('智能体管理 → 模型供应商');
await expect(page.getByText(/Works Square 运营后台/)).toBeVisible();
await page.getByLabel('知识库名称', { exact: true }).fill('我的创作资料');
await page.screenshot({ path: testInfo.outputPath('knowledge-empty.png') });
await page.getByRole('button', { name: '刷新知识库与模型', exact: true }).click();
await page.getByLabel('向量模型', { exact: true }).selectOption('fixture:test-embedding');
await expect(page.getByLabel('知识库名称', { exact: true })).toHaveValue('我的创作资料');
await expect(page.getByRole('button', { name: '创建并选用', exact: true })).toBeEnabled();
await expect(page.getByText('管理员配置说明', { exact: true })).toHaveCount(0);
await page.getByLabel('知识库名称', { exact: true }).fill('');
await page.getByRole('button', { name: '新建知识库', exact: true }).click();
await page.getByRole('button', { name: '指令', exact: true }).click();
const preview = page.getByRole('region', { name: '草稿预览', exact: true });
await preview.getByLabel('消息').fill('先给我一些创作建议');

View File

@@ -0,0 +1,86 @@
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { CloudKnowledgePanel } from '@/pages/CloudAgents/CloudKnowledge';
const api = vi.hoisted(() => ({ call: vi.fn() }));
vi.mock('@/lib/cloud-agents-api', () => ({ cloudAgentsApi: api }));
const slug = 'ml-' + 'a'.repeat(32);
const empty = { databases: [], models: [] };
const available = { ...empty, models: [{ id: 'relay:fixture-embedding', name: '资料向量模型' }] };
beforeEach(() => vi.resetAllMocks());
afterEach(cleanup);
function openCreation() {
render(<CloudKnowledgePanel slug={slug} onCreated={() => undefined} />);
fireEvent.click(screen.getByText('管理我的知识文档'));
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
}
it('explains who configures an empty embedding catalog and refreshes without losing the name', async () => {
api.call.mockResolvedValueOnce(empty).mockResolvedValueOnce(available);
openCreation();
expect(await screen.findByText('暂时没有可用的向量模型。请联系平台管理员完成云端配置,然后刷新。')).toBeVisible();
expect(screen.getByRole('combobox', { name: '向量模型' })).toBeDisabled();
fireEvent.click(screen.getByText('管理员配置说明'));
expect(screen.getByText(/Yuxi 后台/)).toHaveTextContent('智能体管理 → 模型供应商');
expect(screen.getByText(/Works Square 运营后台/)).toHaveTextContent('模型管理');
fireEvent.change(screen.getByRole('textbox', { name: '知识库名称' }), { target: { value: '创作资料' } });
fireEvent.click(screen.getByRole('button', { name: '刷新知识库与模型' }));
expect(await screen.findByRole('option', { name: '资料向量模型' })).toBeInTheDocument();
expect(screen.getByRole('textbox', { name: '知识库名称' })).toHaveValue('创作资料');
expect(screen.queryByText('管理员配置说明')).not.toBeInTheDocument();
fireEvent.change(screen.getByRole('combobox', { name: '向量模型' }), { target: { value: 'relay:fixture-embedding' } });
expect(screen.getByRole('button', { name: '创建并选用' })).toBeEnabled();
});
it('shows loading instead of suggesting configuration is missing before the catalog arrives', () => {
api.call.mockReturnValue(new Promise(() => undefined));
openCreation();
expect(screen.getByRole('combobox', { name: '向量模型' })).toBeDisabled();
expect(screen.getByRole('option', { name: '正在加载向量模型…' })).toBeInTheDocument();
expect(screen.queryByText('管理员配置说明')).not.toBeInTheDocument();
});
it('lets a failed catalog load retry in place and clears its error after success', async () => {
api.call.mockRejectedValueOnce(new Error('目录连接失败')).mockResolvedValueOnce(available);
openCreation();
expect(await screen.findByRole('alert')).toHaveTextContent('目录连接失败');
expect(screen.getByRole('combobox', { name: '向量模型' })).toBeDisabled();
fireEvent.click(screen.getByRole('button', { name: '刷新知识库与模型' }));
expect(await screen.findByRole('option', { name: '资料向量模型' })).toBeInTheDocument();
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
});
it('clears a removed selection after refresh so a new create cannot use an unavailable model', async () => {
api.call.mockResolvedValueOnce(available).mockResolvedValueOnce(empty);
openCreation();
await screen.findByRole('option', { name: '资料向量模型' });
fireEvent.change(screen.getByRole('textbox', { name: '知识库名称' }), { target: { value: '创作资料' } });
fireEvent.change(screen.getByRole('combobox', { name: '向量模型' }), { target: { value: 'relay:fixture-embedding' } });
fireEvent.click(screen.getByRole('button', { name: '刷新知识库与模型' }));
await waitFor(() => expect(screen.getByRole('combobox', { name: '向量模型' })).toHaveValue(''));
expect(screen.getByRole('button', { name: '创建并选用' })).toBeDisabled();
expect(api.call.mock.calls.every(([operation]) => operation === 'knowledge')).toBe(true);
});
it('preserves an uncertain creation intent when its model disappears on refresh', async () => {
let loads = 0;
api.call.mockImplementation(async operation => {
if (operation === 'knowledge') return ++loads === 1 ? available : empty;
throw new Error('创建结果未确认');
});
openCreation();
await screen.findByRole('option', { name: '资料向量模型' });
fireEvent.change(screen.getByRole('textbox', { name: '知识库名称' }), { target: { value: '创作资料' } });
fireEvent.change(screen.getByRole('combobox', { name: '向量模型' }), { target: { value: 'relay:fixture-embedding' } });
fireEvent.click(screen.getByRole('button', { name: '创建并选用' }));
await screen.findByRole('alert');
fireEvent.click(screen.getByRole('button', { name: '刷新知识库与模型' }));
await screen.findByText('管理员配置说明');
fireEvent.click(screen.getByRole('button', { name: '重试创建' }));
await waitFor(() => expect(api.call.mock.calls.filter(([operation]) => operation === 'createKnowledge')).toHaveLength(2));
const attempts = api.call.mock.calls.filter(([operation]) => operation === 'createKnowledge');
expect(attempts[0][1]).toEqual(attempts[1][1]);
expect(attempts[1][1]).toMatchObject({ slug, name: '创作资料', embedding_model: 'relay:fixture-embedding' });
});