erinra 0.2.0

Memory MCP server for LLM coding assistants
<script lang="ts">
	import type { Memory } from '$lib/api';
	import { Checkbox } from '$lib/components/ui/checkbox';
	import * as Tooltip from '$lib/components/ui/tooltip';
	import ArchiveIcon from '@lucide/svelte/icons/archive';
	import ArchiveRestoreIcon from '@lucide/svelte/icons/archive-restore';

	interface MemoryListProps {
		memories: Memory[];
		total: number;
		loading?: boolean;
		scores?: Map<string, number>;
		onAction?: (id: string, action: 'archive' | 'unarchive') => void;
		selectable?: boolean;
		selectedIds?: Set<string>;
		onSelectionChange?: (id: string) => void;
		onSelectAll?: () => void;
	}

	let { memories, total, loading = false, scores, onAction, selectable = false, selectedIds, onSelectionChange, onSelectAll }: MemoryListProps = $props();


</script>

{#if loading}
	<div class="flex items-center justify-center py-12">
		<p class="text-gray-400">Loading memories...</p>
	</div>
{:else if memories.length === 0}
	<div class="flex items-center justify-center py-12">
		<p class="text-gray-500">No memories found</p>
	</div>
{:else}
{#if selectable}
	<div class="mb-2 flex items-center gap-2 px-1">
		<Checkbox
			data-testid="select-all"
			checked={memories.length > 0 && memories.every((m) => selectedIds?.has(m.id))}
			onCheckedChange={() => onSelectAll?.()}
		/>
		<span class="text-xs text-gray-400">Select all</span>
	</div>
{/if}
<div class="flex flex-col gap-2" data-testid="memory-list">
	{#each memories as memory (memory.id)}
		<a
			href="/memory/{memory.id}"
			class="group block rounded-lg border border-gray-800 bg-gray-900 px-4 py-3 transition-colors hover:border-gray-700 hover:bg-gray-800{memory.archived_at ? ' opacity-50' : ''}"
			data-testid="memory-item"
		>
			<div class="flex items-start gap-3">
				{#if selectable}
					<Checkbox
						data-testid="select-memory"
						checked={selectedIds?.has(memory.id) ?? false}
						onCheckedChange={() => onSelectionChange?.(memory.id)}
						onclick={(e: MouseEvent) => e.stopPropagation()}
						class="mt-0.5 shrink-0"
					/>
				{/if}
				<p class="flex-1 text-sm text-gray-200 leading-relaxed line-clamp-2 lg:line-clamp-1">{memory.content}</p>
				{#if scores?.has(memory.id)}
					<span class="shrink-0 rounded-md bg-emerald-950 px-2 py-0.5 text-xs font-medium text-emerald-300" data-testid="score">
						{scores.get(memory.id)?.toFixed(2)}
					</span>
				{/if}
				{#if onAction}
					<Tooltip.Root>
						<Tooltip.Trigger
							class="shrink-0 rounded-md p-1 text-gray-400 opacity-0 transition-[opacity,colors] group-hover:opacity-100 hover:bg-gray-700 hover:text-gray-200 focus-visible:opacity-100"
							data-testid="action-btn"
							onclick={(e: MouseEvent) => {
								e.preventDefault();
								e.stopPropagation();
								onAction(memory.id, memory.archived_at ? 'unarchive' : 'archive');
							}}
						>
							{#if memory.archived_at}
								<ArchiveRestoreIcon class="size-4" />
							{:else}
								<ArchiveIcon class="size-4" />
							{/if}
						</Tooltip.Trigger>
						<Tooltip.Portal>
							<Tooltip.Content>
								{memory.archived_at ? 'Unarchive' : 'Archive'}
							</Tooltip.Content>
						</Tooltip.Portal>
					</Tooltip.Root>
				{/if}
			</div>
			<div class="mt-2 flex flex-wrap items-center gap-1.5">
				{#if memory.archived_at}
					<span class="rounded-md bg-amber-950 px-2 py-0.5 text-xs font-medium text-amber-300">
						Archived
					</span>
				{/if}
				{#if memory.memory_type}
					<span class="rounded-md bg-gray-800 px-2 py-0.5 text-xs font-medium text-gray-300">
						{memory.memory_type}
					</span>
				{/if}
				{#each memory.projects as project}
					<span class="rounded-md bg-blue-950 px-2 py-0.5 text-xs font-medium text-blue-300">
						{project}
					</span>
				{/each}
			</div>
		</a>
	{/each}
</div>
{/if}