Skip to content

Commit 7451a0b

Browse files
committed
feat: migrate anonymous threads to user account
1 parent 6f91020 commit 7451a0b

7 files changed

Lines changed: 104 additions & 16 deletions

File tree

apps/backend-convex/functions/threads/mutate.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,30 @@ export const unfreeze = mutation({
127127
},
128128
})
129129

130+
export const migrateToUser = mutation({
131+
args: {
132+
threadId: v.id('threads'),
133+
lockerKey: v.optional(v.string()),
134+
},
135+
handler: async (ctx, args) => {
136+
const userIdentity = await ctx.auth.getUserIdentity()
137+
if (!userIdentity)
138+
throw new ConvexError('Not authenticated')
139+
140+
const thread = await ctx.db.get(args.threadId)
141+
if (!thread)
142+
throw new ConvexError('Thread not found')
143+
if (thread.userId)
144+
throw new ConvexError('Thread already have an owner')
145+
146+
await assertThreadAccess(ctx, { thread, lockerKey: args.lockerKey })
147+
148+
await ctx.db.patch(args.threadId, {
149+
userId: userIdentity.subject,
150+
})
151+
},
152+
})
153+
130154
export const setLockerKey = mutation({
131155
args: {
132156
threadId: v.id('threads'),

apps/frontend/app/components/chat/ChatSidebar.vue

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,18 @@ if ($auth.loggedIn) {
5454
})
5555
5656
// Migrate anonymous threads to user account
57-
watch(threads, () => {
58-
// TODO
57+
until(fetchingFromConvex).not.toBeTruthy({ timeout: 5000 }).then(() => {
58+
for (const thread of threads.value) {
59+
if (thread && !thread.userId) {
60+
// eslint-disable-next-line no-console
61+
console.info(`Migrating thread: ${thread._id} to user account`)
62+
thread.userId = $auth.user.sub
63+
migrateThreadToUser(convex, { threadId: thread._id, lockerKey: getLockerKey(thread._id) })
64+
.catch(async () => {
65+
await refreshThread(convex, { threadId: thread._id, chatContext })
66+
})
67+
}
68+
}
5969
})
6070
}
6171
// For anonymous users, subscribe to threads via sessionId
@@ -149,8 +159,7 @@ const [DefineThreadLiItem, ReuseThreadLiItem] = createReusableTemplate<{ thread:
149159
<SidebarHeader class="px-4 py-2">
150160
<div class="absolute right-3 top-3">
151161
<Button
152-
variant="ghost" size="icon"
153-
class="size-7"
162+
variant="ghost" size="icon" class="size-7"
154163
@click="colorMode.preference = (colorMode.preference === 'dark') ? 'light' : 'dark'"
155164
>
156165
<div>{{ colorMode.preference === 'dark' ? '🌙' : '🌞' }}</div>

apps/frontend/app/components/chat/thread/ShareThreadAlertDialog.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ const open = ref(false)
5151
<AlertDialogHeader>
5252
<AlertDialogTitle>{{ $t('chat.alert.shareThread.title') }}</AlertDialogTitle>
5353
<AlertDialogDescription class="whitespace-pre-line">
54-
{{ $t('chat.alert.shareThread.description', { name: thread.title }) }}
54+
{{
55+
!thread.userId
56+
? $t('chat.alert.shareThread.descriptionDanger', { name: thread.title })
57+
: $t('chat.alert.shareThread.description', { name: thread.title })
58+
}}
5559
</AlertDialogDescription>
5660
</AlertDialogHeader>
5761
<AlertDialogFooter>

apps/frontend/app/utils/chat.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export interface HostedProvider {
4343
default: string
4444
}
4545

46-
export const [useChatContext, provideChatContext] = createContext<{
46+
export interface ChatContext {
4747
threads: Ref<Doc<'threads'>[]>
4848
threadsKeyed: ComputedRef<Record<Doc<'threads'>['_id'], Doc<'threads'>>>
4949
pinnedThreadIds: Ref<string[]>
@@ -59,7 +59,8 @@ export const [useChatContext, provideChatContext] = createContext<{
5959
insaneUI: Ref<boolean>
6060
// Interface soft render key
6161
interfaceSRK: Ref<number>
62-
}>('chat page')
62+
}
63+
export const [useChatContext, provideChatContext] = createContext<ChatContext>('chat page')
6364

6465
export function displayActiveAgent(agent: AgentObject) {
6566
if (agent.provider === 'hosted')

apps/frontend/app/utils/chat_threads.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@ import type { Id } from 'backend-convex/convex/_generated/dataModel'
22
import type { ConvexClient, ConvexHttpClient } from 'convex/browser'
33
import { api } from 'backend-convex/convex/_generated/api'
44

5+
export interface refreshThreadArgs {
6+
threadId: Id<'threads'>
7+
chatContext: ChatContext
8+
}
9+
export async function refreshThread(convex: ConvexClient | ConvexHttpClient, { threadId, chatContext }: refreshThreadArgs) {
10+
await convex.query(api.threads.get, { threadId, lockerKey: getLockerKey(threadId) })
11+
.then((res) => {
12+
const foundLocallyAt = chatContext.threads.value.findIndex(t => t._id === threadId)
13+
if (foundLocallyAt !== -1)
14+
Object.assign(chatContext.threads.value[foundLocallyAt]!, convex.query(api.threads.get, { threadId, lockerKey: getLockerKey(threadId) }))
15+
else
16+
chatContext.threads.value.push(res)
17+
})
18+
.catch((e) => {
19+
if (getConvexErrorMessage(e) === 'Thread not found') {
20+
const foundLocallyAt = chatContext.threads.value.findIndex(t => t._id === threadId)
21+
if (foundLocallyAt !== -1)
22+
chatContext.threads.value.splice(foundLocallyAt, 1)
23+
}
24+
})
25+
}
26+
527
export interface BranchThreadFromMessageArgs {
628
messageId: Id<'messages'>
729
sessionId?: string
@@ -74,6 +96,17 @@ export async function unfreezeThread(convex: ConvexClient | ConvexHttpClient, {
7496
})
7597
}
7698

99+
export interface MigrateThreadToUserArgs {
100+
threadId: Id<'threads'>
101+
lockerKey?: string
102+
}
103+
export async function migrateThreadToUser(convex: ConvexClient | ConvexHttpClient, { threadId, lockerKey }: MigrateThreadToUserArgs) {
104+
return await convex.mutation(api.threads.migrateToUser, {
105+
threadId,
106+
lockerKey,
107+
})
108+
}
109+
77110
export interface GenerateThreadTitleArgs {
78111
threadId: Id<'threads'>
79112
lockerKey?: string

i18n.lock

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ checksums:
66
cancel: 2e2a849c2223911717de8caa2c71bade
77
chat.alert.deleteThread.description: b0cca6d07bbbd907b4d25dc2a1dbe2e3
88
chat.alert.deleteThread.title: 3dea4a78f8c5c2073d8eab4aad3f2015
9-
chat.alert.shareThread.description: e8eeee4bd83372c3a997a593a585f077
9+
chat.alert.shareThread.description: 38470c662f7c91952c7190a873d200ac
10+
chat.alert.shareThread.descriptionDanger: 85dad0b017b628e0c2e29abb45e3d5fa
1011
chat.alert.shareThread.title: e00e8208d2dd52a00a4cbccdc57199e3
1112
chat.interface.selectOrStart: 5ae94ab41d0b599f36924f5db15dd16d
1213
chat.interface.sendToStart: 6ba07995188dc929a2de0e8f0bb4d1ea
@@ -23,7 +24,7 @@ checksums:
2324
chat.sidebar.threads.empty: eaa3199ed3ba5dd2bb3988ea999649e3
2425
chat.sidebar.threads.loading: c0591fcc6aa3eb6092ab4f4ce577804c
2526
chat.thread.branchedFrom: 9235ad091f1d3b7f65a33b2e906674ad
26-
components.convexIntegrationTest.addTask.label: 94bfce6a5916c8955cea8d04ad41c10e
27+
chat.thread.delete: 3dea4a78f8c5c2073d8eab4aad3f2015
2728
chat.thread.freeze: b90fc4e37a58e2f60fbc9a25945b2138
2829
chat.thread.frozen: 69fa1ed78bb636c218ec3d95eae69bca
2930
chat.thread.frozenWithDescription: 5cf38e514472bed10297a7936924737f
@@ -33,8 +34,10 @@ checksums:
3334
chat.thread.unpin: 9f3e0c1a25452b171dfffee566e11d49
3435
chat.toast.threadBranched: edc2466649225081b38d4c6fe1dd27cf
3536
chat.toast.threadRemovedExternal: db7f0c7a17abd8f2fa3bdf24f0e81377
37+
chat.toast.threadNameRemovedExternal: 4a962f76e9e63e9c764659f88661058c
3638
chat.toast.threadShareLinkCopied: 301236632c59a15e329f433e7b4a3706
3739
chat.typeYourMessageHere: 592c39355ee2ce96113655e58b1e65c9
40+
components.convexIntegrationTest.addTask.label: 94bfce6a5916c8955cea8d04ad41c10e
3841
components.convexIntegrationTest.addTask.placeholder: cfe9cb319a1f23515f7c2215dba202a3
3942
components.convexIntegrationTest.authenticated: 7aeaface3edc0f5dca2c8ebd6c7e1974
4043
components.convexIntegrationTest.configuredUrl: ca306d17f0a8f6bd7e0b9b2efcf23fe7

locals/locales/src/sheets/frontend/i18n.csv

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,25 @@ Hành động này không thể hoàn tác.","您确定要删除“{name}”吗
99
此操作无法撤销。"
1010
chat.alert.deleteThread.title,,Delete Thread,Eliminar conversación,Supprimer la conversation,Удалить тему,Xóa Chủ Đề,删除话题
1111
chat.alert.shareThread.description,,"Are you sure you want to share ""{name}""?
12-
The recipient will have full access to the history and future of the thread.","¿Estás seguro de que quieres compartir ""{name}""?
13-
El destinatario tendrá acceso completo al historial y futuro de la conversación.","Êtes-vous sûr de vouloir partager ""{name}"" ?
14-
Le destinataire aura un accès complet à l'historique et au futur de la conversation.","Вы уверены, что хотите поделиться ""{name}""?
15-
Получатель получит полный доступ к истории и будущему этой ветки.","Bạn có chắc chắn muốn chia sẻ ""{name}""?
16-
Người nhận sẽ có toàn quyền truy cập vào lịch sử và tương lai của cuộc trò chuyện.","您确定要分享“{name}”吗?
17-
接收者将可以完全访问该线程的历史记录和未来内容。"
12+
The recipient will have full access including deleting and viewing the history and future of the thread.","¿Estás seguro de que quieres compartir ""{name}""?
13+
El destinatario tendrá acceso completo, incluyendo la posibilidad de eliminar y ver el historial y futuro del hilo.","Êtes-vous sûr de vouloir partager ""{name}"" ?
14+
Le destinataire aura un accès complet, y compris la possibilité de supprimer et de consulter l'historique et les futures conversations du fil.","Вы уверены, что хотите поделиться ""{name}""?
15+
Получатель получит полный доступ, включая возможность удаления, просмотра истории и будущего этой темы.","Bạn có chắc chắn muốn chia sẻ ""{name}""?
16+
Người nhận sẽ có toàn quyền truy cập bao gồm xóa và xem lịch sử cũng như tương lai của cuộc trò chuyện.","您确定要分享“{name}”吗?
17+
接收者将拥有完全的访问权限,包括删除以及查看该线程的历史和未来内容。"
18+
chat.alert.shareThread.descriptionDanger,,"Are you sure you want to share ""{name}""?
19+
The recipient will have full access including deleting and viewing the history and future of the thread.
20+
The thread currently are also not assigned to any user account, its ownership will be transferred to the first user account that joins the thread.","¿Estás seguro de que quieres compartir ""{name}""?
21+
El destinatario tendrá acceso completo, incluyendo la posibilidad de eliminar y ver el historial y futuro del hilo.
22+
Actualmente, el hilo no está asignado a ninguna cuenta de usuario, su propiedad se transferirá a la primera cuenta de usuario que se una al hilo.","Êtes-vous sûr de vouloir partager ""{name}"" ?
23+
Le destinataire aura un accès complet, y compris la possibilité de supprimer et de consulter l'historique et les futures conversations du fil.
24+
Actuellement, ce fil n'est associé à aucun compte utilisateur, sa propriété sera transférée au premier compte utilisateur qui rejoindra le fil.","Вы уверены, что хотите поделиться ""{name}""?
25+
Получатель получит полный доступ, включая возможность удаления, просмотра истории и будущего этой темы.
26+
В настоящее время эта тема также не привязана ни к одной учетной записи пользователя, и её владение будет передано первой учетной записи пользователя, которая присоединится к теме.","Bạn có chắc chắn muốn chia sẻ ""{name}""?
27+
Người nhận sẽ có toàn quyền truy cập bao gồm xóa và xem lịch sử cũng như tương lai của cuộc trò chuyện.
28+
Hiện tại cuộc trò chuyện này chưa được gán cho bất kỳ tài khoản người dùng nào, quyền sở hữu sẽ được chuyển cho tài khoản người dùng đầu tiên tham gia vào cuộc trò chuyện.","您确定要分享“{name}”吗?
29+
接收者将拥有完全的访问权限,包括删除以及查看该线程的历史和未来内容。
30+
目前该线程未分配给任何用户账户,其所有权将转移给第一个加入该线程的用户账户。"
1831
chat.alert.shareThread.title,,Share Thread,Compartir hilo,Partager la discussion,Поделиться дискуссией,Chia sẻ chủ đề,共享线程
1932
chat.interface.selectOrStart,,Select a chat or start a new one.,Selecciona un chat o inicia uno nuevo.,Sélectionnez une conversation ou commencez-en une nouvelle.,Выберите чат или начните новый.,Chọn một cuộc trò chuyện hoặc bắt đầu cuộc trò chuyện mới.,选择一个聊天或开始新的聊天。
2033
chat.interface.sendToStart,,Send a message to start chatting.,Envía un mensaje para comenzar a chatear.,Envoyez un message pour commencer la conversation.,"Отправьте сообщение, чтобы начать общение.",Gửi tin nhắn để bắt đầu trò chuyện.,发送消息以开始聊天。
@@ -40,7 +53,8 @@ chat.thread.share,,Share thread,Compartir hilo,Partager la discussion,Подел
4053
chat.thread.unfreeze,,Unfreeze thread,Descongelar hilo,Dégeler la discussion,Разморозить тему,Giải băng chủ đề,解冻线程
4154
chat.thread.unpin,,Unpin Thread,Desfijar conversación,Désépingler la conversation,Открепить тему,Bỏ ghim chủ đề,取消置顶话题
4255
chat.toast.threadBranched,,The thread has been branched off.,La conversación ha sido ramificada.,La conversation a été ramifiée.,Тема была выделена в отдельную ветку.,Chủ đề đã được tạo nhánh.,话题已被分支。
43-
chat.toast.threadRemovedExternal,,The chat has been removed by the owner.,El chat ha sido eliminado por el propietario.,Le chat a été supprimé par le propriétaire.,Чат был удален владельцем.,Cuộc trò chuyện đã bị xóa bởi chủ sở hữu.,此聊天已被所有者移除。
56+
chat.toast.threadRemovedExternal,,The chat has been removed by the owner.,El chat ha sido eliminado por el propietario.,Le chat a été supprimé par le propriétaire.,Чат был удалён владельцем.,Cuộc trò chuyện đã bị xóa bởi chủ sở hữu.,此聊天已被所有者移除。
57+
chat.toast.threadNameRemovedExternal,,"Thread ""{name}"" has been removed by the owner.","El hilo ""{name}"" ha sido eliminado por el propietario.","La discussion ""{name}"" a été supprimée par le propriétaire.","Тема ""{name}"" была удалена владельцем.","Cuộc trò chuyện ""{name}"" đã bị xóa bởi chủ sở hữu.",主题“{name}”已被所有者移除。
4458
chat.toast.threadShareLinkCopied,,The thread access link has been copied to your clipboard.,El enlace de acceso a la conversación ha sido copiado a tu portapapeles.,Le lien d'accès à la conversation a été copié dans votre presse-papiers.,Ссылка для доступа к ветке скопирована в ваш буфер обмена.,Đường dẫn truy cập cuộc trò chuyện đã được sao chép vào bảng tạm của bạn.,线程访问链接已复制到您的剪贴板。
4559
chat.typeYourMessageHere,,Type your message here...,Escribe tu mensaje aquí...,Tapez votre message ici...,Введите ваше сообщение здесь...,Nhập tin nhắn của bạn tại đây...,在这里输入你的消息...
4660
components.convexIntegrationTest.addTask.label,,Add task entry (press enter),Añadir tarea (pulsar enter),Ajouter une tâche (appuyer sur Entrée),Добавить задачу (нажмите Enter),Thêm mục công việc (nhấn enter),添加任务条目(按回车键)

0 commit comments

Comments
 (0)