Pablan, as it stands
Self-hosted knowledge management for SMEs: a split-screen Markdown editor whose sections an LLM refines while you write, and RAG question answering over the documents that result. FastAPI + Postgres/pgvector on the back, SvelteKit on the front, everything OpenAI-compatible and self-hostable. Squashed into a single commit; the development history stays local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
This commit is contained in:
co-authored by
Claude Opus 5
parent
68d3a43191
commit
784b76baf7
@@ -0,0 +1,58 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { login, logout } from './helpers';
|
||||
|
||||
// The landing is the front door: one input that starts a real conversation,
|
||||
// and three chips.
|
||||
|
||||
test.setTimeout(120_000);
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await page.request.post('/api/auth/logout');
|
||||
});
|
||||
|
||||
test('the landing input starts a conversation and streams an answer', async ({ page }) => {
|
||||
await login(page, 'pablo@pablan.dev');
|
||||
|
||||
await page.getByTestId('landing-input').fill('Wie beantrage ich Urlaub?');
|
||||
await page.getByTestId('landing-send').click();
|
||||
|
||||
// Hand-off to the chat page, which sends the question straight away.
|
||||
await expect(page).toHaveURL(/\/chat/);
|
||||
const assistant = page.getByTestId('assistant-message').last();
|
||||
await expect(assistant).toBeVisible({ timeout: 45_000 });
|
||||
// The turn is over once the stop button is gone.
|
||||
await expect(page.getByTestId('stop-button')).toHaveCount(0, { timeout: 90_000 });
|
||||
expect((await assistant.innerText()).trim().length).toBeGreaterThan(40);
|
||||
|
||||
// The conversation shows up in the sidebar.
|
||||
await expect(page.getByTestId('conversation-list')).toContainText('Urlaub');
|
||||
|
||||
// Reloading reopens the same conversation instead of re-asking: the
|
||||
// hand-off parameter is replaced by the conversation's own route.
|
||||
const list = await (await page.request.get('/api/conversations')).json();
|
||||
const created = list.find((c: { title: string }) => c.title?.includes('Urlaub'));
|
||||
expect(created).toBeTruthy();
|
||||
await expect(page).toHaveURL(`/chat/${created.id}`);
|
||||
|
||||
await page.reload();
|
||||
await page.locator('body[data-hydrated]').waitFor();
|
||||
await expect(page.getByTestId('assistant-message')).toHaveCount(1);
|
||||
const after = await (await page.request.get(`/api/conversations/${created.id}`)).json();
|
||||
expect(after.messages).toHaveLength(2); // still one question, one answer
|
||||
|
||||
await page.request.delete(`/api/conversations/${created.id}`);
|
||||
});
|
||||
|
||||
test('the chips lead to capture and documents', async ({ page }) => {
|
||||
await login(page, 'pablo@pablan.dev');
|
||||
|
||||
await page.getByTestId('chip-find').click();
|
||||
await expect(page).toHaveURL(/\/documents$/);
|
||||
|
||||
await page.goBack();
|
||||
await page.locator('body[data-hydrated]').waitFor();
|
||||
await page.getByTestId('chip-capture').click();
|
||||
await expect(page).toHaveURL(/\/documents\/new$/);
|
||||
await expect(page.getByTestId('capture-template').first()).toBeVisible({ timeout: 15_000 });
|
||||
await logout(page);
|
||||
});
|
||||
Reference in New Issue
Block a user