nsarrazin HF staff commited on
Commit
a799675
1 Parent(s): af58e08

Remove shared routes (#478)

Browse files

* rm shared route

* fix sharing of shared convs

src/lib/shareConversation.ts CHANGED
@@ -1,25 +1,31 @@
1
  import { base } from "$app/paths";
2
  import { ERROR_MESSAGES, error } from "$lib/stores/errors";
3
  import { share } from "./utils/share";
4
-
 
 
5
  export async function shareConversation(id: string, title: string) {
6
  try {
7
- const res = await fetch(`${base}/conversation/${id}/share`, {
8
- method: "POST",
9
- headers: {
10
- "Content-Type": "application/json",
11
- },
12
- });
13
-
14
- if (!res.ok) {
15
- error.set("Error while sharing conversation, try again.");
16
- console.error("Error while sharing conversation: " + (await res.text()));
17
- return;
18
- }
19
 
20
- const { url } = await res.json();
 
 
 
 
21
 
22
- share(url, title);
 
 
23
  } catch (err) {
24
  error.set(ERROR_MESSAGES.default);
25
  console.error(err);
 
1
  import { base } from "$app/paths";
2
  import { ERROR_MESSAGES, error } from "$lib/stores/errors";
3
  import { share } from "./utils/share";
4
+ import { page } from "$app/stores";
5
+ import { get } from "svelte/store";
6
+ import { getShareUrl } from "./utils/getShareUrl";
7
  export async function shareConversation(id: string, title: string) {
8
  try {
9
+ if (id.length === 7) {
10
+ const url = get(page).url;
11
+ share(getShareUrl(url, id), title);
12
+ } else {
13
+ const res = await fetch(`${base}/conversation/${id}/share`, {
14
+ method: "POST",
15
+ headers: {
16
+ "Content-Type": "application/json",
17
+ },
18
+ });
 
 
19
 
20
+ if (!res.ok) {
21
+ error.set("Error while sharing conversation, try again.");
22
+ console.error("Error while sharing conversation: " + (await res.text()));
23
+ return;
24
+ }
25
 
26
+ const { url } = await res.json();
27
+ share(url, title);
28
+ }
29
  } catch (err) {
30
  error.set(ERROR_MESSAGES.default);
31
  console.error(err);
src/lib/stores/pendingMessageIdToRetry.ts DELETED
@@ -1,4 +0,0 @@
1
- import type { Message } from "$lib/types/Message";
2
- import { writable } from "svelte/store";
3
-
4
- export const pendingMessageIdToRetry = writable<Message["id"] | null>(null);
 
 
 
 
 
src/lib/utils/getShareUrl.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { base } from "$app/paths";
2
+ import { PUBLIC_ORIGIN, PUBLIC_SHARE_PREFIX } from "$env/static/public";
3
+
4
+ export function getShareUrl(url: URL, shareId: string): string {
5
+ return `${PUBLIC_SHARE_PREFIX || `${PUBLIC_ORIGIN || url.origin}${base}`}/r/${shareId}`;
6
+ }
src/routes/conversation/[id]/+page.server.ts CHANGED
@@ -5,34 +5,50 @@ import { authCondition } from "$lib/server/auth";
5
  import { UrlDependency } from "$lib/types/UrlDependency";
6
 
7
  export const load = async ({ params, depends, locals }) => {
8
- // todo: add validation on params.id
9
- const conversation = await collections.conversations.findOne({
10
- _id: new ObjectId(params.id),
11
- ...authCondition(locals),
12
- });
13
 
14
- depends(UrlDependency.Conversation);
 
 
 
 
 
 
15
 
16
- if (!conversation) {
17
- const conversationExists =
18
- (await collections.conversations.countDocuments({
19
- _id: new ObjectId(params.id),
20
- })) !== 0;
21
-
22
- if (conversationExists) {
23
- throw error(
24
- 403,
25
- "You don't have access to this conversation. If someone gave you this link, ask them to use the 'share' feature instead."
26
- );
27
  }
 
 
 
 
 
 
28
 
29
- throw error(404, "Conversation not found.");
30
- }
 
 
 
 
 
31
 
 
 
 
 
 
 
 
 
 
 
32
  return {
33
  messages: conversation.messages,
34
  title: conversation.title,
35
  model: conversation.model,
36
  preprompt: conversation.preprompt,
 
37
  };
38
  };
 
5
  import { UrlDependency } from "$lib/types/UrlDependency";
6
 
7
  export const load = async ({ params, depends, locals }) => {
8
+ let conversation;
9
+ let shared = false;
 
 
 
10
 
11
+ // if the conver
12
+ if (params.id.length === 7) {
13
+ // shared link of length 7
14
+ conversation = await collections.sharedConversations.findOne({
15
+ _id: params.id,
16
+ });
17
+ shared = true;
18
 
19
+ if (!conversation) {
20
+ throw error(404, "Conversation not found");
 
 
 
 
 
 
 
 
 
21
  }
22
+ } else {
23
+ // todo: add validation on params.id
24
+ conversation = await collections.conversations.findOne({
25
+ _id: new ObjectId(params.id),
26
+ ...authCondition(locals),
27
+ });
28
 
29
+ depends(UrlDependency.Conversation);
30
+
31
+ if (!conversation) {
32
+ const conversationExists =
33
+ (await collections.conversations.countDocuments({
34
+ _id: new ObjectId(params.id),
35
+ })) !== 0;
36
 
37
+ if (conversationExists) {
38
+ throw error(
39
+ 403,
40
+ "You don't have access to this conversation. If someone gave you this link, ask them to use the 'share' feature instead."
41
+ );
42
+ }
43
+
44
+ throw error(404, "Conversation not found.");
45
+ }
46
+ }
47
  return {
48
  messages: conversation.messages,
49
  title: conversation.title,
50
  model: conversation.model,
51
  preprompt: conversation.preprompt,
52
+ shared,
53
  };
54
  };
src/routes/conversation/[id]/+page.svelte CHANGED
@@ -1,10 +1,9 @@
1
  <script lang="ts">
2
  import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
3
  import { pendingMessage } from "$lib/stores/pendingMessage";
4
- import { pendingMessageIdToRetry } from "$lib/stores/pendingMessageIdToRetry";
5
  import { onMount } from "svelte";
6
  import { page } from "$app/stores";
7
- import { invalidate } from "$app/navigation";
8
  import { base } from "$app/paths";
9
  import { shareConversation } from "$lib/shareConversation";
10
  import { UrlDependency } from "$lib/types/UrlDependency";
@@ -34,6 +33,35 @@
34
  let pending = false;
35
  let loginRequired = false;
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  // this function is used to send new message to the backends
38
  async function writeMessage(message: string, messageId = randomUUID()) {
39
  if (!message.trim()) return;
@@ -76,6 +104,10 @@
76
  throw new Error("Body not defined");
77
  }
78
 
 
 
 
 
79
  // eslint-disable-next-line no-undef
80
  const encoder = new TextDecoderStream();
81
  const reader = response?.body?.pipeThrough(encoder).getReader();
@@ -84,7 +116,7 @@
84
  // this is a bit ugly
85
  // we read the stream until we get the final answer
86
  while (finalAnswer === "") {
87
- // await new Promise((r) => setTimeout(r, 25));
88
 
89
  // check for abort
90
  if (isAborted) {
@@ -111,6 +143,7 @@
111
  let update = JSON.parse(el) as MessageUpdate;
112
  if (update.type === "finalAnswer") {
113
  finalAnswer = update.text;
 
114
  invalidate(UrlDependency.Conversation);
115
  } else if (update.type === "stream") {
116
  pending = false;
@@ -128,6 +161,8 @@
128
  }
129
  } else if (update.type === "webSearch") {
130
  webSearchMessages = [...webSearchMessages, update];
 
 
131
  }
132
  } catch (parseError) {
133
  // in case of parsing error we wait for the next message
@@ -185,15 +220,38 @@
185
  }
186
 
187
  onMount(async () => {
 
188
  if ($pendingMessage) {
189
- const val = $pendingMessage;
190
- const messageId = $pendingMessageIdToRetry || undefined;
191
- $pendingMessage = "";
192
- $pendingMessageIdToRetry = null;
193
-
194
- writeMessage(val, messageId);
195
  }
196
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  $: $page.params.id, (isAborted = true);
198
  $: title = data.conversations.find((conv) => conv.id === $page.params.id)?.title ?? data.title;
199
 
@@ -218,10 +276,11 @@
218
  {loading}
219
  {pending}
220
  {messages}
 
221
  preprompt={data.preprompt}
222
  bind:webSearchMessages
223
- on:message={(event) => writeMessage(event.detail)}
224
- on:retry={(event) => writeMessage(event.detail.content, event.detail.id)}
225
  on:vote={(event) => voteMessage(event.detail.score, event.detail.id)}
226
  on:share={() => shareConversation($page.params.id, data.title)}
227
  on:stop={() => (isAborted = true)}
 
1
  <script lang="ts">
2
  import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
3
  import { pendingMessage } from "$lib/stores/pendingMessage";
 
4
  import { onMount } from "svelte";
5
  import { page } from "$app/stores";
6
+ import { goto, invalidate } from "$app/navigation";
7
  import { base } from "$app/paths";
8
  import { shareConversation } from "$lib/shareConversation";
9
  import { UrlDependency } from "$lib/types/UrlDependency";
 
33
  let pending = false;
34
  let loginRequired = false;
35
 
36
+ async function convFromShared() {
37
+ try {
38
+ loading = true;
39
+ const res = await fetch(`${base}/conversation`, {
40
+ method: "POST",
41
+ headers: {
42
+ "Content-Type": "application/json",
43
+ },
44
+ body: JSON.stringify({
45
+ fromShare: $page.params.id,
46
+ model: data.model,
47
+ }),
48
+ });
49
+
50
+ if (!res.ok) {
51
+ error.set("Error while creating conversation, try again.");
52
+ console.error("Error while creating conversation: " + (await res.text()));
53
+ return;
54
+ }
55
+
56
+ const { conversationId } = await res.json();
57
+
58
+ return conversationId;
59
+ } catch (err) {
60
+ error.set(ERROR_MESSAGES.default);
61
+ console.error(String(err));
62
+ throw err;
63
+ }
64
+ }
65
  // this function is used to send new message to the backends
66
  async function writeMessage(message: string, messageId = randomUUID()) {
67
  if (!message.trim()) return;
 
104
  throw new Error("Body not defined");
105
  }
106
 
107
+ if (!response.ok) {
108
+ error.set((await response.json())?.message);
109
+ return;
110
+ }
111
  // eslint-disable-next-line no-undef
112
  const encoder = new TextDecoderStream();
113
  const reader = response?.body?.pipeThrough(encoder).getReader();
 
116
  // this is a bit ugly
117
  // we read the stream until we get the final answer
118
  while (finalAnswer === "") {
119
+ await new Promise((r) => setTimeout(r, 25));
120
 
121
  // check for abort
122
  if (isAborted) {
 
143
  let update = JSON.parse(el) as MessageUpdate;
144
  if (update.type === "finalAnswer") {
145
  finalAnswer = update.text;
146
+ reader.cancel();
147
  invalidate(UrlDependency.Conversation);
148
  } else if (update.type === "stream") {
149
  pending = false;
 
161
  }
162
  } else if (update.type === "webSearch") {
163
  webSearchMessages = [...webSearchMessages, update];
164
+ } else {
165
+ console.log();
166
  }
167
  } catch (parseError) {
168
  // in case of parsing error we wait for the next message
 
220
  }
221
 
222
  onMount(async () => {
223
+ // only used in case of creating new conversations (from the parent POST endpoint)
224
  if ($pendingMessage) {
225
+ writeMessage($pendingMessage);
 
 
 
 
 
226
  }
227
  });
228
+
229
+ async function onMessage(event: CustomEvent<string>) {
230
+ if (!data.shared) {
231
+ writeMessage(event.detail);
232
+ } else {
233
+ convFromShared()
234
+ .then(async (convId) => {
235
+ await goto(`${base}/conversation/${convId}`, { invalidateAll: true });
236
+ })
237
+ .then(() => writeMessage(event.detail))
238
+ .finally(() => (loading = false));
239
+ }
240
+ }
241
+
242
+ async function onRetry(event: CustomEvent<{ id: Message["id"]; content: string }>) {
243
+ if (!data.shared) {
244
+ writeMessage(event.detail.content, event.detail.id);
245
+ } else {
246
+ convFromShared()
247
+ .then(async (convId) => {
248
+ await goto(`${base}/conversation/${convId}`, { invalidateAll: true });
249
+ })
250
+ .then(() => writeMessage(event.detail.content, event.detail.id))
251
+ .finally(() => (loading = false));
252
+ }
253
+ }
254
+
255
  $: $page.params.id, (isAborted = true);
256
  $: title = data.conversations.find((conv) => conv.id === $page.params.id)?.title ?? data.title;
257
 
 
276
  {loading}
277
  {pending}
278
  {messages}
279
+ shared={data.shared}
280
  preprompt={data.preprompt}
281
  bind:webSearchMessages
282
+ on:message={onMessage}
283
+ on:retry={onRetry}
284
  on:vote={(event) => voteMessage(event.detail.score, event.detail.id)}
285
  on:share={() => shareConversation($page.params.id, data.title)}
286
  on:stop={() => (isAborted = true)}
src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts CHANGED
@@ -6,14 +6,17 @@ import { error } from "@sveltejs/kit";
6
  import { ObjectId } from "mongodb";
7
 
8
  export async function GET({ params, locals }) {
9
- const convId = new ObjectId(params.id);
10
-
11
- const conv = await collections.conversations.findOne({
12
- _id: convId,
13
- ...authCondition(locals),
14
- });
15
-
16
- if (!conv) {
 
 
 
17
  throw error(404, "Conversation not found");
18
  }
19
 
@@ -31,8 +34,12 @@ export async function GET({ params, locals }) {
31
  throw error(404, "Conversation model not found");
32
  }
33
 
 
 
34
  const prompt = await buildPrompt({
35
- messages: conv.messages.slice(0, messageIndex + 1),
 
 
36
  model: model,
37
  });
38
 
 
6
  import { ObjectId } from "mongodb";
7
 
8
  export async function GET({ params, locals }) {
9
+ const conv =
10
+ params.id.length === 7
11
+ ? await collections.sharedConversations.findOne({
12
+ _id: params.id,
13
+ })
14
+ : await collections.conversations.findOne({
15
+ _id: new ObjectId(params.id),
16
+ ...authCondition(locals),
17
+ });
18
+
19
+ if (conv === null) {
20
  throw error(404, "Conversation not found");
21
  }
22
 
 
34
  throw error(404, "Conversation model not found");
35
  }
36
 
37
+ const messagesUpTo = conv.messages.slice(0, messageIndex + 1);
38
+
39
  const prompt = await buildPrompt({
40
+ preprompt: conv.preprompt,
41
+ webSearch: messagesUpTo[messagesUpTo.length - 1].webSearch,
42
+ messages: messagesUpTo,
43
  model: model,
44
  });
45
 
src/routes/conversation/[id]/share/+server.ts CHANGED
@@ -1,8 +1,7 @@
1
- import { base } from "$app/paths";
2
- import { PUBLIC_ORIGIN, PUBLIC_SHARE_PREFIX } from "$env/static/public";
3
  import { authCondition } from "$lib/server/auth";
4
  import { collections } from "$lib/server/database";
5
  import type { SharedConversation } from "$lib/types/SharedConversation";
 
6
  import { hashConv } from "$lib/utils/hashConv";
7
  import { error } from "@sveltejs/kit";
8
  import { ObjectId } from "mongodb";
@@ -51,7 +50,3 @@ export async function POST({ params, url, locals }) {
51
  { headers: { "Content-Type": "application/json" } }
52
  );
53
  }
54
-
55
- function getShareUrl(url: URL, shareId: string): string {
56
- return `${PUBLIC_SHARE_PREFIX || `${PUBLIC_ORIGIN || url.origin}${base}`}/r/${shareId}`;
57
- }
 
 
 
1
  import { authCondition } from "$lib/server/auth";
2
  import { collections } from "$lib/server/database";
3
  import type { SharedConversation } from "$lib/types/SharedConversation";
4
+ import { getShareUrl } from "$lib/utils/getShareUrl.js";
5
  import { hashConv } from "$lib/utils/hashConv";
6
  import { error } from "@sveltejs/kit";
7
  import { ObjectId } from "mongodb";
 
50
  { headers: { "Content-Type": "application/json" } }
51
  );
52
  }
 
 
 
 
src/routes/r/[id]/+page.server.ts DELETED
@@ -1,20 +0,0 @@
1
- import type { PageServerLoad } from "./$types";
2
- import { collections } from "$lib/server/database";
3
- import { error } from "@sveltejs/kit";
4
-
5
- export const load: PageServerLoad = async ({ params }) => {
6
- const conversation = await collections.sharedConversations.findOne({
7
- _id: params.id,
8
- });
9
-
10
- if (!conversation) {
11
- throw error(404, "Conversation not found");
12
- }
13
-
14
- return {
15
- preprompt: conversation.preprompt,
16
- messages: conversation.messages,
17
- title: conversation.title,
18
- model: conversation.model,
19
- };
20
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/routes/r/[id]/+page.svelte DELETED
@@ -1,87 +0,0 @@
1
- <script lang="ts">
2
- import { goto } from "$app/navigation";
3
- import { base } from "$app/paths";
4
- import { page } from "$app/stores";
5
- import { PUBLIC_APP_DISCLAIMER } from "$env/static/public";
6
- import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
7
- import { ERROR_MESSAGES, error } from "$lib/stores/errors";
8
- import { pendingMessage } from "$lib/stores/pendingMessage";
9
- import { pendingMessageIdToRetry } from "$lib/stores/pendingMessageIdToRetry";
10
- import { findCurrentModel } from "$lib/utils/models";
11
- import { share } from "$lib/utils/share";
12
- import type { PageData } from "./$types";
13
-
14
- export let data: PageData;
15
-
16
- let loading = false;
17
-
18
- async function createConversation() {
19
- try {
20
- loading = true;
21
- const res = await fetch(`${base}/conversation`, {
22
- method: "POST",
23
- headers: {
24
- "Content-Type": "application/json",
25
- },
26
- body: JSON.stringify({
27
- fromShare: $page.params.id,
28
- model: data.model,
29
- }),
30
- });
31
-
32
- if (!res.ok) {
33
- error.set("Error while creating conversation, try again.");
34
- console.error("Error while creating conversation: " + (await res.text()));
35
- return;
36
- }
37
-
38
- const { conversationId } = await res.json();
39
-
40
- return conversationId;
41
- } catch (err) {
42
- error.set(ERROR_MESSAGES.default);
43
- console.error(String(err));
44
- throw err;
45
- }
46
- }
47
-
48
- async function shareConversation() {
49
- const url = `${window.location.origin}${window.location.pathname}`;
50
-
51
- share(url, data.title);
52
- }
53
- </script>
54
-
55
- <svelte:head>
56
- <title>{data.title}</title>
57
- </svelte:head>
58
-
59
- <ChatWindow
60
- {loading}
61
- shared={true}
62
- messages={data.messages}
63
- preprompt={data.preprompt}
64
- on:message={(ev) =>
65
- createConversation()
66
- .then((convId) => {
67
- $pendingMessage = ev.detail;
68
- return goto(`${base}/conversation/${convId}`, { invalidateAll: true });
69
- })
70
- .finally(() => (loading = false))}
71
- on:share={shareConversation}
72
- on:retry={(ev) =>
73
- createConversation()
74
- .then((convId) => {
75
- $pendingMessageIdToRetry = ev.detail.id;
76
- $pendingMessage = ev.detail.content;
77
- return goto(`${base}/conversation/${convId}`, { invalidateAll: true });
78
- })
79
- .finally(() => (loading = false))}
80
- models={data.models}
81
- currentModel={findCurrentModel(data.models, data.model)}
82
- settings={data.settings}
83
- loginRequired={!$page.error &&
84
- (data.requiresLogin
85
- ? !data.user
86
- : !data.settings.ethicsModalAcceptedAt && !!PUBLIC_APP_DISCLAIMER)}
87
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/routes/r/[id]/+page.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import { redirect } from "@sveltejs/kit";
2
+
3
+ export const load = async ({ params }) => {
4
+ throw redirect(302, "../conversation/" + params.id);
5
+ };
src/routes/r/[id]/message/[messageId]/prompt/+server.ts DELETED
@@ -1,47 +0,0 @@
1
- import { buildPrompt } from "$lib/buildPrompt";
2
- import { collections } from "$lib/server/database";
3
- import { models } from "$lib/server/models";
4
- import { error } from "@sveltejs/kit";
5
-
6
- export async function GET({ params }) {
7
- const conv = await collections.sharedConversations.findOne({
8
- _id: params.id,
9
- });
10
-
11
- if (!conv) {
12
- throw error(404, "Conversation not found");
13
- }
14
-
15
- const messageId = params.messageId;
16
-
17
- const messageIndex = conv.messages.findIndex((msg) => msg.id === messageId);
18
-
19
- if (messageIndex === -1) {
20
- throw error(404, "Message not found");
21
- }
22
-
23
- const model = models.find((m) => m.id === conv.model);
24
-
25
- if (!model) {
26
- throw error(404, "Conversation model not found");
27
- }
28
-
29
- const prompt = await buildPrompt({ messages: conv.messages.slice(0, messageIndex + 1), model });
30
-
31
- return new Response(
32
- JSON.stringify(
33
- {
34
- note: "This is a preview of the prompt that will be sent to the model when retrying the message. It may differ from what was sent in the past if the parameters have been updated since",
35
- prompt,
36
- model: model.name,
37
- parameters: {
38
- ...model.parameters,
39
- return_full_text: false,
40
- },
41
- },
42
- null,
43
- 2
44
- ),
45
- { headers: { "Content-Type": "application/json" } }
46
- );
47
- }