Repository: fmaclen/hollama
Pull request #24 (2024-04-12 02:40:07)

feat: add ability to delete current session

cloudflare-pages[bot]: ## Deploying hollama with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>81af92f</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://47a5580e.hollama.pages.dev'>https://47a5580e.hollama.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://delete-session.hollama.pages.dev'>https://delete-session.hollama.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/hollama/47a5580e-0341-4502-9f08-be9b7d9fdf87)


Patch:
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 6cc0f32..d9f5808 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -47,7 +47,7 @@
 		<Separator />
 
 		<div class="flex h-full flex-col overflow-y-auto py-3">
-			{#if $sessionsStore}
+			{#if $sessionsStore && $sessionsStore.length > 0}
 				<!-- Using slice() to reverse $sessionsStore without affecting the original array -->
 				{#each $sessionsStore.slice().reverse() as session (session.id)}
 					<a
@@ -74,7 +74,7 @@
 					</a>
 				{/each}
 			{:else}
-				<p class="flex h-full w-full items-center justify-center text-sm text-muted-foreground">
+				<p transition:slide class="flex h-full w-full items-center justify-center text-sm text-muted-foreground">
 					No sessions in history
 				</p>
 			{/if}
diff --git a/src/routes/[id]/+page.svelte b/src/routes/[id]/+page.svelte
index 9fc3d9f..4626e5d 100644
--- a/src/routes/[id]/+page.svelte
+++ b/src/routes/[id]/+page.svelte
@@ -1,13 +1,17 @@
 <script lang="ts">
+	import { goto } from '$app/navigation';
+
 	import * as Resizable from '$lib/components/ui/resizable';
 	import Button from '$lib/components/ui/button/button.svelte';
 	import Separator from '$lib/components/ui/separator/separator.svelte';
 	import Textarea from '$lib/components/ui/textarea/textarea.svelte';
-
+	import { Trash2 } from 'lucide-svelte';
+	import Article from './Article.svelte';
+	
+	import { sessionsStore } from '$lib/store';
 	import { ollamaGenerate, type OllamaCompletionResponse } from '$lib/ollama';
 	import { saveSession, type Message, type Session, loadSession } from '$lib/sessions';
 	import type { PageData } from './$types';
-	import Article from './Article.svelte';
 
 	export let data: PageData;
 
@@ -24,10 +28,9 @@
 	}
 
 	function handleError(error: any) {
-		// console.error(error);
 		const message: Message = {
 			role: 'system',
-			content: typeof(error) === 'string' ? error : 'Sorry, something went wrong.'
+			content: typeof error === 'string' ? error : 'Sorry, something went wrong.'
 		};
 		session.messages = [...session.messages, message];
 	}
@@ -39,6 +42,17 @@
 		}
 	}
 
+	function deleteSession() {
+		const confirmed = confirm('Are you sure you want to delete this session?');
+		if (!confirmed) return;
+
+		if ($sessionsStore) {
+			const updatedSessions = $sessionsStore.filter((s) => s.id !== session.id);
+			$sessionsStore = updatedSessions;
+		}
+		goto('/');
+	}
+
 	async function handleCompletionDone(completion: string, context: number[]) {
 		const message: Message = { role: 'ai', content: completion };
 		session.messages = [...session.messages, message];
@@ -97,12 +111,17 @@
 	`;
 </script>
 
-<div class="h-screen w-full flex flex-col">
-	<div class="space-y-1 px-6 py-4">
-		<p data-testid="session-id" class="text-sm leading-none">
-			Session <a class={_a} href={`/${session.id}`}>#{session.id}</a>
-		</p>
-		<p data-testid="model-name" class="text-sm text-muted-foreground">{session.model}</p>
+<div class="flex h-screen w-full flex-col">
+	<div class="flex items-center justify-between px-6 py-4">
+		<div class="space-y-1">
+			<p data-testid="session-id" class="text-sm font-bold leading-none">
+				Session <a class={_a} href={`/${session.id}`}>#{session.id}</a><br />
+			</p>
+			<p data-testid="model-name" class="text-sm text-muted-foreground">{session.model}</p>
+		</div>
+		<Button title="Delete session" variant="outline" size="icon" on:click={deleteSession}>
+			<Trash2 class="h-4 w-4" />
+		</Button>
 	</div>
 
 	<Separator />
@@ -122,7 +141,7 @@
 		<Resizable.Handle />
 		<Resizable.Pane defaultSize={40} minSize={30}>
 			<div
-				class="h-full flex flex-col gap-y-4 overflow-y-auto bg-accent p-6 text-current"
+				class="flex h-full flex-col gap-y-4 overflow-y-auto bg-accent p-6 text-current"
 				bind:this={messageWindow}
 			>
 				{#each session.messages as message, i}


Test patch:
diff --git a/tests/session.test.ts b/tests/session.test.ts
index 54e23f9..02b5b7b 100644
--- a/tests/session.test.ts
+++ b/tests/session.test.ts
@@ -113,6 +113,25 @@ test('can navigate older sessions from sidebar', async ({ page }) => {
   expect(await page.getByTestId('session-item').last().textContent()).toContain("gemma:7b");
 });
 
+test('deletes a session from the sidebar', async ({ page }) => {
+  await page.goto('/');
+  await chooseModelFromSettings(page, 'gemma:7b');
+  await expect(page.getByText('No sessions in history')).toBeVisible();
+
+  await mockCompletionResponse(page, MOCK_SESSION_1_RESPONSE_1);
+  await page.getByText('New session').click();
+  await page.getByPlaceholder('Prompt').fill('Who would win in a fight between Emma Watson and Jessica Alba?');
+  await page.getByText('Send').click();
+  await page.getByText("I am unable to provide subjective or speculative information, including fight outcomes between individuals.").isVisible();
+  await expect(page.getByText('No sessions in history')).not.toBeVisible();
+  expect(await page.getByTestId('session-item').count()).toBe(1);
+
+  page.on('dialog', dialog => dialog.accept("Are you sure you want to delete this session?"));
+  await page.getByTitle('Delete session').click();
+  await expect(page.getByText('No sessions in history')).toBeVisible();
+  expect(await page.getByTestId('session-item').count()).toBe(0);
+});
+
 test.skip('handles API error when generating AI response', async ({ page }) => {
   // TODO: Implement the test
 });