Skip to content

Commit ade8da3

Browse files
authored
feat: move last thread read to backend (#1696)
Moved to the backend so that we can server-render the previous chat and improve the UX 10x.
1 parent d9d5668 commit ade8da3

File tree

27 files changed

+153
-227
lines changed

27 files changed

+153
-227
lines changed

apps/web/src/app/(private)/_data-access/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ProjectLimitedView,
1212
Workspace,
1313
type Commit,
14+
LAST_LATTE_THREAD_CACHE_KEY,
1415
} from '@latitude-data/core/browser'
1516
import { cache as redis } from '@latitude-data/core/cache'
1617
import { NotFoundError } from '@latitude-data/core/lib/errors'
@@ -401,3 +402,15 @@ export const isFeatureEnabledCached = cache(async (feature: string) => {
401402

402403
return enabled
403404
})
405+
406+
export const getLastLatteThreadUuidCached = async ({
407+
projectId,
408+
}: {
409+
projectId: number
410+
}) => {
411+
const { workspace, user } = await getCurrentUserOrRedirect()
412+
const client = await redis()
413+
const key = LAST_LATTE_THREAD_CACHE_KEY(workspace.id, user.id, projectId)
414+
const uuid = await client.get(key)
415+
return uuid || undefined
416+
}

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/_components/DocumentEditor/Editor/DocumentEditor.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ import { RunButton } from './RunButton'
3939
import { V2Playground } from './V2Playground'
4040
import DocumentParams from './V2Playground/DocumentParams'
4141
import { useCurrentDocument } from '$/app/providers/DocumentProvider'
42-
import type { ProviderApiKey } from '@latitude-data/core/browser'
42+
import type {
43+
ProviderApiKey,
44+
ProviderLogDto,
45+
} from '@latitude-data/core/browser'
4346

4447
export type DocumentEditorProps = {
4548
document: DocumentVersion
@@ -49,6 +52,8 @@ export type DocumentEditorProps = {
4952
copilotEnabled: boolean
5053
refinementEnabled: boolean
5154
experimentDiff?: string
55+
initialThreadUuid?: string
56+
initialProviderLog?: ProviderLogDto
5257
}
5358

5459
export function DocumentEditor(props: DocumentEditorProps) {
@@ -87,6 +92,8 @@ function DocumentEditorContent({
8792
document: doc,
8893
freeRunsCount,
8994
refinementEnabled,
95+
initialThreadUuid,
96+
initialProviderLog,
9097
}: Omit<DocumentEditorProps, 'experimentDiff'>) {
9198
const { updateDocumentContent } = useDocumentValue()
9299
const [mode, setMode] = useState<'preview' | 'chat'>('preview')
@@ -149,7 +156,10 @@ function DocumentEditorContent({
149156
useAutoScroll(containerRef, { startAtBottom: mode === 'chat' })
150157

151158
return (
152-
<LatteLayout>
159+
<LatteLayout
160+
initialThreadUuid={initialThreadUuid}
161+
initialProviderLog={initialProviderLog}
162+
>
153163
<div className='relative flex flex-col pt-6 h-full min-h-0'>
154164
<div className='w-full flex flex-col justify-center items-start gap-4 px-4 pb-4'>
155165
<div className='w-full flex flex-row items-center justify-between gap-4'>

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
getDocumentByUuidCached,
44
getDocumentsAtCommitCached,
55
getProviderApiKeysCached,
6+
getLastLatteThreadUuidCached,
67
} from '$/app/(private)/_data-access'
78
import providerApiKeyPresenter from '$/presenters/providerApiKeyPresenter'
89
import { getCurrentUserOrRedirect } from '$/services/auth/getCurrentUser'
@@ -12,6 +13,7 @@ import { getFreeRuns } from '@latitude-data/core/services/freeRunsManager/index'
1213
import { env } from '@latitude-data/env'
1314
import { redirect } from 'next/navigation'
1415
import DocumentEditor from './_components/DocumentEditor/Editor'
16+
import { findLatteThreadProviderLog } from '@latitude-data/core/services/providerLogs/findLatteThreadProviderLog'
1517

1618
export default async function DocumentPage({
1719
params,
@@ -45,6 +47,10 @@ export default async function DocumentPage({
4547
const documents = await getDocumentsAtCommitCached({ commit })
4648
const providerApiKeys = await getProviderApiKeysCached()
4749
const freeRunsCount = await getFreeRuns(workspace.id)
50+
const lastThreadUuid = await getLastLatteThreadUuidCached({ projectId })
51+
const initialProviderLog = await findLatteThreadProviderLog({
52+
lastThreadUuid,
53+
})
4854

4955
return (
5056
<DocumentEditor
@@ -54,6 +60,8 @@ export default async function DocumentPage({
5460
freeRunsCount={freeRunsCount ? Number(freeRunsCount) : undefined}
5561
copilotEnabled={env.LATITUDE_CLOUD}
5662
refinementEnabled={env.LATITUDE_CLOUD}
63+
initialThreadUuid={lastThreadUuid}
64+
initialProviderLog={initialProviderLog}
5765
/>
5866
)
5967
}

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/preview/page.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
IntegrationsRepository,
99
} from '@latitude-data/core/repositories'
1010
import { TriggersList } from './_components/TriggersList'
11+
import { getLastLatteThreadUuidCached } from '$/app/(private)/_data-access'
12+
import { findLatteThreadProviderLog } from '@latitude-data/core/services/providerLogs/findLatteThreadProviderLog'
1113

1214
export default async function PreviewPage({
1315
params,
@@ -38,9 +40,16 @@ export default async function PreviewPage({
3840
.findAll()
3941
.then((r) => r.unwrap())
4042
.then((integrations) => integrations.filter((i) => i.type === 'pipedream'))
43+
const lastThreadUuid = await getLastLatteThreadUuidCached({ projectId })
44+
const initialProviderLog = await findLatteThreadProviderLog({
45+
lastThreadUuid,
46+
})
4147

4248
return (
43-
<LatteLayout>
49+
<LatteLayout
50+
initialThreadUuid={lastThreadUuid}
51+
initialProviderLog={initialProviderLog}
52+
>
4453
<TriggersList
4554
triggers={integrationTriggers}
4655
integrations={integrations}

apps/web/src/components/LatteChat/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,16 @@ import { LatteMessageList } from './_components/MessageList'
3131
import { LatteUnconfiguredIntegrations } from './_components/UnconfiguredIntegrations'
3232
import { LatteChatInput } from './LatteChatInput'
3333

34-
export function LatteChat() {
35-
const isLoading = useLoadThread()
34+
import type { ProviderLogDto } from '@latitude-data/core/browser'
35+
36+
export function LatteChat({
37+
initialThreadUuid,
38+
initialProviderLog,
39+
}: {
40+
initialThreadUuid?: string
41+
initialProviderLog?: ProviderLogDto
42+
}) {
43+
const isLoading = useLoadThread({ initialThreadUuid, initialProviderLog })
3644

3745
if (isLoading)
3846
return (

apps/web/src/components/LatteLayout/index.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@ import { ReactNode } from 'react'
44
import { LatteChat } from '$/components/LatteChat'
55
import { ClientOnly } from '@latitude-data/web-ui/atoms/ClientOnly'
66
import { SplitPane } from '@latitude-data/web-ui/atoms/SplitPane'
7+
import type { ProviderLogDto } from '@latitude-data/core/browser'
78

8-
export function LatteLayout({ children }: { children: ReactNode }) {
9+
export function LatteLayout({
10+
children,
11+
initialThreadUuid,
12+
initialProviderLog,
13+
}: {
14+
children: ReactNode
15+
initialThreadUuid?: string
16+
initialProviderLog?: ProviderLogDto
17+
}) {
918
return (
1019
<SplitPane
1120
direction='horizontal'
@@ -14,7 +23,10 @@ export function LatteLayout({ children }: { children: ReactNode }) {
1423
firstPane={children}
1524
secondPane={
1625
<ClientOnly>
17-
<LatteChat />
26+
<LatteChat
27+
initialThreadUuid={initialThreadUuid}
28+
initialProviderLog={initialProviderLog}
29+
/>
1830
</ClientOnly>
1931
}
2032
/>

apps/web/src/hooks/latte/useLoadThread/fetchProviderLogHydrated.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,63 @@
1-
import { useEffect, useTransition, useRef } from 'react'
1+
import { useEffect, useRef } from 'react'
22
import { useLatteStore } from '$/stores/latte/index'
33

44
import { useCurrentProject } from '@latitude-data/web-ui/providers'
5-
import { fetchProviderLogHydrated } from './fetchProviderLogHydrated'
65
import { buildInteractionsFromProviderLog } from './buildInteractionsFromProviderLog'
7-
import { useSyncAndGetThreadUuid } from './useSyncAndGetThreadUuid'
6+
import type { ProviderLogDto } from '@latitude-data/core/browser'
87

98
/**
10-
* - Loads and transforms provider logs into Latte interactions.
11-
* - Fetches provider logs for the current thread UUID and converts them
12-
* into a structured format of user-assistant interactions with input/output pairs.
13-
* - Runs only if there are no interactions in the current chat state,
14-
* so it avoids overriding existing state.
15-
* - Runs once when Chat loads for a given project.
9+
* Hook to load and initialize a thread in the Latte store based on the current project.
10+
* It sets the thread UUID and builds interactions from the initial provider log if provided.
11+
* @param initialThreadUuid - Optional UUID of the thread to load
12+
* @param initialProviderLog - Optional provider log to build interactions from
13+
* @returns Always returns false (placeholder for future loading state)
1614
*/
17-
export function useLoadThread() {
15+
export function useLoadThread({
16+
initialThreadUuid,
17+
initialProviderLog,
18+
}: {
19+
initialThreadUuid?: string
20+
initialProviderLog?: ProviderLogDto
21+
}) {
1822
const { project } = useCurrentProject()
1923
const projectId = project.id
2024

2125
const lastLoadedProjectId = useRef<number | null>(null)
22-
const { interactions, setInteractions, currentProjectId, setCurrentProject } =
23-
useLatteStore()
24-
const syncAndGetThreadUuid = useSyncAndGetThreadUuid({ project })
25-
const [isLoading, startTransition] = useTransition()
26+
const {
27+
interactions,
28+
setInteractions,
29+
setThreadUuid,
30+
currentProjectId,
31+
setCurrentProject,
32+
} = useLatteStore()
2633

2734
useEffect(() => {
2835
if (currentProjectId !== projectId) {
2936
setCurrentProject(projectId)
30-
lastLoadedProjectId.current = null // force a reload next time
37+
lastLoadedProjectId.current = null
3138
}
3239
}, [currentProjectId, projectId, setCurrentProject])
3340

3441
useEffect(() => {
35-
// Don’t refetch if there are already interactions
36-
if (interactions.length > 0) return
37-
38-
// Don’t refetch if we already fetched this project
3942
if (lastLoadedProjectId.current === projectId) return
40-
4143
lastLoadedProjectId.current = projectId
4244

43-
startTransition(async () => {
44-
const threadUuid = syncAndGetThreadUuid()
45-
if (!threadUuid) return
46-
47-
const providerLog = await fetchProviderLogHydrated({ threadUuid })
48-
if (!providerLog) return
45+
if (initialThreadUuid) setThreadUuid(initialThreadUuid)
4946

50-
const _interactions = buildInteractionsFromProviderLog({ providerLog })
51-
if (_interactions.length > 0) {
52-
setInteractions(_interactions)
53-
}
54-
})
55-
}, [projectId, interactions.length, syncAndGetThreadUuid, setInteractions])
56-
57-
return isLoading
47+
if (initialProviderLog && interactions.length === 0) {
48+
const built = buildInteractionsFromProviderLog({
49+
providerLog: initialProviderLog,
50+
})
51+
if (built.length > 0) setInteractions(built)
52+
}
53+
}, [
54+
projectId,
55+
initialThreadUuid,
56+
initialProviderLog,
57+
setThreadUuid,
58+
interactions.length,
59+
setInteractions,
60+
])
61+
62+
return false
5863
}

apps/web/src/hooks/latte/useLoadThread/useSyncAndGetThreadUuid.ts

Lines changed: 0 additions & 53 deletions
This file was deleted.

0 commit comments

Comments
 (0)