mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-10 16:23:44 +00:00
* 💬 feat: Conversation Starters for Model Specs Adds an optional conversation_starters field to model specs in librechat.yaml. When the active conversation uses a spec that defines starters (and no agent/assistant starters apply), the chat landing renders clickable starter prompts between the landing content and the chat input; clicking one submits it as the first message. - data-provider: add conversation_starters to TModelSpec and tModelSpecSchema so the field survives strict config parsing - client: ConversationStarters falls back to the active spec's starters via getModelSpec; entity (agent/assistant) starters take precedence; starter cards are centered, size to content, wrap at word boundaries, stagger their fade-in, and gain a focus-visible ring - sanitizeModelSpecs passes the field through (denylist); covered by a new unit test - e2e: mock spec + tests for rendering, absence, click-to-submit, and the MAX_CONVO_STARTERS cap Closes #3619 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: Sort ChatView imports --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Danny Avila <danny@librechat.ai>
49 lines
1.8 KiB
TypeScript
49 lines
1.8 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
import { messagesView, NEW_CHAT_PATH, replyPrompt, replyText, selectModelSpec } from './helpers';
|
|
|
|
/** Spec with five `conversation_starters` in e2e/config/librechat.e2e.yaml; only four may render. */
|
|
const STARTER_SPEC_LABEL = 'E2E Starters';
|
|
const STARTER_PROMPTS = [
|
|
replyPrompt('starter'),
|
|
'Plan my week',
|
|
'Third starter prompt',
|
|
'Fourth starter prompt',
|
|
];
|
|
const STARTER_BEYOND_CAP = 'Fifth starter beyond the cap';
|
|
|
|
/** A spec without `conversation_starters`. */
|
|
const PLAIN_SPEC_LABEL = 'E2E Soft Default';
|
|
|
|
test.describe('model spec conversation starters', () => {
|
|
test('starter prompts render on the landing for a spec that defines them', async ({ page }) => {
|
|
await page.goto(NEW_CHAT_PATH, { timeout: 10000 });
|
|
await selectModelSpec(page, STARTER_SPEC_LABEL);
|
|
|
|
for (const text of STARTER_PROMPTS) {
|
|
await expect(page.getByRole('button', { name: text })).toBeVisible();
|
|
}
|
|
await expect(page.getByRole('button', { name: STARTER_BEYOND_CAP })).toBeHidden();
|
|
});
|
|
|
|
test('a spec without starters shows none', async ({ page }) => {
|
|
await page.goto(NEW_CHAT_PATH, { timeout: 10000 });
|
|
await selectModelSpec(page, PLAIN_SPEC_LABEL);
|
|
|
|
for (const text of STARTER_PROMPTS) {
|
|
await expect(page.getByRole('button', { name: text })).toBeHidden();
|
|
}
|
|
});
|
|
|
|
test('clicking a starter submits it as the first message', async ({ page }) => {
|
|
await page.goto(NEW_CHAT_PATH, { timeout: 10000 });
|
|
await selectModelSpec(page, STARTER_SPEC_LABEL);
|
|
|
|
const prompt = replyPrompt('starter');
|
|
await page.getByRole('button', { name: prompt }).click();
|
|
|
|
await expect(messagesView(page).getByText(prompt)).toBeVisible({ timeout: 30000 });
|
|
await expect(messagesView(page).getByText(replyText('starter'))).toBeVisible({
|
|
timeout: 30000,
|
|
});
|
|
});
|
|
});
|