diff --git a/docs/chat-chain-changes/2026-10-25-agent-profile-picker.md b/docs/chat-chain-changes/2026-10-25-agent-profile-picker.md new file mode 100644 index 0000000..c2391b8 --- /dev/null +++ b/docs/chat-chain-changes/2026-10-25-agent-profile-picker.md @@ -0,0 +1,10 @@ +--- +date: 2026-10-25 +pr: pending +feature: New chat Agent profile picker +impact: The Agent popup shows more profiles using a viewport-bounded height and supports searching by profile name. Model/provider pickers and chat events are unchanged. +--- + +The new-chat profile list uses up to 480px or 60% of the dynamic viewport height, +whichever is smaller. Browser checks cover 20 profiles, search/selection and popup +bounds on desktop, portrait mobile and short landscape screens. diff --git a/packages/client/src/components/hermes/chat/ChatPanel.vue b/packages/client/src/components/hermes/chat/ChatPanel.vue index 6b61625..c5c955a 100644 --- a/packages/client/src/components/hermes/chat/ChatPanel.vue +++ b/packages/client/src/components/hermes/chat/ChatPanel.vue @@ -1444,6 +1444,8 @@ async function handleSessionModelCustomSubmit() { :model-value="newChatProfile" :options="newChatProfileOptions" :loading="newChatLoading || profilesStore.loading" + :trigger-props="{ contentClass: 'new-chat-profile-popup' }" + allow-search @change="handleNewChatProfileChange" /> @@ -2158,6 +2160,10 @@ async function handleSessionModelCustomSubmit() { } } +:global(.new-chat-profile-popup .arco-select-dropdown-list-wrapper) { + max-height: min(480px, 60dvh); +} + .new-chat-form { display: flex; flex-direction: column; diff --git a/tests/e2e/chat-input-dropdown.spec.ts b/tests/e2e/chat-input-dropdown.spec.ts index 6500ffb..433ed55 100644 --- a/tests/e2e/chat-input-dropdown.spec.ts +++ b/tests/e2e/chat-input-dropdown.spec.ts @@ -100,3 +100,42 @@ test('new chat selectors stay open after clicking their field and allow selectio await expect(drawer).toBeVisible() } }) + + +for (const viewport of [{ width: 1440, height: 900 }, { width: 390, height: 844 }, { width: 844, height: 390 }]) { + test(`Agent selector shows more profiles and supports searching at ${viewport.width}x${viewport.height}`, async ({ page }, testInfo) => { + await page.setViewportSize(viewport) + await authenticate(page, TEST_ACCESS_KEY, 'research') + await mockHermesApi(page) + await page.route(url => url.pathname === '/api/hermes/profiles', route => route.fulfill({ + json: { profiles: ['research', ...Array.from({ length: 19 }, (_, index) => `profile-${String(index + 1).padStart(2, '0')}`)].map(name => ({ name, model: 'test-model', alias: '', active: name === 'research' })) }, + })) + await mockChatSocket(page) + await page.goto('/#/hermes/chat') + if (viewport.width <= 768) await page.locator('.hamburger-btn').click() + await page.getByRole('button', { name: 'New Chat', exact: true }).click() + const selector = page.locator('.new-chat-drawer .new-chat-field > .arco-select').first() + await selector.click({ position: { x: 8, y: 16 } }) + const popup = page.locator('.new-chat-profile-popup') + await expect(popup).toBeVisible() + const list = popup.locator('.arco-select-dropdown-list-wrapper') + await expect.poll(() => list.evaluate(element => element.clientHeight)).toBeGreaterThan(viewport.height > 600 ? 350 : 200) + const bounds = (await popup.boundingBox())! + expect(bounds.y).toBeGreaterThanOrEqual(0) + expect(bounds.y + bounds.height).toBeLessThanOrEqual(viewport.height) + const visibleOptions = await list.evaluate(element => { + const bounds = element.getBoundingClientRect() + return [...element.querySelectorAll('.arco-select-option')].filter(option => { + const rect = option.getBoundingClientRect() + return rect.top >= bounds.top && rect.bottom <= bounds.bottom + }).length + }) + expect(visibleOptions).toBeGreaterThanOrEqual(viewport.height > 600 ? 10 : 5) + if (viewport.width === 390) await page.screenshot({ path: testInfo.outputPath('agent-profile-picker-mobile.png'), animations: 'disabled' }) + await selector.locator('input').fill('profile-19') + await expect(popup.locator('.arco-select-option')).toHaveCount(1) + await popup.locator('.arco-select-option', { hasText: 'profile-19' }).click() + await expect(selector).toContainText('profile-19') + await expect(popup).toBeHidden() + }) +}