Reopen: Feature/disable login for n messages (#356)
Browse files* disable login on first message
* update banner here too
* modal wording tweaks
* prevent NaN
* fix login wall
* fix flicker
* lint
* put modal text behind login check
* fix bug with sending messages without login
* fix misalignment between ui and api
* fix data update on disable login
---------
Co-authored-by: Nathan Sarrazin <[email protected]>
- .env +5 -4
- src/hooks.server.ts +6 -2
- src/lib/components/LoginModal.svelte +9 -5
- src/routes/+layout.server.ts +2 -1
- src/routes/+layout.svelte +1 -1
- src/routes/conversation/[id]/+page.svelte +9 -2
- src/routes/conversation/[id]/+server.ts +10 -2
.env
CHANGED
@@ -60,10 +60,10 @@ PUBLIC_SHARE_PREFIX=#https://hf.co/chat
|
|
60 |
PUBLIC_GOOGLE_ANALYTICS_ID=#G-XXXXXXXX / Leave empty to disable
|
61 |
PUBLIC_DEPRECATED_GOOGLE_ANALYTICS_ID=#UA-XXXXXXXX-X / Leave empty to disable
|
62 |
PUBLIC_ANNOUNCEMENT_BANNERS=`[
|
63 |
-
|
64 |
-
"title": "
|
65 |
-
"linkTitle": "
|
66 |
-
"linkHref": "https://
|
67 |
}
|
68 |
]`
|
69 |
|
@@ -72,6 +72,7 @@ PARQUET_EXPORT_HF_TOKEN=
|
|
72 |
PARQUET_EXPORT_SECRET=
|
73 |
|
74 |
RATE_LIMIT= # requests per minute
|
|
|
75 |
|
76 |
PUBLIC_APP_NAME=ChatUI # name used as title throughout the app
|
77 |
PUBLIC_APP_ASSETS=chatui # used to find logos & favicons in static/$PUBLIC_APP_ASSETS
|
|
|
60 |
PUBLIC_GOOGLE_ANALYTICS_ID=#G-XXXXXXXX / Leave empty to disable
|
61 |
PUBLIC_DEPRECATED_GOOGLE_ANALYTICS_ID=#UA-XXXXXXXX-X / Leave empty to disable
|
62 |
PUBLIC_ANNOUNCEMENT_BANNERS=`[
|
63 |
+
{
|
64 |
+
"title": "Llama v2 is live on HuggingChat! 🦙",
|
65 |
+
"linkTitle": "Announcement",
|
66 |
+
"linkHref": "https://huggingface.co/blog/llama2"
|
67 |
}
|
68 |
]`
|
69 |
|
|
|
72 |
PARQUET_EXPORT_SECRET=
|
73 |
|
74 |
RATE_LIMIT= # requests per minute
|
75 |
+
MESSAGES_BEFORE_LOGIN=# how many messages a user can send in a conversation before having to login. set to 0 to force login right away
|
76 |
|
77 |
PUBLIC_APP_NAME=ChatUI # name used as title throughout the app
|
78 |
PUBLIC_APP_ASSETS=chatui # used to find logos & favicons in static/$PUBLIC_APP_ASSETS
|
src/hooks.server.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import { COOKIE_NAME } from "$env/static/private";
|
2 |
import type { Handle } from "@sveltejs/kit";
|
3 |
import {
|
4 |
PUBLIC_GOOGLE_ANALYTICS_ID,
|
@@ -64,7 +64,11 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|
64 |
!event.url.pathname.startsWith(`${base}/admin`) &&
|
65 |
!["GET", "OPTIONS", "HEAD"].includes(event.request.method)
|
66 |
) {
|
67 |
-
if (
|
|
|
|
|
|
|
|
|
68 |
return errorResponse(401, ERROR_MESSAGES.authOnly);
|
69 |
}
|
70 |
|
|
|
1 |
+
import { COOKIE_NAME, MESSAGES_BEFORE_LOGIN } from "$env/static/private";
|
2 |
import type { Handle } from "@sveltejs/kit";
|
3 |
import {
|
4 |
PUBLIC_GOOGLE_ANALYTICS_ID,
|
|
|
64 |
!event.url.pathname.startsWith(`${base}/admin`) &&
|
65 |
!["GET", "OPTIONS", "HEAD"].includes(event.request.method)
|
66 |
) {
|
67 |
+
if (
|
68 |
+
!user &&
|
69 |
+
requiresUser &&
|
70 |
+
!((MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0) > 0)
|
71 |
+
) {
|
72 |
return errorResponse(401, ERROR_MESSAGES.authOnly);
|
73 |
}
|
74 |
|
src/lib/components/LoginModal.svelte
CHANGED
@@ -25,11 +25,16 @@
|
|
25 |
v{PUBLIC_VERSION}
|
26 |
</div>
|
27 |
</h2>
|
28 |
-
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
31 |
<p class="text-base text-gray-800">
|
32 |
-
AI is an area of active research with known problems such as biased generation and
|
33 |
misinformation. Do not use this application for high-stakes decisions or advice.
|
34 |
</p>
|
35 |
{#if PUBLIC_APP_DATA_SHARING}
|
@@ -54,7 +59,6 @@
|
|
54 |
with <LogoHuggingFaceBorderless classNames="text-xl mr-1 ml-1.5" /> Hugging Face
|
55 |
{/if}
|
56 |
</button>
|
57 |
-
<p class="mt-2 px-2 text-sm text-gray-500">to start chatting right away</p>
|
58 |
{:else}
|
59 |
<input type="hidden" name="ethicsModalAccepted" value={true} />
|
60 |
{#each Object.entries(settings) as [key, val]}
|
|
|
25 |
v{PUBLIC_VERSION}
|
26 |
</div>
|
27 |
</h2>
|
28 |
+
{#if $page.data.requiresLogin}
|
29 |
+
<p
|
30 |
+
class="px-4 text-lg font-semibold leading-snug text-gray-800 sm:px-12"
|
31 |
+
style="text-wrap: balance;"
|
32 |
+
>
|
33 |
+
Please Sign in with Hugging Face to continue
|
34 |
+
</p>
|
35 |
+
{/if}
|
36 |
<p class="text-base text-gray-800">
|
37 |
+
Disclaimer: AI is an area of active research with known problems such as biased generation and
|
38 |
misinformation. Do not use this application for high-stakes decisions or advice.
|
39 |
</p>
|
40 |
{#if PUBLIC_APP_DATA_SHARING}
|
|
|
59 |
with <LogoHuggingFaceBorderless classNames="text-xl mr-1 ml-1.5" /> Hugging Face
|
60 |
{/if}
|
61 |
</button>
|
|
|
62 |
{:else}
|
63 |
<input type="hidden" name="ethicsModalAccepted" value={true} />
|
64 |
{#each Object.entries(settings) as [key, val]}
|
src/routes/+layout.server.ts
CHANGED
@@ -6,7 +6,7 @@ import { UrlDependency } from "$lib/types/UrlDependency";
|
|
6 |
import { defaultModel, models, oldModels, validateModel } from "$lib/server/models";
|
7 |
import { authCondition, requiresUser } from "$lib/server/auth";
|
8 |
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
9 |
-
import { SERPAPI_KEY, SERPER_API_KEY } from "$env/static/private";
|
10 |
|
11 |
export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
|
12 |
const { conversations } = collections;
|
@@ -82,5 +82,6 @@ export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
|
|
82 |
email: locals.user.email,
|
83 |
},
|
84 |
requiresLogin: requiresUser,
|
|
|
85 |
};
|
86 |
};
|
|
|
6 |
import { defaultModel, models, oldModels, validateModel } from "$lib/server/models";
|
7 |
import { authCondition, requiresUser } from "$lib/server/auth";
|
8 |
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
9 |
+
import { SERPAPI_KEY, SERPER_API_KEY, MESSAGES_BEFORE_LOGIN } from "$env/static/private";
|
10 |
|
11 |
export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
|
12 |
const { conversations } = collections;
|
|
|
82 |
email: locals.user.email,
|
83 |
},
|
84 |
requiresLogin: requiresUser,
|
85 |
+
messagesBeforeLogin: MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0,
|
86 |
};
|
87 |
};
|
src/routes/+layout.svelte
CHANGED
@@ -178,7 +178,7 @@
|
|
178 |
{#if isSettingsOpen}
|
179 |
<SettingsModal on:close={() => (isSettingsOpen = false)} settings={data.settings} />
|
180 |
{/if}
|
181 |
-
{#if requiresLogin}
|
182 |
<LoginModal settings={data.settings} />
|
183 |
{/if}
|
184 |
<slot />
|
|
|
178 |
{#if isSettingsOpen}
|
179 |
<SettingsModal on:close={() => (isSettingsOpen = false)} settings={data.settings} />
|
180 |
{/if}
|
181 |
+
{#if requiresLogin && data.messagesBeforeLogin === 0}
|
182 |
<LoginModal settings={data.settings} />
|
183 |
{/if}
|
184 |
<slot />
|
src/routes/conversation/[id]/+page.svelte
CHANGED
@@ -15,7 +15,7 @@
|
|
15 |
import { webSearchParameters } from "$lib/stores/webSearchParameters";
|
16 |
import type { WebSearchMessage } from "$lib/types/WebSearch";
|
17 |
import type { Message } from "$lib/types/Message";
|
18 |
-
import {
|
19 |
|
20 |
export let data;
|
21 |
|
@@ -33,6 +33,7 @@
|
|
33 |
|
34 |
let loading = false;
|
35 |
let pending = false;
|
|
|
36 |
|
37 |
async function getTextGenerationStream(
|
38 |
inputs: string,
|
@@ -195,7 +196,6 @@
|
|
195 |
await getTextGenerationStream(message, messageId, isRetry, searchResponseId ?? undefined);
|
196 |
|
197 |
webSearchMessages = [];
|
198 |
-
if (browser) invalidate(UrlDependency.Conversation);
|
199 |
|
200 |
if (messages.filter((m) => m.from === "user").length === 1) {
|
201 |
summarizeTitle($page.params.id)
|
@@ -259,6 +259,12 @@
|
|
259 |
});
|
260 |
$: $page.params.id, (isAborted = true);
|
261 |
$: title = data.conversations.find((conv) => conv.id === $page.params.id)?.title ?? data.title;
|
|
|
|
|
|
|
|
|
|
|
|
|
262 |
</script>
|
263 |
|
264 |
<svelte:head>
|
@@ -279,4 +285,5 @@
|
|
279 |
models={data.models}
|
280 |
currentModel={findCurrentModel([...data.models, ...data.oldModels], data.model)}
|
281 |
settings={data.settings}
|
|
|
282 |
/>
|
|
|
15 |
import { webSearchParameters } from "$lib/stores/webSearchParameters";
|
16 |
import type { WebSearchMessage } from "$lib/types/WebSearch";
|
17 |
import type { Message } from "$lib/types/Message";
|
18 |
+
import { PUBLIC_APP_DISCLAIMER } from "$env/static/public";
|
19 |
|
20 |
export let data;
|
21 |
|
|
|
33 |
|
34 |
let loading = false;
|
35 |
let pending = false;
|
36 |
+
let loginRequired = false;
|
37 |
|
38 |
async function getTextGenerationStream(
|
39 |
inputs: string,
|
|
|
196 |
await getTextGenerationStream(message, messageId, isRetry, searchResponseId ?? undefined);
|
197 |
|
198 |
webSearchMessages = [];
|
|
|
199 |
|
200 |
if (messages.filter((m) => m.from === "user").length === 1) {
|
201 |
summarizeTitle($page.params.id)
|
|
|
259 |
});
|
260 |
$: $page.params.id, (isAborted = true);
|
261 |
$: title = data.conversations.find((conv) => conv.id === $page.params.id)?.title ?? data.title;
|
262 |
+
|
263 |
+
$: loginRequired =
|
264 |
+
(data.requiresLogin
|
265 |
+
? !data.user
|
266 |
+
: !data.settings.ethicsModalAcceptedAt && !!PUBLIC_APP_DISCLAIMER) &&
|
267 |
+
messages.length >= data.messagesBeforeLogin;
|
268 |
</script>
|
269 |
|
270 |
<svelte:head>
|
|
|
285 |
models={data.models}
|
286 |
currentModel={findCurrentModel([...data.models, ...data.oldModels], data.model)}
|
287 |
settings={data.settings}
|
288 |
+
{loginRequired}
|
289 |
/>
|
src/routes/conversation/[id]/+server.ts
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
-
import { RATE_LIMIT } from "$env/static/private";
|
2 |
import { buildPrompt } from "$lib/buildPrompt";
|
3 |
import { PUBLIC_SEP_TOKEN } from "$lib/constants/publicSepToken";
|
4 |
import { abortedGenerations } from "$lib/server/abortedGenerations";
|
5 |
-
import { authCondition } from "$lib/server/auth";
|
6 |
import { collections } from "$lib/server/database";
|
7 |
import { modelEndpoint } from "$lib/server/modelEndpoint";
|
8 |
import { models } from "$lib/server/models";
|
@@ -37,6 +37,14 @@ export async function POST({ request, fetch, locals, params, getClientAddress })
|
|
37 |
throw error(404, "Conversation not found");
|
38 |
}
|
39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
if (RATE_LIMIT !== "") {
|
41 |
let nEvents = 0;
|
42 |
if (locals.user?._id) {
|
|
|
1 |
+
import { MESSAGES_BEFORE_LOGIN, RATE_LIMIT } from "$env/static/private";
|
2 |
import { buildPrompt } from "$lib/buildPrompt";
|
3 |
import { PUBLIC_SEP_TOKEN } from "$lib/constants/publicSepToken";
|
4 |
import { abortedGenerations } from "$lib/server/abortedGenerations";
|
5 |
+
import { authCondition, requiresUser } from "$lib/server/auth";
|
6 |
import { collections } from "$lib/server/database";
|
7 |
import { modelEndpoint } from "$lib/server/modelEndpoint";
|
8 |
import { models } from "$lib/server/models";
|
|
|
37 |
throw error(404, "Conversation not found");
|
38 |
}
|
39 |
|
40 |
+
if (
|
41 |
+
!locals.user?._id &&
|
42 |
+
requiresUser &&
|
43 |
+
conv.messages.length > (MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0)
|
44 |
+
) {
|
45 |
+
throw error(429, "Exceeded number of messages before login");
|
46 |
+
}
|
47 |
+
|
48 |
if (RATE_LIMIT !== "") {
|
49 |
let nEvents = 0;
|
50 |
if (locals.user?._id) {
|