Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/routes/motd/motd.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

- **Redesigned sidebar navigation** with improved accessibility and mobile experience.
- **Copy & paste images** directly into the prompt field for quick image attachments.
- **Improved stop completion** now preserves partial responses instead of discarding them.

#### Previously, in Hollama

Expand Down
32 changes: 28 additions & 4 deletions src/routes/sessions/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,25 @@
}

function stopCompletion() {
editor.prompt = session.messages[session.messages.length - 1].content; // Reset the prompt to the last sent message
editor.abortController?.abort();

// Add the incomplete message to session if there's any content
if (editor.completion || editor.reasoning) {
const message: Message = {
role: 'assistant',
content: editor.completion || '',
reasoning: editor.reasoning || ''
};
session.messages = [...session.messages, message];
session.updatedAt = new Date().toISOString();
saveSession(session);
}

// Clear editor state
editor.completion = '';
editor.reasoning = '';
editor.isCompletionInProgress = false;
session.messages = session.messages.slice(0, -1); // Remove the "incomplete" AI response
editor.isNewSession = !session.messages.length;
editor.shouldFocusTextarea = true;
}

function handleError(error: Error) {
Expand All @@ -255,7 +268,18 @@
} else {
toast.error($LL.genericError(), { description: error.toString() });
}
stopCompletion();

// For errors, restore the prompt so user can retry
const lastUserMessage = session.messages.filter((m) => m.role === 'user').at(-1);
if (lastUserMessage) {
editor.prompt = lastUserMessage.content;
}

editor.abortController?.abort();
editor.completion = '';
editor.reasoning = '';
editor.isCompletionInProgress = false;
editor.shouldFocusTextarea = true;
}

function handleScroll() {
Expand Down
10 changes: 5 additions & 5 deletions tests/session-interaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,11 @@ test.describe('Session interaction', () => {

await promptTextarea.fill('');
await stopButton.click();
await expect(page.getByText('Write a prompt to start a new session')).toBeVisible();
await expect(userMessage).not.toBeVisible();
await expect(aiMessage).not.toBeVisible();
await expect(page.getByText('Write a prompt to start a new session')).not.toBeVisible();
await expect(userMessage).toBeVisible();
// AI message may not be visible if no content was generated before stopping
await expect(stopButton).not.toBeVisible();
await expect(promptTextarea).toHaveValue('Hello world!');
await expect(promptTextarea).toHaveValue('');
});

test('can toggle the prompt editor fullscreen', async ({ page }) => {
Expand Down Expand Up @@ -578,7 +578,7 @@ test.describe('Session interaction', () => {
// Check that the completion has stopped
await page.goto('/sessions');
const sessionsCount = await page.locator('.session__history').count();
expect(sessionsCount).toBe(0);
expect(sessionsCount).toBe(0); // No session saved since no content was generated
});

test('warns when navigating away with unsaved prompt content', async ({ page }) => {
Expand Down