kramp HF Staff rtrm HF Staff commited on
Commit
719cfe8
·
unverified ·
1 Parent(s): 4adcf97

Shared conversation redirect (#1968)

Browse files

* redirect to the convo

* backend redirect to the convo

* fix: fetch url logs

* server side redirect

* require a query only for the get

* fix error type

* fix: fetch url logs

---------

Co-authored-by: rtrm <remy.trompier@gmail.com>

src/lib/components/BackgroundGenerationPoller.svelte CHANGED
@@ -64,7 +64,7 @@
64
  log("poll", id);
65
 
66
  try {
67
- const response = await client.conversations({ id }).get();
68
  const conversation = handleResponse(response);
69
  const messages = conversation?.messages ?? [];
70
  const lastAssistant = [...messages]
 
64
  log("poll", id);
65
 
66
  try {
67
+ const response = await client.conversations({ id }).get({ query: {} });
68
  const conversation = handleResponse(response);
69
  const messages = conversation?.messages ?? [];
70
  const lastAssistant = [...messages]
src/lib/server/api/routes/groups/conversations.ts CHANGED
@@ -72,18 +72,17 @@ export const conversationGroup = new Elysia().use(authPlugin).group("/conversati
72
  },
73
  (app) => {
74
  return app
75
- .derive(async ({ locals, params }) => {
76
  let conversation;
77
  let shared = false;
78
 
79
- // if the conver
80
  if (params.id.length === 7) {
81
  // shared link of length 7
82
  conversation = await collections.sharedConversations.findOne({
83
  _id: params.id,
84
  });
85
  shared = true;
86
-
87
  if (!conversation) {
88
  throw new Error("Conversation not found");
89
  }
@@ -113,6 +112,9 @@ export const conversationGroup = new Elysia().use(authPlugin).group("/conversati
113
 
114
  throw new Error("Conversation not found.");
115
  }
 
 
 
116
  }
117
 
118
  const convertedConv = {
@@ -123,19 +125,29 @@ export const conversationGroup = new Elysia().use(authPlugin).group("/conversati
123
 
124
  return { conversation: convertedConv };
125
  })
126
- .get("", async ({ conversation }) => {
127
- return {
128
- messages: conversation.messages,
129
- title: conversation.title,
130
- model: conversation.model,
131
- preprompt: conversation.preprompt,
132
- rootMessageId: conversation.rootMessageId,
133
- id: conversation._id.toString(),
134
- updatedAt: conversation.updatedAt,
135
- modelId: conversation.model,
136
- shared: conversation.shared,
137
- };
138
- })
 
 
 
 
 
 
 
 
 
 
139
  .post("", () => {
140
  // todo: post new message
141
  throw new Error("Not implemented");
 
72
  },
73
  (app) => {
74
  return app
75
+ .derive(async ({ locals, params, query }) => {
76
  let conversation;
77
  let shared = false;
78
 
79
+ // if the conversation is shared
80
  if (params.id.length === 7) {
81
  // shared link of length 7
82
  conversation = await collections.sharedConversations.findOne({
83
  _id: params.id,
84
  });
85
  shared = true;
 
86
  if (!conversation) {
87
  throw new Error("Conversation not found");
88
  }
 
112
 
113
  throw new Error("Conversation not found.");
114
  }
115
+ if (query.fromShare && conversation.meta?.fromShareId === query.fromShare) {
116
+ shared = true;
117
+ }
118
  }
119
 
120
  const convertedConv = {
 
125
 
126
  return { conversation: convertedConv };
127
  })
128
+ .get(
129
+ "",
130
+ async ({ conversation }) => {
131
+ return {
132
+ messages: conversation.messages,
133
+ title: conversation.title,
134
+ model: conversation.model,
135
+ preprompt: conversation.preprompt,
136
+ rootMessageId: conversation.rootMessageId,
137
+ id: conversation._id.toString(),
138
+ updatedAt: conversation.updatedAt,
139
+ modelId: conversation.model,
140
+ shared: conversation.shared,
141
+ };
142
+ },
143
+ {
144
+ query: t.Optional(
145
+ t.Object({
146
+ fromShare: t.Optional(t.String()),
147
+ })
148
+ ),
149
+ }
150
+ )
151
  .post("", () => {
152
  // todo: post new message
153
  throw new Error("Not implemented");
src/lib/server/conversation.ts ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { collections } from "$lib/server/database";
2
+ import { MetricsServer } from "$lib/server/metrics";
3
+ import { error } from "@sveltejs/kit";
4
+ import { ObjectId } from "mongodb";
5
+ import { authCondition } from "$lib/server/auth";
6
+
7
+ /**
8
+ * Create a new conversation from a shared conversation ID.
9
+ * If the conversation already exists for the user/session, return the existing conversation ID.
10
+ * returns the conversation ID.
11
+ */
12
+ export async function createConversationFromShare(
13
+ fromShareId: string,
14
+ locals: App.Locals,
15
+ userAgent?: string
16
+ ): Promise<string> {
17
+ const conversation = await collections.sharedConversations.findOne({
18
+ _id: fromShareId,
19
+ });
20
+
21
+ if (!conversation) {
22
+ error(404, "Conversation not found");
23
+ }
24
+
25
+ // Check if shared conversation exists already for this user/session
26
+ const existingConversation = await collections.conversations.findOne({
27
+ "meta.fromShareId": fromShareId,
28
+ ...authCondition(locals),
29
+ });
30
+
31
+ if (existingConversation) {
32
+ return existingConversation._id.toString();
33
+ }
34
+
35
+ // Create new conversation from shared conversation
36
+ const res = await collections.conversations.insertOne({
37
+ _id: new ObjectId(),
38
+ title: conversation.title.replace(/<\/?think>/gi, "").trim(),
39
+ rootMessageId: conversation.rootMessageId,
40
+ messages: conversation.messages,
41
+ model: conversation.model,
42
+ preprompt: conversation.preprompt,
43
+ createdAt: new Date(),
44
+ updatedAt: new Date(),
45
+ userAgent,
46
+ ...(locals.user ? { userId: locals.user._id } : { sessionId: locals.sessionId }),
47
+ meta: { fromShareId },
48
+ });
49
+
50
+ if (MetricsServer.isEnabled()) {
51
+ MetricsServer.getMetrics().model.conversationsTotal.inc({ model: conversation.model });
52
+ }
53
+ return res.insertedId.toString();
54
+ }
src/routes/conversation/[id]/+page.server.ts ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { redirect } from "@sveltejs/kit";
2
+ import type { PageServerLoad } from "./$types";
3
+ import { loginEnabled } from "$lib/server/auth";
4
+ import { createConversationFromShare } from "$lib/server/conversation";
5
+
6
+ export const load: PageServerLoad = async ({ url, params, locals, request }) => {
7
+ /// if logged in and valid share ID, create conversation from share, usually occurs after login
8
+ if (loginEnabled && locals.user && params.id.length === 7) {
9
+ const leafId = url.searchParams.get("leafId");
10
+ const conversationId = await createConversationFromShare(
11
+ params.id,
12
+ locals,
13
+ request.headers.get("User-Agent") ?? undefined
14
+ );
15
+
16
+ return redirect(
17
+ 302,
18
+ `../conversation/${conversationId}?leafId=${leafId}&fromShare=${params.id}`
19
+ );
20
+ }
21
+ };
src/routes/conversation/[id]/+page.svelte CHANGED
@@ -4,7 +4,7 @@
4
  import { isAborted } from "$lib/stores/isAborted";
5
  import { onMount } from "svelte";
6
  import { page } from "$app/state";
7
- import { beforeNavigate, goto, invalidateAll } from "$app/navigation";
8
  import { base } from "$app/paths";
9
  import { ERROR_MESSAGES, error } from "$lib/stores/errors";
10
  import { findCurrentModel } from "$lib/utils/models";
@@ -95,35 +95,6 @@
95
  return alternatives;
96
  }
97
 
98
- async function convFromShared() {
99
- try {
100
- $loading = true;
101
- const res = await fetch(`${base}/conversation`, {
102
- method: "POST",
103
- headers: {
104
- "Content-Type": "application/json",
105
- },
106
- body: JSON.stringify({
107
- fromShare: page.params.id,
108
- model: data.model,
109
- }),
110
- });
111
-
112
- if (!res.ok) {
113
- error.set(await res.text());
114
- console.error("Error while creating conversation: " + (await res.text()));
115
- return;
116
- }
117
-
118
- const { conversationId } = await res.json();
119
-
120
- return conversationId;
121
- } catch (err) {
122
- error.set(ERROR_MESSAGES.default);
123
- console.error(String(err));
124
- throw err;
125
- }
126
- }
127
  // this function is used to send new message to the backends
128
  async function writeMessage({
129
  prompt,
@@ -381,16 +352,7 @@
381
  });
382
 
383
  async function onMessage(content: string) {
384
- if (!data.shared) {
385
- await writeMessage({ prompt: content });
386
- } else {
387
- await convFromShared()
388
- .then(async (convId) => {
389
- await goto(`${base}/conversation/${convId}`, { invalidateAll: true });
390
- })
391
- .then(async () => await writeMessage({ prompt: content }))
392
- .finally(() => ($loading = false));
393
- }
394
  }
395
 
396
  async function onRetry(payload: { id: Message["id"]; content?: string }) {
@@ -399,27 +361,11 @@
399
  const lastMsgId = payload.id;
400
  messagesPath = createMessagesPath(messages, lastMsgId);
401
 
402
- if (!data.shared) {
403
- await writeMessage({
404
- prompt: payload.content,
405
- messageId: payload.id,
406
- isRetry: true,
407
- });
408
- } else {
409
- await convFromShared()
410
- .then(async (convId) => {
411
- await goto(`${base}/conversation/${convId}`, { invalidateAll: true });
412
- })
413
- .then(
414
- async () =>
415
- await writeMessage({
416
- prompt: payload.content,
417
- messageId: payload.id,
418
- isRetry: true,
419
- })
420
- )
421
- .finally(() => ($loading = false));
422
- }
423
  }
424
 
425
  async function onShowAlternateMsg(payload: { id: Message["id"] }) {
 
4
  import { isAborted } from "$lib/stores/isAborted";
5
  import { onMount } from "svelte";
6
  import { page } from "$app/state";
7
+ import { beforeNavigate, invalidateAll } from "$app/navigation";
8
  import { base } from "$app/paths";
9
  import { ERROR_MESSAGES, error } from "$lib/stores/errors";
10
  import { findCurrentModel } from "$lib/utils/models";
 
95
  return alternatives;
96
  }
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  // this function is used to send new message to the backends
99
  async function writeMessage({
100
  prompt,
 
352
  });
353
 
354
  async function onMessage(content: string) {
355
+ await writeMessage({ prompt: content });
 
 
 
 
 
 
 
 
 
356
  }
357
 
358
  async function onRetry(payload: { id: Message["id"]; content?: string }) {
 
361
  const lastMsgId = payload.id;
362
  messagesPath = createMessagesPath(messages, lastMsgId);
363
 
364
+ await writeMessage({
365
+ prompt: payload.content,
366
+ messageId: payload.id,
367
+ isRetry: true,
368
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  }
370
 
371
  async function onShowAlternateMsg(payload: { id: Message["id"] }) {
src/routes/conversation/[id]/+page.ts CHANGED
@@ -8,7 +8,10 @@ export const load = async ({ params, depends, fetch, url }) => {
8
  const client = useAPIClient({ fetch, origin: url.origin });
9
 
10
  try {
11
- return await client.conversations({ id: params.id }).get().then(handleResponse);
 
 
 
12
  } catch {
13
  redirect(302, "/");
14
  }
 
8
  const client = useAPIClient({ fetch, origin: url.origin });
9
 
10
  try {
11
+ return await client
12
+ .conversations({ id: params.id })
13
+ .get({ query: { fromShare: url.searchParams.get("fromShare") ?? undefined } })
14
+ .then(handleResponse);
15
  } catch {
16
  redirect(302, "/");
17
  }
src/routes/r/[id]/+page.server.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { redirect } from "@sveltejs/kit";
2
+ import type { PageServerLoad } from "./$types";
3
+ import { loginEnabled } from "$lib/server/auth";
4
+ import { createConversationFromShare } from "$lib/server/conversation";
5
+
6
+ export const load: PageServerLoad = async ({ url, params, locals, request }) => {
7
+ const leafId = url.searchParams.get("leafId");
8
+
9
+ /// if logged in and valid share ID, create conversation from share
10
+ if (loginEnabled && locals.user && params.id) {
11
+ const conversationId = await createConversationFromShare(
12
+ params.id,
13
+ locals,
14
+ request.headers.get("User-Agent") ?? undefined
15
+ );
16
+
17
+ return redirect(
18
+ 302,
19
+ `../conversation/${conversationId}?leafId=${leafId}&fromShare=${params.id}`
20
+ );
21
+ }
22
+ return redirect(302, `../conversation/${params.id}?leafId=${leafId}`);
23
+ };
src/routes/r/[id]/+page.ts DELETED
@@ -1,7 +0,0 @@
1
- import { redirect, type LoadEvent } from "@sveltejs/kit";
2
-
3
- export const load = async ({ params, url }: LoadEvent) => {
4
- const leafId = url.searchParams.get("leafId");
5
-
6
- redirect(302, "../conversation/" + params.id + `?leafId=${leafId}`);
7
- };