nsarrazin HF staff victor HF staff Mishig commited on
Commit
5c9a37f
1 Parent(s): 171ed27

Bring back disclaimer modal & updates to guest mode (#526)

Browse files

* Changes to modal on login:
- Ethics modal on page load
- Login modal pop up for guest mode later
- Change message limit to be 5 messages across all conversations

* fix login button width

* tweak modals

* Update src/routes/+layout.server.ts

Co-authored-by: Mishig <[email protected]>

* Fix linting

---------

Co-authored-by: Victor Mustar <[email protected]>
Co-authored-by: Mishig <[email protected]>

.env.template CHANGED
@@ -142,7 +142,7 @@ PUBLIC_APP_DATA_SHARING=1
142
  PUBLIC_APP_DISCLAIMER=1
143
 
144
  RATE_LIMIT=16
145
- MESSAGES_BEFORE_LOGIN=1# how many messages a user can send in a conversation before having to login. set to 0 to force login right away
146
 
147
  PUBLIC_GOOGLE_ANALYTICS_ID=G-8Q63TH4CSL
148
 
 
142
  PUBLIC_APP_DISCLAIMER=1
143
 
144
  RATE_LIMIT=16
145
+ MESSAGES_BEFORE_LOGIN=5# how many messages a user can send in a conversation before having to login. set to 0 to force login right away
146
 
147
  PUBLIC_GOOGLE_ANALYTICS_ID=G-8Q63TH4CSL
148
 
src/lib/components/DisclaimerModal.svelte ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { base } from "$app/paths";
3
+ import { page } from "$app/stores";
4
+ import { PUBLIC_APP_DESCRIPTION, PUBLIC_APP_NAME } from "$env/static/public";
5
+ import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
6
+ import Modal from "$lib/components/Modal.svelte";
7
+ import type { LayoutData } from "../../routes/$types";
8
+ import Logo from "./icons/Logo.svelte";
9
+
10
+ export let settings: LayoutData["settings"];
11
+ </script>
12
+
13
+ <Modal>
14
+ <div
15
+ class="flex w-full flex-col items-center gap-6 bg-gradient-to-b from-primary-500/40 via-primary-500/10 to-primary-500/0 px-5 pb-8 pt-9 text-center sm:px-6"
16
+ >
17
+ <h2 class="flex items-center text-2xl font-semibold text-gray-800">
18
+ <Logo classNames="mr-1" />
19
+ {PUBLIC_APP_NAME}
20
+ </h2>
21
+
22
+ <p class="text-lg font-semibold leading-snug text-gray-800" style="text-wrap: balance;">
23
+ {PUBLIC_APP_DESCRIPTION}
24
+ </p>
25
+
26
+ <p class="text-sm text-gray-500">
27
+ Disclaimer: AI is an area of active research with known problems such as biased generation and
28
+ misinformation. Do not use this application for high-stakes decisions or advice.
29
+ </p>
30
+
31
+ <div class="flex w-full flex-col items-center gap-2">
32
+ {#if $page.data.guestMode || !$page.data.loginEnabled}
33
+ <form action="{base}/settings" target="_parent" method="POST" class="w-full">
34
+ <input type="hidden" name="ethicsModalAccepted" value={true} />
35
+ {#each Object.entries(settings).filter(([k]) => !(k === "customPrompts")) as [key, val]}
36
+ <input type="hidden" name={key} value={val} />
37
+ {/each}
38
+ <input
39
+ type="hidden"
40
+ name="customPrompts"
41
+ value={JSON.stringify(settings.customPrompts)}
42
+ />
43
+ <button
44
+ type="submit"
45
+ class="w-full justify-center rounded-full border-2 border-gray-300 bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-100"
46
+ class:bg-white={$page.data.loginEnabled}
47
+ class:text-gray-800={$page.data.loginEnabled}
48
+ >
49
+ {#if $page.data.loginEnabled}
50
+ Try as guest
51
+ {:else}
52
+ Start chatting
53
+ {/if}
54
+ </button>
55
+ </form>
56
+ {/if}
57
+ {#if $page.data.loginEnabled}
58
+ <form action="{base}/login" target="_parent" method="POST" class="w-full">
59
+ <button
60
+ type="submit"
61
+ class="flex w-full items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
62
+ >
63
+ Sign in
64
+ {#if PUBLIC_APP_NAME === "HuggingChat"}
65
+ with <LogoHuggingFaceBorderless classNames="text-xl mr-1 ml-1.5 flex-none" /> Hugging Face
66
+ {/if}
67
+ </button>
68
+ </form>
69
+ {/if}
70
+ </div>
71
+ </div>
72
+ </Modal>
src/lib/components/LoginModal.svelte CHANGED
@@ -1,7 +1,7 @@
1
  <script lang="ts">
2
  import { base } from "$app/paths";
3
  import { page } from "$app/stores";
4
- import { PUBLIC_APP_DATA_SHARING, PUBLIC_APP_NAME, PUBLIC_VERSION } from "$env/static/public";
5
  import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
6
  import Modal from "$lib/components/Modal.svelte";
7
  import type { LayoutData } from "../../routes/$types";
@@ -9,47 +9,32 @@
9
  export let settings: LayoutData["settings"];
10
  </script>
11
 
12
- <Modal>
13
  <div
14
- class="flex w-full flex-col items-center gap-6 bg-gradient-to-t from-primary-500/40 via-primary-500/10 to-primary-500/0 px-4 pb-10 pt-9 text-center"
15
  >
16
  <h2 class="flex items-center text-2xl font-semibold text-gray-800">
17
  <Logo classNames="mr-1" />
18
  {PUBLIC_APP_NAME}
19
- <div
20
- class="ml-3 flex h-6 items-center rounded-lg border border-gray-100 bg-gray-50 px-2 text-base text-gray-400"
21
- >
22
- v{PUBLIC_VERSION}
23
- </div>
24
  </h2>
25
- {#if $page.data.requiresLogin}
26
- <p
27
- class="px-4 text-lg font-semibold leading-snug text-gray-800 sm:px-12"
28
- style="text-wrap: balance;"
29
- >
30
- Please Sign in with Hugging Face to continue
31
- </p>
32
- {/if}
33
- <p class="text-base text-gray-800">
34
- Disclaimer: AI is an area of active research with known problems such as biased generation and
35
- misinformation. Do not use this application for high-stakes decisions or advice.
36
  </p>
37
- {#if PUBLIC_APP_DATA_SHARING}
38
- <p class="px-2 text-sm text-gray-500">
39
- Your conversations will be shared with model authors unless you disable it from your
40
- settings.
41
- </p>
42
- {/if}
43
  <form
44
- action="{base}/{$page.data.requiresLogin ? 'login' : 'settings'}"
45
  target="_parent"
46
  method="POST"
47
  class="flex w-full flex-col items-center gap-2"
48
  >
49
- {#if $page.data.requiresLogin}
50
  <button
51
  type="submit"
52
- class="mt-2 flex items-center whitespace-nowrap rounded-full bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-primary-500"
53
  >
54
  Sign in
55
  {#if PUBLIC_APP_NAME === "HuggingChat"}
@@ -63,7 +48,7 @@
63
  {/each}
64
  <button
65
  type="submit"
66
- class="mt-2 rounded-full bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-primary-500"
67
  >
68
  Start chatting
69
  </button>
 
1
  <script lang="ts">
2
  import { base } from "$app/paths";
3
  import { page } from "$app/stores";
4
+ import { PUBLIC_APP_DESCRIPTION, PUBLIC_APP_NAME } from "$env/static/public";
5
  import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
6
  import Modal from "$lib/components/Modal.svelte";
7
  import type { LayoutData } from "../../routes/$types";
 
9
  export let settings: LayoutData["settings"];
10
  </script>
11
 
12
+ <Modal on:close>
13
  <div
14
+ class="flex w-full flex-col items-center gap-6 bg-gradient-to-b from-primary-500/40 via-primary-500/10 to-primary-500/0 px-5 pb-8 pt-9 text-center"
15
  >
16
  <h2 class="flex items-center text-2xl font-semibold text-gray-800">
17
  <Logo classNames="mr-1" />
18
  {PUBLIC_APP_NAME}
 
 
 
 
 
19
  </h2>
20
+ <p class="text-lg font-semibold leading-snug text-gray-800" style="text-wrap: balance;">
21
+ {PUBLIC_APP_DESCRIPTION}
 
 
 
 
 
 
 
 
 
22
  </p>
23
+ <p class="rounded-xl border bg-white/80 p-2 text-base text-gray-800">
24
+ You have reached the guest message limit, please Sign In with your Hugging Face account to
25
+ continue.
26
+ </p>
27
+
 
28
  <form
29
+ action="{base}/{$page.data.loginRequired ? 'login' : 'settings'}"
30
  target="_parent"
31
  method="POST"
32
  class="flex w-full flex-col items-center gap-2"
33
  >
34
+ {#if $page.data.loginRequired}
35
  <button
36
  type="submit"
37
+ class="flex w-full items-center justify-center whitespace-nowrap rounded-full bg-black px-5 py-2 text-center text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
38
  >
39
  Sign in
40
  {#if PUBLIC_APP_NAME === "HuggingChat"}
 
48
  {/each}
49
  <button
50
  type="submit"
51
+ class="flex w-full items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
52
  >
53
  Start chatting
54
  </button>
src/lib/components/NavMenu.svelte CHANGED
@@ -67,7 +67,7 @@
67
  <form action="{base}/login" method="POST" target="_parent">
68
  <button
69
  type="submit"
70
- class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
71
  >
72
  Login
73
  </button>
 
67
  <form action="{base}/login" method="POST" target="_parent">
68
  <button
69
  type="submit"
70
+ class="flex h-9 w-full flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
71
  >
72
  Login
73
  </button>
src/lib/components/chat/ChatWindow.svelte CHANGED
@@ -15,6 +15,8 @@
15
  import WebSearchToggle from "../WebSearchToggle.svelte";
16
  import LoginModal from "../LoginModal.svelte";
17
  import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
 
 
18
 
19
  export let messages: Message[] = [];
20
  export let loading = false;
@@ -26,7 +28,6 @@
26
  export let webSearchMessages: WebSearchUpdate[] = [];
27
  export let preprompt: string | undefined = undefined;
28
 
29
- export let loginRequired = false;
30
  $: isReadOnly = !models.some((model) => model.id === currentModel.id);
31
 
32
  let loginModalOpen = false;
@@ -47,8 +48,15 @@
47
  </script>
48
 
49
  <div class="relative min-h-0 min-w-0">
50
- {#if loginModalOpen}
51
- <LoginModal {settings} on:close={() => (loginModalOpen = false)} />
 
 
 
 
 
 
 
52
  {/if}
53
  <ChatMessages
54
  {loading}
@@ -61,7 +69,13 @@
61
  isAuthor={!shared}
62
  {webSearchMessages}
63
  {preprompt}
64
- on:message
 
 
 
 
 
 
65
  on:vote
66
  on:retry={(ev) => {
67
  if (!loading) dispatch("retry", ev.detail);
@@ -91,8 +105,11 @@
91
  placeholder="Ask anything"
92
  bind:value={message}
93
  on:submit={handleSubmit}
94
- on:keypress={() => {
95
- if (loginRequired) loginModalOpen = true;
 
 
 
96
  }}
97
  maxRows={4}
98
  disabled={isReadOnly}
 
15
  import WebSearchToggle from "../WebSearchToggle.svelte";
16
  import LoginModal from "../LoginModal.svelte";
17
  import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
18
+ import { page } from "$app/stores";
19
+ import DisclaimerModal from "../DisclaimerModal.svelte";
20
 
21
  export let messages: Message[] = [];
22
  export let loading = false;
 
28
  export let webSearchMessages: WebSearchUpdate[] = [];
29
  export let preprompt: string | undefined = undefined;
30
 
 
31
  $: isReadOnly = !models.some((model) => model.id === currentModel.id);
32
 
33
  let loginModalOpen = false;
 
48
  </script>
49
 
50
  <div class="relative min-h-0 min-w-0">
51
+ {#if !settings.ethicsModalAcceptedAt}
52
+ <DisclaimerModal {settings} />
53
+ {:else if loginModalOpen}
54
+ <LoginModal
55
+ {settings}
56
+ on:close={() => {
57
+ loginModalOpen = false;
58
+ }}
59
+ />
60
  {/if}
61
  <ChatMessages
62
  {loading}
 
69
  isAuthor={!shared}
70
  {webSearchMessages}
71
  {preprompt}
72
+ on:message={(ev) => {
73
+ if ($page.data.loginRequired) {
74
+ loginModalOpen = true;
75
+ } else {
76
+ dispatch("message", ev.detail);
77
+ }
78
+ }}
79
  on:vote
80
  on:retry={(ev) => {
81
  if (!loading) dispatch("retry", ev.detail);
 
105
  placeholder="Ask anything"
106
  bind:value={message}
107
  on:submit={handleSubmit}
108
+ on:keypress={(ev) => {
109
+ if ($page.data.loginRequired) {
110
+ ev.preventDefault();
111
+ loginModalOpen = true;
112
+ }
113
  }}
114
  maxRows={4}
115
  disabled={isReadOnly}
src/routes/+layout.server.ts CHANGED
@@ -38,6 +38,26 @@ export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
38
  });
39
  }
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  return {
42
  conversations: await conversations
43
  .find(authCondition(locals))
@@ -83,7 +103,8 @@ export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
83
  avatarUrl: locals.user.avatarUrl,
84
  email: locals.user.email,
85
  },
86
- requiresLogin: requiresUser,
87
- messagesBeforeLogin: MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0,
 
88
  };
89
  };
 
38
  });
39
  }
40
 
41
+ // get the number of messages where `from === "assistant"` across all conversations.
42
+ const totalMessages =
43
+ (
44
+ await conversations
45
+ .aggregate([
46
+ { $match: authCondition(locals) },
47
+ { $project: { messages: 1 } },
48
+ { $unwind: "$messages" },
49
+ { $match: { "messages.from": "assistant" } },
50
+ { $count: "messages" },
51
+ ])
52
+ .toArray()
53
+ )[0]?.messages ?? 0;
54
+
55
+ const messagesBeforeLogin = MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0;
56
+
57
+ const userHasExceededMessages = messagesBeforeLogin > 0 && totalMessages > messagesBeforeLogin;
58
+
59
+ const loginRequired = requiresUser && !locals.user && userHasExceededMessages;
60
+
61
  return {
62
  conversations: await conversations
63
  .find(authCondition(locals))
 
103
  avatarUrl: locals.user.avatarUrl,
104
  email: locals.user.email,
105
  },
106
+ loginRequired,
107
+ loginEnabled: requiresUser,
108
+ guestMode: requiresUser && messagesBeforeLogin > 0,
109
  };
110
  };
src/routes/+layout.svelte CHANGED
@@ -4,7 +4,7 @@
4
  import { page } from "$app/stores";
5
  import "../styles/main.css";
6
  import { base } from "$app/paths";
7
- import { PUBLIC_ORIGIN, PUBLIC_APP_DISCLAIMER } from "$env/static/public";
8
 
9
  import { shareConversation } from "$lib/shareConversation";
10
  import { UrlDependency } from "$lib/types/UrlDependency";
@@ -14,7 +14,6 @@
14
  import NavMenu from "$lib/components/NavMenu.svelte";
15
  import Toast from "$lib/components/Toast.svelte";
16
  import SettingsModal from "$lib/components/SettingsModal.svelte";
17
- import LoginModal from "$lib/components/LoginModal.svelte";
18
  import { PUBLIC_APP_ASSETS, PUBLIC_APP_NAME } from "$env/static/public";
19
  import titleUpdate from "$lib/stores/titleUpdate";
20
 
@@ -94,13 +93,6 @@
94
 
95
  $: if ($error) onError();
96
 
97
- const requiresLogin =
98
- !$page.error &&
99
- !$page.route.id?.startsWith("/r/") &&
100
- (data.requiresLogin
101
- ? !data.user
102
- : !data.settings.ethicsModalAcceptedAt && !!PUBLIC_APP_DISCLAIMER);
103
-
104
  $: if ($titleUpdate) {
105
  const convIdx = data.conversations.findIndex(({ id }) => id === $titleUpdate?.convId);
106
 
@@ -157,7 +149,7 @@
157
  <NavMenu
158
  conversations={data.conversations}
159
  user={data.user}
160
- canLogin={data.user === undefined && data.requiresLogin}
161
  on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
162
  on:deleteConversation={(ev) => deleteConversation(ev.detail)}
163
  on:clickSettings={() => (isSettingsOpen = true)}
@@ -168,7 +160,7 @@
168
  <NavMenu
169
  conversations={data.conversations}
170
  user={data.user}
171
- canLogin={data.user === undefined && data.requiresLogin}
172
  on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
173
  on:deleteConversation={(ev) => deleteConversation(ev.detail)}
174
  on:clickSettings={() => (isSettingsOpen = true)}
@@ -185,8 +177,5 @@
185
  models={data.models}
186
  />
187
  {/if}
188
- {#if requiresLogin && data.messagesBeforeLogin === 0}
189
- <LoginModal settings={data.settings} />
190
- {/if}
191
  <slot />
192
  </div>
 
4
  import { page } from "$app/stores";
5
  import "../styles/main.css";
6
  import { base } from "$app/paths";
7
+ import { PUBLIC_ORIGIN } from "$env/static/public";
8
 
9
  import { shareConversation } from "$lib/shareConversation";
10
  import { UrlDependency } from "$lib/types/UrlDependency";
 
14
  import NavMenu from "$lib/components/NavMenu.svelte";
15
  import Toast from "$lib/components/Toast.svelte";
16
  import SettingsModal from "$lib/components/SettingsModal.svelte";
 
17
  import { PUBLIC_APP_ASSETS, PUBLIC_APP_NAME } from "$env/static/public";
18
  import titleUpdate from "$lib/stores/titleUpdate";
19
 
 
93
 
94
  $: if ($error) onError();
95
 
 
 
 
 
 
 
 
96
  $: if ($titleUpdate) {
97
  const convIdx = data.conversations.findIndex(({ id }) => id === $titleUpdate?.convId);
98
 
 
149
  <NavMenu
150
  conversations={data.conversations}
151
  user={data.user}
152
+ canLogin={data.user === undefined && data.loginEnabled}
153
  on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
154
  on:deleteConversation={(ev) => deleteConversation(ev.detail)}
155
  on:clickSettings={() => (isSettingsOpen = true)}
 
160
  <NavMenu
161
  conversations={data.conversations}
162
  user={data.user}
163
+ canLogin={data.user === undefined && data.loginEnabled}
164
  on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
165
  on:deleteConversation={(ev) => deleteConversation(ev.detail)}
166
  on:clickSettings={() => (isSettingsOpen = true)}
 
177
  models={data.models}
178
  />
179
  {/if}
 
 
 
180
  <slot />
181
  </div>
src/routes/conversation/[id]/+page.svelte CHANGED
@@ -12,7 +12,6 @@
12
  import { findCurrentModel } from "$lib/utils/models";
13
  import { webSearchParameters } from "$lib/stores/webSearchParameters";
14
  import type { Message } from "$lib/types/Message";
15
- import { PUBLIC_APP_DISCLAIMER } from "$env/static/public";
16
  import type { MessageUpdate, WebSearchUpdate } from "$lib/types/MessageUpdate";
17
  import titleUpdate from "$lib/stores/titleUpdate";
18
 
@@ -32,7 +31,6 @@
32
 
33
  let loading = false;
34
  let pending = false;
35
- let loginRequired = false;
36
 
37
  async function convFromShared() {
38
  try {
@@ -266,12 +264,6 @@
266
 
267
  $: $page.params.id, (isAborted = true);
268
  $: title = data.conversations.find((conv) => conv.id === $page.params.id)?.title ?? data.title;
269
-
270
- $: loginRequired =
271
- (data.requiresLogin
272
- ? !data.user
273
- : !data.settings.ethicsModalAcceptedAt && !!PUBLIC_APP_DISCLAIMER) &&
274
- messages.length >= data.messagesBeforeLogin;
275
  </script>
276
 
277
  <svelte:head>
@@ -299,5 +291,4 @@
299
  models={data.models}
300
  currentModel={findCurrentModel([...data.models, ...data.oldModels], data.model)}
301
  settings={data.settings}
302
- {loginRequired}
303
  />
 
12
  import { findCurrentModel } from "$lib/utils/models";
13
  import { webSearchParameters } from "$lib/stores/webSearchParameters";
14
  import type { Message } from "$lib/types/Message";
 
15
  import type { MessageUpdate, WebSearchUpdate } from "$lib/types/MessageUpdate";
16
  import titleUpdate from "$lib/stores/titleUpdate";
17
 
 
31
 
32
  let loading = false;
33
  let pending = false;
 
34
 
35
  async function convFromShared() {
36
  try {
 
264
 
265
  $: $page.params.id, (isAborted = true);
266
  $: title = data.conversations.find((conv) => conv.id === $page.params.id)?.title ?? data.title;
 
 
 
 
 
 
267
  </script>
268
 
269
  <svelte:head>
 
291
  models={data.models}
292
  currentModel={findCurrentModel([...data.models, ...data.oldModels], data.model)}
293
  settings={data.settings}
 
294
  />
src/routes/conversation/[id]/+server.ts CHANGED
@@ -49,13 +49,28 @@ export async function POST({ request, fetch, locals, params, getClientAddress })
49
  ip: getClientAddress(),
50
  });
51
 
52
- // make sure an anonymous user can't post more than one message
53
  if (
54
  !locals.user?._id &&
55
  requiresUser &&
56
- conv.messages.length > (MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0)
57
  ) {
58
- throw error(429, "Exceeded number of messages before login");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
  // check if the user is rate limited
 
49
  ip: getClientAddress(),
50
  });
51
 
52
+ // guest mode check
53
  if (
54
  !locals.user?._id &&
55
  requiresUser &&
56
+ (MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0) > 0
57
  ) {
58
+ const totalMessages =
59
+ (
60
+ await collections.conversations
61
+ .aggregate([
62
+ { $match: authCondition(locals) },
63
+ { $project: { messages: 1 } },
64
+ { $unwind: "$messages" },
65
+ { $match: { "messages.from": "assistant" } },
66
+ { $count: "messages" },
67
+ ])
68
+ .toArray()
69
+ )[0]?.messages ?? 0;
70
+
71
+ if (totalMessages > parseInt(MESSAGES_BEFORE_LOGIN)) {
72
+ throw error(429, "Exceeded number of messages before login");
73
+ }
74
  }
75
 
76
  // check if the user is rate limited