mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-20 17:13:44 +00:00
🧬 fix: Resolve URL-Named Model Spec to Its Full Preset on Cold Load (#14610)
* 🧬 fix: Resolve URL-Named Model Spec to Its Full Preset on Cold Load * 🧬 fix: Preserve Hidden Spec Names for Server-Side Resolution
This commit is contained in:
parent
6bbbee7a78
commit
f738810c11
2 changed files with 59 additions and 5 deletions
|
|
@ -166,10 +166,6 @@ export default function ChatRoute() {
|
|||
}
|
||||
|
||||
const getNewConvoPreset = () => {
|
||||
const result = getDefaultModelSpec(startupConfig, endpointsQuery.data);
|
||||
const spec = result?.default ?? result?.last ?? result?.softDefault;
|
||||
const specPreset = spec ? getModelSpecPreset(spec) : undefined;
|
||||
|
||||
const queryParams: Record<string, string> = {};
|
||||
searchParams.forEach((value, key) => {
|
||||
if (key !== 'prompt' && key !== 'q' && key !== 'submit' && key !== 'projectId') {
|
||||
|
|
@ -178,6 +174,18 @@ export default function ChatRoute() {
|
|||
});
|
||||
const querySettings = processValidSettings(queryParams);
|
||||
|
||||
/** A spec named in the URL is an explicit selection: it must resolve to its own
|
||||
* full preset, or stale last-selection state (endpoint/agent) fills the gaps.
|
||||
* Names absent from the client config (e.g. `showInMenu: false`) stay in the
|
||||
* query settings untouched, since they remain resolvable server-side by name. */
|
||||
const urlSpec = querySettings.spec
|
||||
? startupConfig?.modelSpecs?.list?.find((spec) => spec.name === querySettings.spec)
|
||||
: undefined;
|
||||
|
||||
const result = urlSpec ? undefined : getDefaultModelSpec(startupConfig, endpointsQuery.data);
|
||||
const spec = urlSpec ?? result?.default ?? result?.last ?? result?.softDefault;
|
||||
const specPreset = spec ? getModelSpecPreset(spec) : undefined;
|
||||
|
||||
if (Object.keys(querySettings).length > 0) {
|
||||
return mergeQuerySettingsWithSpec(specPreset, querySettings);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { expect, test } from '@playwright/test';
|
||||
import type { TStartupConfig } from 'librechat-data-provider';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { getPrimaryE2EUser } from '../../setup/users.mock';
|
||||
import {
|
||||
NEW_CHAT_PATH,
|
||||
getAccessToken,
|
||||
|
|
@ -13,6 +14,9 @@ import {
|
|||
/** Label of the `softDefault: true` spec in e2e/config/librechat.e2e.yaml. */
|
||||
const SOFT_DEFAULT_LABEL = 'E2E Soft Default';
|
||||
|
||||
/** Name (URL identity) of the `softDefault: true` spec in e2e/config/librechat.e2e.yaml. */
|
||||
const SOFT_DEFAULT_NAME = 'e2e-soft-default';
|
||||
|
||||
/** Ephemeral endpoint from e2e/config/librechat.e2e.yaml with no mirroring spec. */
|
||||
const EPHEMERAL_ENDPOINT = { label: 'Mock Provider C', model: 'mock-model-c' };
|
||||
|
||||
|
|
@ -32,7 +36,7 @@ type AgentResponse = {
|
|||
name?: string | null;
|
||||
};
|
||||
|
||||
async function createAgent(page: Page, name: string): Promise<AgentResponse> {
|
||||
async function createAgent(page: Page, name: string, description?: string): Promise<AgentResponse> {
|
||||
const token = await getAccessToken(page);
|
||||
return requestJson<AgentResponse>(page, {
|
||||
path: '/api/agents',
|
||||
|
|
@ -40,6 +44,7 @@ async function createAgent(page: Page, name: string): Promise<AgentResponse> {
|
|||
method: 'POST',
|
||||
body: {
|
||||
name,
|
||||
...(description ? { description } : {}),
|
||||
provider: 'Mock Provider A',
|
||||
model: 'mock-model-a',
|
||||
model_parameters: {},
|
||||
|
|
@ -226,4 +231,45 @@ test.describe('soft default model spec', () => {
|
|||
await expect(modelTrigger(page)).toContainText(SOFT_DEFAULT_LABEL, { timeout: 15000 });
|
||||
await expect(modelTrigger(page)).not.toHaveText('Select a model');
|
||||
});
|
||||
|
||||
// Regression: two tabs share localStorage — one on the soft default spec, one on a
|
||||
// configured agent. Refreshing the agent tab stamps the agent as the last setup; a
|
||||
// cold load of `/c/new?spec=<name>` then carried only the spec NAME while the
|
||||
// endpoint and agent_id resurfaced from storage, rendering a chimera (spec chip over
|
||||
// an agent landing/composer). A spec named in the URL must resolve to its full preset.
|
||||
test('a spec named in the URL wins over a previously selected agent on a cold load', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.setTimeout(120000);
|
||||
await startFresh(page);
|
||||
await expect(modelTrigger(page)).toContainText(SOFT_DEFAULT_LABEL, { timeout: 15000 });
|
||||
|
||||
const agentName = uniqueName('E2E URL Spec Agent');
|
||||
const agentDescription = 'Powered by E2E Mock';
|
||||
await createAgent(page, agentName, agentDescription);
|
||||
await page.goto(NEW_CHAT_PATH, { timeout: 10000 });
|
||||
await selectAgent(page, agentName);
|
||||
|
||||
// Mirrors refreshing the agent tab: the restored selection is re-stamped as the
|
||||
// last conversation setup.
|
||||
await page.reload({ timeout: 10000 });
|
||||
await expect(modelTrigger(page)).toContainText(agentName, { timeout: 15000 });
|
||||
|
||||
await page.goto(`${NEW_CHAT_PATH}?spec=${SOFT_DEFAULT_NAME}`, { timeout: 10000 });
|
||||
await expect(modelTrigger(page)).toContainText(SOFT_DEFAULT_LABEL, { timeout: 15000 });
|
||||
|
||||
const main = page.getByRole('main');
|
||||
const composer = page.getByRole('textbox', { name: 'Message input' });
|
||||
const user = getPrimaryE2EUser();
|
||||
await expect(composer).toHaveAttribute('placeholder', /Mock Provider A/, { timeout: 15000 });
|
||||
await expect(main).toContainText(user.name, { timeout: 15000 });
|
||||
await expect(main).not.toContainText(agentName);
|
||||
await expect(main).not.toContainText(agentDescription);
|
||||
|
||||
// The mixed state used to be written back to localStorage; a follow-up cold load
|
||||
// of a plain New Chat must stay on the spec, not resurrect the agent.
|
||||
await page.goto(NEW_CHAT_PATH, { timeout: 10000 });
|
||||
await expect(modelTrigger(page)).toContainText(SOFT_DEFAULT_LABEL, { timeout: 15000 });
|
||||
await expect(main).not.toContainText(agentName);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue