feat(web): add session model selector
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-layout": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-model-selector": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-question": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-theme": "workspace:^",
|
||||
|
||||
@@ -37,6 +37,7 @@ const CLIENT_PACKAGES = [
|
||||
'@deepseek-ai/dsh-client-ui-layout',
|
||||
'@deepseek-ai/dsh-client-ui-sidebar',
|
||||
'@deepseek-ai/dsh-client-ui-conversation',
|
||||
'@deepseek-ai/dsh-client-ui-model-selector',
|
||||
'@deepseek-ai/dsh-client-ui-question',
|
||||
'@deepseek-ai/dsh-client-ui-trajectory',
|
||||
] as const
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
{
|
||||
"path": "../../packages/client/ui-conversation"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/ui-model-selector"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/ui-trajectory"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ const PLUGINS: readonly (WebBootEntry & { dir: string })[] = [
|
||||
{ id: '@deepseek-ai/dsh-client-ui-layout', dir: 'ui-layout', url: '/plugins/ui-layout.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-runtime'] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-sidebar', dir: 'ui-sidebar', url: '/plugins/ui-sidebar.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-layout'] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-conversation', dir: 'ui-conversation', url: '/plugins/ui-conversation.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-layout'] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-model-selector', dir: 'ui-model-selector', url: '/plugins/ui-model-selector.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-trajectory', dir: 'ui-trajectory', url: '/plugins/ui-trajectory.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
|
||||
]
|
||||
|
||||
@@ -77,7 +78,7 @@ function titleSurfaces(label: string): { sidebar: string; breadcrumb: string; do
|
||||
return { sidebar, breadcrumb, documentTitle: document.title }
|
||||
}
|
||||
|
||||
it('projects initial and revised durable titles through the built eight-plugin fixture app', async () => {
|
||||
it('projects initial and revised durable titles through the built nine-plugin fixture app', async () => {
|
||||
const root = document.querySelector<HTMLElement>('#root')
|
||||
if (root === null) throw new Error('snapshot root missing')
|
||||
act(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Keyless boot-chain smoke over the REAL carrier: startWebServer + entry
|
||||
// graph (__DSH_BOOT__ web2 shape) injection + built shell dist in a real
|
||||
// chromium. First describe: graph injection + the fail-loud half. Second
|
||||
// describe: the settled success pass — all nine REAL tsdown bundles load
|
||||
// describe: the settled success pass — all ten REAL tsdown bundles load
|
||||
// through the module system + vendored Loader chain in ?fixture mode (the
|
||||
// infrastructure four ride the immediately prefetch tier, the UI rows fetch
|
||||
// on demand), the three-column frame appears in one flip, and the resident
|
||||
@@ -31,6 +31,7 @@ const REAL_PLUGINS: { id: string; dir: string; inject?: string[]; immediately?:
|
||||
{ id: LAYOUT_ID, dir: 'ui-layout', inject: ['@deepseek-ai/dsh-client-runtime'] },
|
||||
{ id: SIDEBAR_ID, dir: 'ui-sidebar', inject: [LAYOUT_ID] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-conversation', dir: 'ui-conversation', inject: [LAYOUT_ID] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-model-selector', dir: 'ui-model-selector', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-question', dir: 'ui-question', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-trajectory', dir: 'ui-trajectory', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
|
||||
]
|
||||
@@ -118,7 +119,7 @@ describe('web boot chain (keyless, real carrier)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('web boot chain success pass (keyless, nine real bundles, ?fixture)', () => {
|
||||
describe('web boot chain success pass (keyless, ten real bundles, ?fixture)', () => {
|
||||
let server: Awaited<ReturnType<typeof startWebServer>>
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
@@ -246,6 +247,37 @@ describe('web boot chain success pass (keyless, nine real bundles, ?fixture)', (
|
||||
expect(await external.getAttribute('rel')).toBe('noopener noreferrer')
|
||||
})
|
||||
|
||||
it('selects a model from a provider group and uses it for the next fixture request', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'smoke-model-selector'))
|
||||
await page.getByRole('button', { name: 'New session', exact: true }).click()
|
||||
const emptyInput = page.locator('textarea[placeholder]')
|
||||
await emptyInput.fill('selector seed')
|
||||
await page.getByRole('button', { name: '发送' }).click()
|
||||
|
||||
const selector = page.getByRole('button', { name: '选择模型,当前 DeepSeek-V4-Flash' })
|
||||
await selector.waitFor({ state: 'visible', timeout: 15_000 })
|
||||
expect(await selector.innerText()).toBe('DeepSeek-V4-Flash')
|
||||
await selector.click()
|
||||
const menu = page.getByRole('menu', { name: '模型' })
|
||||
await menu.waitFor()
|
||||
const triggerBox = await selector.boundingBox()
|
||||
const menuBox = await menu.boundingBox()
|
||||
expect(triggerBox).not.toBeNull()
|
||||
expect(menuBox).not.toBeNull()
|
||||
expect((menuBox?.y ?? 0) + (menuBox?.height ?? 0)).toBeLessThanOrEqual((triggerBox?.y ?? 0) + 1)
|
||||
expect(await page.getByRole('group', { name: 'DeepSeek' }).getByRole('menuitemradio').allTextContents())
|
||||
.toEqual(expect.arrayContaining(['DeepSeek-V4-Flash快速响应', 'DeepSeek-V4-Pro复杂任务']))
|
||||
await page.getByRole('group', { name: 'OpenAI' }).getByRole('menuitemradio', { name: 'GPT-5' }).click()
|
||||
await menu.waitFor({ state: 'detached' })
|
||||
expect(await page.getByRole('button', { name: '选择模型,当前 GPT-5' }).innerText()).toBe('GPT-5')
|
||||
|
||||
const residentInput = page.locator('textarea[placeholder]')
|
||||
await expect.poll(() => residentInput.isEnabled(), { timeout: 15_000 }).toBe(true)
|
||||
await residentInput.fill('report model')
|
||||
await page.getByRole('button', { name: '发送' }).click()
|
||||
await page.getByText('当前模型:openai/gpt-5', { exact: true }).waitFor({ timeout: 15_000 })
|
||||
})
|
||||
|
||||
it('renders and completes the resident question through the composer slot', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'smoke-question-composer'))
|
||||
const sessionTree = page.getByRole('tree', { name: 'Sessions' })
|
||||
|
||||
@@ -144,10 +144,13 @@ async function detailsTrack(page: Page): Promise<number> {
|
||||
return Number(cols.split(' ').pop()!.replace('px', ''))
|
||||
}
|
||||
|
||||
// Readiness gate: `dsh web` serves ALL nine manifest plugins; until every UI
|
||||
// Readiness gate: `dsh web` serves all ten production manifest plugins; until every UI
|
||||
// plugin's client bundle exists and exports apply, the loader fail-louds and
|
||||
// the frame never appears.
|
||||
const UI_PLUGIN_DIRS = ['connection', 'runtime', 'ui-theme', 'i18n', 'ui-layout', 'ui-sidebar', 'ui-conversation', 'ui-question', 'ui-trajectory']
|
||||
const UI_PLUGIN_DIRS = [
|
||||
'connection', 'runtime', 'ui-theme', 'i18n', 'ui-layout', 'ui-sidebar',
|
||||
'ui-conversation', 'ui-model-selector', 'ui-question', 'ui-trajectory',
|
||||
]
|
||||
const ROUND_DONE_MARKER = 'WEB_ROUND_DONE'
|
||||
const notReady = UI_PLUGIN_DIRS.filter((dir) => {
|
||||
const bundle = join(REPO_ROOT, 'packages/client', dir, 'lib/client.js')
|
||||
|
||||
Reference in New Issue
Block a user