From c50b39fe24c578eab92ed9d0a0c5831a4f8804ce Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 17 Nov 2025 08:04:14 +0530 Subject: [PATCH 01/21] Bump flowbite-svelte for VirtualLists --- apps/frontend/package.json | 2 +- pnpm-lock.yaml | 93 +++++++++++++++++++------------------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index a415fe6..3ca6462 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -38,7 +38,7 @@ "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^3.0.0", "flowbite": "^3.1.2", - "flowbite-svelte": "^1.5.3", + "flowbite-svelte": "^1.28.0", "flowbite-svelte-blocks": "^2.0.0", "flowbite-svelte-icons": "^2.2.0", "globals": "^16.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1730852..087a322 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,11 +78,11 @@ importers: specifier: ^3.1.2 version: 3.1.2(rollup@4.45.1) flowbite-svelte: - specifier: ^1.5.3 - version: 1.10.19(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11) + specifier: ^1.28.0 + version: 1.28.0(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11) flowbite-svelte-blocks: specifier: ^2.0.0 - version: 2.0.0(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(flowbite-svelte-icons@2.2.1(svelte@5.36.14))(flowbite-svelte@1.10.19(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11))(flowbite@3.1.2(rollup@4.45.1))(svelte@5.36.14)(tailwind-merge@3.3.1)(tailwindcss@4.1.11) + version: 2.0.0(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(flowbite-svelte-icons@2.2.1(svelte@5.36.14))(flowbite-svelte@1.28.0(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11))(flowbite@3.1.2(rollup@4.45.1))(svelte@5.36.14)(tailwind-merge@3.3.1)(tailwindcss@4.1.11) flowbite-svelte-icons: specifier: ^2.2.0 version: 2.2.1(svelte@5.36.14) @@ -496,11 +496,11 @@ packages: resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@floating-ui/core@1.7.2': - resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} - '@floating-ui/dom@1.7.2': - resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==} + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} @@ -1149,8 +1149,8 @@ packages: resolution: {integrity: sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==} engines: {node: '>= 0.8.0'} - '@svgdotjs/svg.js@3.2.4': - resolution: {integrity: sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==} + '@svgdotjs/svg.js@3.2.5': + resolution: {integrity: sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ==} '@svgdotjs/svg.resize.js@2.0.5': resolution: {integrity: sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==} @@ -1502,8 +1502,8 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - apexcharts@4.7.0: - resolution: {integrity: sha512-iZSrrBGvVlL+nt2B1NpqfDuBZ9jX61X9I2+XV0hlYXHtTwhwLTHDKGXjNXAgFBDLuvSYCB/rq2nPWVPRv2DrGA==} + apexcharts@5.3.6: + resolution: {integrity: sha512-sVEPw+J0Gp0IHQabKu8cfdsxlfME0e36Wid7RIaPclGM2OUt+O7O4+6mfAmTUYhy5bDk8cNHzEhPfVtLCIXEJA==} argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1927,10 +1927,10 @@ packages: peerDependencies: svelte: ^5.0.0 - flowbite-svelte@1.10.19: - resolution: {integrity: sha512-Chr/sNT8yxEYaNwJtPBk7FAF1iVaEpE9XeoRgQfqfBy1G0eS2qAz/MT80lpFmUz61pdwxPndT49Kie/qWUYgDQ==} + flowbite-svelte@1.28.0: + resolution: {integrity: sha512-3nrXBNbbJJ11PqcTLJg6a/3tNXBPnnhXC/Nf2isuABhWdGdTgKPQcX+ZjkYwi40hWFPmCgjFP8SO2OGdwZNaZA==} peerDependencies: - svelte: ^5.0.0 + svelte: ^5.29.0 tailwindcss: ^4.1.4 flowbite@2.5.2: @@ -2999,17 +2999,18 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tailwind-merge@3.0.2: - resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==} - tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} - tailwind-variants@1.0.0: - resolution: {integrity: sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==} + tailwind-variants@3.1.1: + resolution: {integrity: sha512-ftLXe3krnqkMHsuBTEmaVUXYovXtPyTK7ckEfDRXS8PBZx0bAUas+A0jYxuKA5b8qg++wvQ3d2MQ7l/xeZxbZQ==} engines: {node: '>=16.x', pnpm: '>=7.x'} peerDependencies: + tailwind-merge: '>=3.0.0' tailwindcss: '*' + peerDependenciesMeta: + tailwind-merge: + optional: true tailwindcss@4.1.11: resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} @@ -3655,13 +3656,13 @@ snapshots: '@eslint/core': 0.15.1 levn: 0.4.1 - '@floating-ui/core@1.7.2': + '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 - '@floating-ui/dom@1.7.2': + '@floating-ui/dom@1.7.4': dependencies: - '@floating-ui/core': 1.7.2 + '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 '@floating-ui/utils@0.2.10': {} @@ -4413,24 +4414,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@svgdotjs/svg.draggable.js@3.0.6(@svgdotjs/svg.js@3.2.4)': + '@svgdotjs/svg.draggable.js@3.0.6(@svgdotjs/svg.js@3.2.5)': dependencies: - '@svgdotjs/svg.js': 3.2.4 + '@svgdotjs/svg.js': 3.2.5 '@svgdotjs/svg.filter.js@3.0.9': dependencies: - '@svgdotjs/svg.js': 3.2.4 + '@svgdotjs/svg.js': 3.2.5 - '@svgdotjs/svg.js@3.2.4': {} + '@svgdotjs/svg.js@3.2.5': {} - '@svgdotjs/svg.resize.js@2.0.5(@svgdotjs/svg.js@3.2.4)(@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.4))': + '@svgdotjs/svg.resize.js@2.0.5(@svgdotjs/svg.js@3.2.5)(@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5))': dependencies: - '@svgdotjs/svg.js': 3.2.4 - '@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.4) + '@svgdotjs/svg.js': 3.2.5 + '@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.5) - '@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.4)': + '@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5)': dependencies: - '@svgdotjs/svg.js': 3.2.4 + '@svgdotjs/svg.js': 3.2.5 '@tailwindcss/forms@0.5.10(tailwindcss@4.1.11)': dependencies: @@ -4809,13 +4810,13 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - apexcharts@4.7.0: + apexcharts@5.3.6: dependencies: - '@svgdotjs/svg.draggable.js': 3.0.6(@svgdotjs/svg.js@3.2.4) + '@svgdotjs/svg.draggable.js': 3.0.6(@svgdotjs/svg.js@3.2.5) '@svgdotjs/svg.filter.js': 3.0.9 - '@svgdotjs/svg.js': 3.2.4 - '@svgdotjs/svg.resize.js': 2.0.5(@svgdotjs/svg.js@3.2.4)(@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.4)) - '@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.4) + '@svgdotjs/svg.js': 3.2.5 + '@svgdotjs/svg.resize.js': 2.0.5(@svgdotjs/svg.js@3.2.5)(@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5)) + '@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.5) '@yr/monotone-cubic-spline': 1.0.3 argparse@2.0.1: {} @@ -5226,11 +5227,11 @@ snapshots: transitivePeerDependencies: - rollup - flowbite-svelte-blocks@2.0.0(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(flowbite-svelte-icons@2.2.1(svelte@5.36.14))(flowbite-svelte@1.10.19(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11))(flowbite@3.1.2(rollup@4.45.1))(svelte@5.36.14)(tailwind-merge@3.3.1)(tailwindcss@4.1.11): + flowbite-svelte-blocks@2.0.0(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(flowbite-svelte-icons@2.2.1(svelte@5.36.14))(flowbite-svelte@1.28.0(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11))(flowbite@3.1.2(rollup@4.45.1))(svelte@5.36.14)(tailwind-merge@3.3.1)(tailwindcss@4.1.11): dependencies: '@sveltejs/kit': 2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)) flowbite: 3.1.2(rollup@4.45.1) - flowbite-svelte: 1.10.19(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11) + flowbite-svelte: 1.28.0(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11) flowbite-svelte-icons: 2.2.1(svelte@5.36.14) svelte: 5.36.14 tailwind-merge: 3.3.1 @@ -5242,17 +5243,18 @@ snapshots: svelte: 5.36.14 tailwind-merge: 3.3.1 - flowbite-svelte@1.10.19(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11): + flowbite-svelte@1.28.0(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11): dependencies: - '@floating-ui/dom': 1.7.2 + '@floating-ui/dom': 1.7.4 '@floating-ui/utils': 0.2.10 - apexcharts: 4.7.0 + apexcharts: 5.3.6 clsx: 2.1.1 date-fns: 4.1.0 + esm-env: 1.2.2 flowbite: 3.1.2(rollup@4.45.1) svelte: 5.36.14 tailwind-merge: 3.3.1 - tailwind-variants: 1.0.0(tailwindcss@4.1.11) + tailwind-variants: 3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.11) tailwindcss: 4.1.11 transitivePeerDependencies: - rollup @@ -6409,14 +6411,13 @@ snapshots: symbol-tree@3.2.4: {} - tailwind-merge@3.0.2: {} - tailwind-merge@3.3.1: {} - tailwind-variants@1.0.0(tailwindcss@4.1.11): + tailwind-variants@3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.11): dependencies: - tailwind-merge: 3.0.2 tailwindcss: 4.1.11 + optionalDependencies: + tailwind-merge: 3.3.1 tailwindcss@4.1.11: {} From 54dae3d7389fa47b7c226a9c96b80e52dfe265f1 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Mon, 17 Nov 2025 08:26:20 +0530 Subject: [PATCH 02/21] Refactor chat to use virtual list Chat components now accept and use the full thread object instead of just threadId, enabling loading of existing messages on mount. ChatMessages uses VirtualList for efficient rendering of large message lists. Updated client and page logic to pass and handle thread objects throughout. --- apps/frontend/src/lib/components/Chat.svelte | 49 +++++++++++++++++-- .../src/lib/components/ChatMessages.svelte | 28 ++++++----- apps/frontend/src/lib/langgraph/client.ts | 8 +-- apps/frontend/src/routes/chat/+page.svelte | 10 ++-- 4 files changed, 71 insertions(+), 24 deletions(-) diff --git a/apps/frontend/src/lib/components/Chat.svelte b/apps/frontend/src/lib/components/Chat.svelte index 3f8ddc9..7df964e 100644 --- a/apps/frontend/src/lib/components/Chat.svelte +++ b/apps/frontend/src/lib/components/Chat.svelte @@ -1,16 +1,17 @@ -
- {#each messages as message (message.id)} -
- {#if message.type === 'tool'} - - {:else if message.text} - - {/if} -
- {/each} +
+ + {#snippet children(item: Message)} +
+
+ {#if item.type === 'tool'} + + {:else if item.text} + + {/if} +
+
+ {/snippet} +
+ {#if generationError && onRetryError} {:else if !finalAnswerStarted} diff --git a/apps/frontend/src/lib/langgraph/client.ts b/apps/frontend/src/lib/langgraph/client.ts index 925772b..d48e7f3 100644 --- a/apps/frontend/src/lib/langgraph/client.ts +++ b/apps/frontend/src/lib/langgraph/client.ts @@ -1,4 +1,4 @@ -import { Client } from '@langchain/langgraph-sdk'; +import { Client, type Thread, type DefaultValues } from '@langchain/langgraph-sdk'; import { env } from '$env/dynamic/public'; export function createClient(accessToken: string): Client { @@ -13,7 +13,7 @@ export function createClient(accessToken: string): Client { }); } -export async function getOrCreateThread(client: Client): Promise { +export async function getOrCreateThread(client: Client): Promise> { // Search for existing thread first const existingThreads = await client.threads.search({ status: 'idle', @@ -25,11 +25,11 @@ export async function getOrCreateThread(client: Client): Promise { if (existingThreads.length > 0) { const existingThread = existingThreads[0]; console.info('Using existing thread', existingThread); - return existingThread.thread_id; + return existingThread; } else { console.info('No existing thread found, creating anew'); const thread = await client.threads.create(); - return thread.thread_id; + return thread; } } diff --git a/apps/frontend/src/routes/chat/+page.svelte b/apps/frontend/src/routes/chat/+page.svelte index 91b43e9..449bf48 100644 --- a/apps/frontend/src/routes/chat/+page.svelte +++ b/apps/frontend/src/routes/chat/+page.svelte @@ -7,7 +7,7 @@ import LoginModal from '$lib/components/LoginModal.svelte'; import { getOrCreateAssistant, createClient, getOrCreateThread } from '$lib/langgraph/client'; import * as m from '$lib/paraglide/messages.js'; - import type { Client } from '@langchain/langgraph-sdk'; + import type { Client, Thread, DefaultValues } from '@langchain/langgraph-sdk'; import ChatError from '$lib/components/ChatError.svelte'; let show_login_dialog = $state(false); @@ -16,12 +16,14 @@ let client = $derived(page.data.session ? createClient(page.data.session.accessToken) : null); let assistantId = $state(null); let threadId = $state(null); + let thread = $state | null>(null); let initialization_error = $state(null); async function initLangGraph(client: Client) { try { assistantId = await getOrCreateAssistant(client, 'chat'); - threadId = await getOrCreateThread(client); + thread = await getOrCreateThread(client); + threadId = thread.thread_id; } catch (err) { if (err instanceof Error) initialization_error = err; error(500, { @@ -74,12 +76,12 @@ {#if initialization_error} -{:else if assistantId && threadId && client} +{:else if assistantId && thread && client} Date: Mon, 17 Nov 2025 08:26:48 +0530 Subject: [PATCH 03/21] Formatting d'oh --- apps/frontend/src/lib/components/Chat.svelte | 2 +- apps/frontend/src/routes/chat/+page.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/lib/components/Chat.svelte b/apps/frontend/src/lib/components/Chat.svelte index 7df964e..f2bece0 100644 --- a/apps/frontend/src/lib/components/Chat.svelte +++ b/apps/frontend/src/lib/components/Chat.svelte @@ -68,7 +68,7 @@ messages = loadedMessages; chat_started = true; } - console.info(loadedMessages) + console.info(loadedMessages); } }); diff --git a/apps/frontend/src/routes/chat/+page.svelte b/apps/frontend/src/routes/chat/+page.svelte index 449bf48..b3ae44b 100644 --- a/apps/frontend/src/routes/chat/+page.svelte +++ b/apps/frontend/src/routes/chat/+page.svelte @@ -76,7 +76,7 @@ {#if initialization_error} -{:else if assistantId && thread && client} +{:else if assistantId && thread && client} Date: Mon, 17 Nov 2025 08:27:22 +0530 Subject: [PATCH 04/21] Update Chat.svelte --- apps/frontend/src/lib/components/Chat.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/frontend/src/lib/components/Chat.svelte b/apps/frontend/src/lib/components/Chat.svelte index f2bece0..6047a02 100644 --- a/apps/frontend/src/lib/components/Chat.svelte +++ b/apps/frontend/src/lib/components/Chat.svelte @@ -68,7 +68,6 @@ messages = loadedMessages; chat_started = true; } - console.info(loadedMessages); } }); From b56338664a5f1ef73e05d25efa3d59b69019b956 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Tue, 18 Nov 2025 21:32:35 +0530 Subject: [PATCH 05/21] Refactor thread message conversion logic Moved thread message conversion from Chat.svelte into new utility functions convertThreadMessage and convertThreadMessages in utils.ts. This improves code reuse and clarity when loading existing messages from a thread. --- apps/frontend/src/lib/components/Chat.svelte | 33 +++--------- apps/frontend/src/lib/langgraph/utils.ts | 56 +++++++++++++++++++- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/apps/frontend/src/lib/components/Chat.svelte b/apps/frontend/src/lib/components/Chat.svelte index 6047a02..8a30d5e 100644 --- a/apps/frontend/src/lib/components/Chat.svelte +++ b/apps/frontend/src/lib/components/Chat.svelte @@ -1,10 +1,11 @@
diff --git a/apps/frontend/src/lib/langgraph/utils.ts b/apps/frontend/src/lib/langgraph/utils.ts index d611d4f..5f2ebe1 100644 --- a/apps/frontend/src/lib/langgraph/utils.ts +++ b/apps/frontend/src/lib/langgraph/utils.ts @@ -5,7 +5,7 @@ import type { } from '@langchain/langgraph-sdk'; import { InvalidData } from './errors'; -import type { Message } from './types'; +import type { Message, UserMessage, AIMessage, ToolMessage } from './types'; interface FixedAIMessage extends Omit { type: 'AIMessageChunk'; @@ -13,6 +13,60 @@ interface FixedAIMessage extends Omit { type FixedMessage = FixedAIMessage | LangGraphToolMessage; +/** + * Converts a single LangGraph message to our internal Message format. + * Handles conversion of thread history messages (human, ai, tool types). + * + * @param item - Raw message object from LangGraph thread values + * @returns Converted Message or null if unable to convert + */ +export function convertThreadMessage(item: Record): Message | null { + if (item.type === 'human') { + return { + type: 'user', + text: typeof item.content === 'string' ? item.content : '', + id: (item.id as string) || crypto.randomUUID() + } as UserMessage; + } else if (item.type === 'ai') { + return { + type: 'ai', + text: typeof item.content === 'string' ? item.content : '', + id: (item.id as string) || crypto.randomUUID() + } as AIMessage; + } else if (item.type === 'tool') { + return { + type: 'tool', + text: typeof item.content === 'string' ? item.content : '', + tool_name: (item.name as string) || '', + id: (item.tool_call_id as string) || (item.id as string) || crypto.randomUUID(), + status: (item.status as 'success' | 'error') || 'success' + } as ToolMessage; + } + return null; +} + +/** + * Converts thread history messages to our internal Message format. + * Filters out null conversions. + * + * @param threadMessages - Array of raw message objects from thread.values.messages + * @returns Array of converted Messages + */ +export function convertThreadMessages(threadMessages: Record[]): Message[] { + return threadMessages + .map((item) => convertThreadMessage(item)) + .filter((msg): msg is Message => msg !== null); +} + +/** + * Converts a LangGraph message from the streaming response to our internal Message format. + * Handles streaming messages (AIMessageChunk, tool) and yields individual messages and tool calls. + * For AI messages, also yields any associated tool calls as separate messages. + * + * @param m - LangGraph message from streaming response + * @yields Message objects in our internal format + * @throws InvalidData if message is malformed or invalid + */ export function* YieldMessages(m: LangGraphMessage): Generator { const fixed = m as FixedMessage; From 5438c9cc2a91050e6f613454857ea63d33f84182 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Tue, 18 Nov 2025 22:10:58 +0530 Subject: [PATCH 06/21] Set final_answer_started when loading messages Ensures that final_answer_started is set to true if existing messages are loaded, indicating that the final answer has already started. This improves chat state consistency when restoring previous threads. --- apps/frontend/src/lib/components/Chat.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/frontend/src/lib/components/Chat.svelte b/apps/frontend/src/lib/components/Chat.svelte index 8a30d5e..bf4d142 100644 --- a/apps/frontend/src/lib/components/Chat.svelte +++ b/apps/frontend/src/lib/components/Chat.svelte @@ -43,6 +43,8 @@ if (loadedMessages.length > 0) { messages = loadedMessages; chat_started = true; + // If we have existing messages, the final answer already started + final_answer_started = true; } console.info('Loaded existing messages from thread:', loadedMessages); } From 3f999917d9a465e423a2868d6532f28244fc1bd9 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 19 Nov 2025 19:13:07 +0530 Subject: [PATCH 07/21] Revert flowbit package update --- apps/frontend/package.json | 2 +- pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 1f5b9e9..25f73c6 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -38,7 +38,7 @@ "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^3.0.0", "flowbite": "^3.1.2", - "flowbite-svelte": "^1.28.0", + "flowbite-svelte": "^1.5.3", "flowbite-svelte-blocks": "^2.0.0", "flowbite-svelte-icons": "^2.2.0", "globals": "^16.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f86b2f..7d1431b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -81,7 +81,7 @@ importers: specifier: ^3.1.2 version: 3.1.2(rollup@4.45.1) flowbite-svelte: - specifier: ^1.28.0 + specifier: ^1.5.3 version: 1.28.0(rollup@4.45.1)(svelte@5.36.14)(tailwindcss@4.1.11) flowbite-svelte-blocks: specifier: ^2.0.0 From 9a4afc4a5bd7bce10330de8293fbb06a68403355 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 19 Nov 2025 19:20:26 +0530 Subject: [PATCH 08/21] Rollback changes -Virtual list --- .../src/lib/components/ChatMessages.svelte | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/apps/frontend/src/lib/components/ChatMessages.svelte b/apps/frontend/src/lib/components/ChatMessages.svelte index 0f37b57..8499690 100644 --- a/apps/frontend/src/lib/components/ChatMessages.svelte +++ b/apps/frontend/src/lib/components/ChatMessages.svelte @@ -5,7 +5,6 @@ import ChatWaiting from './ChatWaiting.svelte'; import ChatErrorMessage from './ChatErrorMessage.svelte'; import { fly } from 'svelte/transition'; - import { VirtualList } from 'flowbite-svelte'; import { ScrollableContainer } from './ScrollableContainer'; interface Props { @@ -20,26 +19,21 @@ {#snippet children({ scrollToMe })} -
- -
-
- {#if item.type === 'tool'} - - {:else if item.text} - - {/if} -
-
-
- -
- {#if generationError && onRetryError} - - {:else if !finalAnswerStarted} - + {#each messages as message (message.id)} +
+ {#if message.type === 'tool'} + + {:else if message.text} + {/if}
+ {/each} +
+ {#if generationError && onRetryError} + + {:else if !finalAnswerStarted} + + {/if}
{/snippet} From 2cc7cbd7e5cbabf7cfd0d1eb3cb7810acf8de914 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 19 Nov 2025 19:27:43 +0530 Subject: [PATCH 09/21] Load only list 100 messages Added MAX_MESSAGES_TO_LOAD constant Updated the onMount function (line 46) to use the MAX_MESSAGES_TO_LOAD constant instead of the hardcoded 100, making it easy to change the limit in one place. --- apps/frontend/src/lib/components/Chat.svelte | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/lib/components/Chat.svelte b/apps/frontend/src/lib/components/Chat.svelte index bf4d142..3436883 100644 --- a/apps/frontend/src/lib/components/Chat.svelte +++ b/apps/frontend/src/lib/components/Chat.svelte @@ -9,6 +9,10 @@ import { error } from '@sveltejs/kit'; import { onMount } from 'svelte'; + // Configuration: Keep this simple for now, will update for performance-oriented + // lazy loaded or context loaded messages + const MAX_MESSAGES_TO_LOAD = 100; + interface Props { langGraphClient: Client; assistantId: string; @@ -38,7 +42,9 @@ // Load existing messages from thread on component initialization onMount(() => { if (thread?.values?.messages && thread.values.messages.length > 0) { - const loadedMessages = convertThreadMessages(thread.values.messages); + // Only load the last MAX_MESSAGES_TO_LOAD messages + const lastMessages = thread.values.messages.slice(-MAX_MESSAGES_TO_LOAD); + const loadedMessages = convertThreadMessages(lastMessages); if (loadedMessages.length > 0) { messages = loadedMessages; @@ -46,7 +52,6 @@ // If we have existing messages, the final answer already started final_answer_started = true; } - console.info('Loaded existing messages from thread:', loadedMessages); } }); @@ -139,8 +144,7 @@ submitInputOrRetry(true); } } - - console.info(final_answer_started); +
From f7a1cb90398b7058035bcdb549e3b84960036a35 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 19 Nov 2025 19:27:49 +0530 Subject: [PATCH 10/21] Update Chat.svelte --- apps/frontend/src/lib/components/Chat.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/frontend/src/lib/components/Chat.svelte b/apps/frontend/src/lib/components/Chat.svelte index 3436883..c9e66c4 100644 --- a/apps/frontend/src/lib/components/Chat.svelte +++ b/apps/frontend/src/lib/components/Chat.svelte @@ -144,7 +144,6 @@ submitInputOrRetry(true); } } -
From 31f28973963e9de73fd58f2734303e7a3888eb6f Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 19 Nov 2025 19:35:47 +0530 Subject: [PATCH 11/21] Prevent's avalanche of message onMount 1. Instant scroll to bottom - No scroll animation 2. Fade in the entire container - Content gradually appears over 400ms 3. Smooth experience - All messages display with a nice ease-in effect --- .../ScrollableContainer.svelte | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/lib/components/ScrollableContainer/ScrollableContainer.svelte b/apps/frontend/src/lib/components/ScrollableContainer/ScrollableContainer.svelte index 42d0587..5f3ee69 100644 --- a/apps/frontend/src/lib/components/ScrollableContainer/ScrollableContainer.svelte +++ b/apps/frontend/src/lib/components/ScrollableContainer/ScrollableContainer.svelte @@ -4,6 +4,8 @@ import type { Snippet } from 'svelte'; import type { Attachment } from 'svelte/attachments'; import type { BaseMessage } from '$lib/langgraph/types'; + import { onMount } from 'svelte'; + import { fade } from 'svelte/transition'; interface Props { children: Snippet<[{ scrollToMe: (message?: BaseMessage | null) => Attachment }]>; @@ -13,6 +15,7 @@ let isUserScrolledAway = $state(false); let scrollContainerRef: HTMLElement | null = null; + let containerNode: HTMLElement | null = null; const scrollListenerAction = createScrollListener({ setIsUserScrolledAway: (value: boolean) => { @@ -23,6 +26,17 @@ } }); + // Scroll to bottom on mount without animation + onMount(() => { + if (containerNode) { + const container = findScrollContainer(containerNode); + if (container) { + // Use instant scroll on initial mount + container.scrollTop = container.scrollHeight; + } + } + }); + function scrollToMe(message: BaseMessage | null = null): Attachment { return (element: Element) => { if (!(element instanceof HTMLElement)) return; @@ -42,6 +56,6 @@ } -
+
{@render children({ scrollToMe })}
From 49fc83c66e595a946ec88de61b5d7a003c6633ec Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 19 Nov 2025 19:36:02 +0530 Subject: [PATCH 12/21] Formatting d'oh! --- .../ScrollableContainer/ScrollableContainer.svelte | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/lib/components/ScrollableContainer/ScrollableContainer.svelte b/apps/frontend/src/lib/components/ScrollableContainer/ScrollableContainer.svelte index 5f3ee69..2258f3d 100644 --- a/apps/frontend/src/lib/components/ScrollableContainer/ScrollableContainer.svelte +++ b/apps/frontend/src/lib/components/ScrollableContainer/ScrollableContainer.svelte @@ -56,6 +56,11 @@ } -
+
{@render children({ scrollToMe })}
From 839b8a2103eb2f92161e1c0cca2b3a0d08551b03 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 19 Nov 2025 19:47:07 +0530 Subject: [PATCH 13/21] Fix Clipboard Button size --- apps/frontend/src/lib/components/AIMessageActions.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/lib/components/AIMessageActions.svelte b/apps/frontend/src/lib/components/AIMessageActions.svelte index 88d3cfd..6f65284 100644 --- a/apps/frontend/src/lib/components/AIMessageActions.svelte +++ b/apps/frontend/src/lib/components/AIMessageActions.svelte @@ -19,10 +19,10 @@ class="absolute bottom-2 left-0 flex items-center gap-1 transition-all duration-300 ease-in-out" style="opacity: {isHovered ? '1' : '0'}; transform: translateY({isHovered ? '0' : '-4px'});" > - + {#snippet children(success)} {success ? m.message_copied() : m.message_copy()} - {#if success}{:else}{/if} + {#if success}{:else}{/if} {/snippet} From 538a1fb153987bbdee98f6dc380081e85a8e8158 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 19 Nov 2025 19:47:15 +0530 Subject: [PATCH 14/21] Update AIMessageActions.svelte --- apps/frontend/src/lib/components/AIMessageActions.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/lib/components/AIMessageActions.svelte b/apps/frontend/src/lib/components/AIMessageActions.svelte index 6f65284..de86ec2 100644 --- a/apps/frontend/src/lib/components/AIMessageActions.svelte +++ b/apps/frontend/src/lib/components/AIMessageActions.svelte @@ -19,7 +19,7 @@ class="absolute bottom-2 left-0 flex items-center gap-1 transition-all duration-300 ease-in-out" style="opacity: {isHovered ? '1' : '0'}; transform: translateY({isHovered ? '0' : '-4px'});" > - + {#snippet children(success)} {success ? m.message_copied() : m.message_copy()} {#if success}{:else}{/if} From 79652d41d5be98dffe362d1dbcd98d885676576b Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 19 Nov 2025 19:59:53 +0530 Subject: [PATCH 15/21] Added datatype ThreadValues 1. Added ThreadValues interface to types.ts - Defines the structure of thread values with a messages property 2. Updated client.ts - Changed return type of getOrCreateThread() from Thread to Thread and added type casts 3. Updated Chat.svelte - Changed thread prop type from Thread to Thread 4. Updated +page.svelte - Changed thread state type from Thread to Thread and imported the new type --- apps/frontend/src/lib/components/Chat.svelte | 6 +++--- apps/frontend/src/lib/langgraph/client.ts | 9 +++++---- apps/frontend/src/lib/langgraph/types.ts | 5 +++++ apps/frontend/src/routes/chat/+page.svelte | 5 +++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/frontend/src/lib/components/Chat.svelte b/apps/frontend/src/lib/components/Chat.svelte index c9e66c4..4e41a56 100644 --- a/apps/frontend/src/lib/components/Chat.svelte +++ b/apps/frontend/src/lib/components/Chat.svelte @@ -1,11 +1,11 @@ -{#if initialization_error} - -{:else if assistantId && thread && client} - - +{#if redirect_error} +
+
+

Error

+

{redirect_error.message}

+
+
{:else} {/if} diff --git a/apps/frontend/src/routes/chat/[threadID]/+page.svelte b/apps/frontend/src/routes/chat/[threadID]/+page.svelte new file mode 100644 index 0000000..f84c74b --- /dev/null +++ b/apps/frontend/src/routes/chat/[threadID]/+page.svelte @@ -0,0 +1,90 @@ + + +{#if initialization_error} + +{:else if assistantId && threadId && client} + + +{:else} + +{/if} + + From d366db96eb05a64d5ce4eb0f1a0c9d621a47ca10 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Thu, 20 Nov 2025 23:12:18 +0530 Subject: [PATCH 18/21] Consume thread to load chats --- apps/frontend/src/routes/chat/+page.svelte | 4 ++-- .../src/routes/chat/[threadID]/+page.svelte | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/routes/chat/+page.svelte b/apps/frontend/src/routes/chat/+page.svelte index 9cb0c1b..858d804 100644 --- a/apps/frontend/src/routes/chat/+page.svelte +++ b/apps/frontend/src/routes/chat/+page.svelte @@ -13,8 +13,8 @@ if (!client) return; try { - const threadId = await getOrCreateThread(client); - await goto(`/chat/${threadId}`); + const thread = await getOrCreateThread(client); + await goto(`/chat/${thread.thread_id}`); } catch (err) { if (err instanceof Error) redirect_error = err; console.error('Error creating or fetching thread:', err); diff --git a/apps/frontend/src/routes/chat/[threadID]/+page.svelte b/apps/frontend/src/routes/chat/[threadID]/+page.svelte index f84c74b..b8aa449 100644 --- a/apps/frontend/src/routes/chat/[threadID]/+page.svelte +++ b/apps/frontend/src/routes/chat/[threadID]/+page.svelte @@ -2,12 +2,14 @@ import { error } from '@sveltejs/kit'; import { page } from '$app/state'; import { onMount } from 'svelte'; + import type { Thread } from '@langchain/langgraph-sdk'; import Chat from '$lib/components/Chat.svelte'; import ChatLoader from '$lib/components/ChatLoader.svelte'; import LoginModal from '$lib/components/LoginModal.svelte'; import { getOrCreateAssistant, createClient } from '$lib/langgraph/client'; import * as m from '$lib/paraglide/messages.js'; import type { Client } from '@langchain/langgraph-sdk'; + import type { ThreadValues } from '$lib/langgraph/types'; import ChatError from '$lib/components/ChatError.svelte'; let show_login_dialog = $state(false); @@ -15,12 +17,15 @@ // Updates client whenever accessToken changes let client = $derived(page.data.session ? createClient(page.data.session.accessToken) : null); let assistantId = $state(null); + let thread = $state | null>(null); let threadId = $derived(page.params.threadID); let initialization_error = $state(null); - async function initAssistant(client: Client) { + async function initAssistantAndThread(client: Client, threadId: string) { try { assistantId = await getOrCreateAssistant(client, 'chat'); + const fetchedThread = await client.threads.get(threadId); + thread = fetchedThread as Thread; } catch (err) { if (err instanceof Error) initialization_error = err; error(500, { @@ -30,7 +35,9 @@ } $effect(() => { - if (assistantId === null && client) initAssistant(client); + if ((assistantId === null || thread === null) && client && threadId) { + initAssistantAndThread(client, threadId); + } }); onMount(async () => { @@ -73,12 +80,12 @@ {#if initialization_error} -{:else if assistantId && threadId && client} +{:else if assistantId && thread && client} Date: Thu, 20 Nov 2025 23:19:02 +0530 Subject: [PATCH 19/21] Use ChatError --- apps/frontend/src/routes/chat/+page.svelte | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/routes/chat/+page.svelte b/apps/frontend/src/routes/chat/+page.svelte index 858d804..704ba3a 100644 --- a/apps/frontend/src/routes/chat/+page.svelte +++ b/apps/frontend/src/routes/chat/+page.svelte @@ -4,6 +4,7 @@ import { createClient, getOrCreateThread } from '$lib/langgraph/client'; import ChatLoader from '$lib/components/ChatLoader.svelte'; import LoginModal from '$lib/components/LoginModal.svelte'; + import ChatError from '$lib/components/ChatError.svelte'; let show_login_dialog = $state(false); let client = $derived(page.data.session ? createClient(page.data.session.accessToken) : null); @@ -34,12 +35,7 @@ {#if redirect_error} -
-
-

Error

-

{redirect_error.message}

-
-
+ {:else} {/if} From 64708d5c65ae3a7520b9c117b32a77d648a38a46 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Fri, 21 Nov 2025 22:12:50 +0530 Subject: [PATCH 20/21] Add comment on lazily made threads --- apps/frontend/src/lib/langgraph/client.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/frontend/src/lib/langgraph/client.ts b/apps/frontend/src/lib/langgraph/client.ts index b316724..fba96d7 100644 --- a/apps/frontend/src/lib/langgraph/client.ts +++ b/apps/frontend/src/lib/langgraph/client.ts @@ -23,6 +23,8 @@ export async function getOrCreateThread(client: Client): Promise 0) { const existingThread = existingThreads[0]; console.info('Using existing thread', existingThread); From 31cc936954ece57067e31396916c93dcbb0d8426 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Fri, 21 Nov 2025 22:12:59 +0530 Subject: [PATCH 21/21] Update client.ts --- apps/frontend/src/lib/langgraph/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/lib/langgraph/client.ts b/apps/frontend/src/lib/langgraph/client.ts index fba96d7..d88e17a 100644 --- a/apps/frontend/src/lib/langgraph/client.ts +++ b/apps/frontend/src/lib/langgraph/client.ts @@ -23,7 +23,7 @@ export async function getOrCreateThread(client: Client): Promise 0) { const existingThread = existingThreads[0];