Skip to content

Commit 04a893f

Browse files
authored
Merge branch 'main' into feat/custom_instruction
2 parents f86b724 + 7f7375d commit 04a893f

File tree

11 files changed

+109
-31
lines changed

11 files changed

+109
-31
lines changed

.env

+5-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ ADMIN_API_SECRET=# secret to admin API calls, like computing usage stats or expo
113113

114114
PARQUET_EXPORT_SECRET=#DEPRECATED, use ADMIN_API_SECRET instead
115115

116-
RATE_LIMIT= # requests per minute
116+
RATE_LIMIT= # /!\ Legacy definition of messages per minute. Use USAGE_LIMITS.messagesPerMinute instead
117117
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
118118

119119
APP_BASE="" # base path of the app, e.g. /chat, left blank as default
@@ -140,4 +140,7 @@ ALTERNATIVE_REDIRECT_URLS=`[]` #valide alternative redirect URL for OAuth
140140

141141
WEBHOOK_URL_REPORT_ASSISTANT=#provide webhook url to get notified when an assistant gets reported
142142

143-
ALLOWED_USER_EMAILS=`[]` # if it's defined, only these emails will be allowed to use the app
143+
ALLOWED_USER_EMAILS=`[]` # if it's defined, only these emails will be allowed to use the app
144+
145+
USAGE_LIMITS=`{}`
146+

.env.template

-3
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,6 @@ PUBLIC_APP_DISCLAIMER_MESSAGE="Disclaimer: AI is an area of active research with
269269
PUBLIC_APP_DATA_SHARING=1
270270
PUBLIC_APP_DISCLAIMER=1
271271

272-
RATE_LIMIT=16
273-
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
274-
275272
PUBLIC_GOOGLE_ANALYTICS_ID=G-8Q63TH4CSL
276273
PUBLIC_PLAUSIBLE_SCRIPT_URL="/js/script.js"
277274

.github/workflows/deploy-release.yml

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ jobs:
2727
HF_DEPLOYMENT_TOKEN: ${{ secrets.HF_DEPLOYMENT_TOKEN }}
2828
WEBHOOK_URL_REPORT_ASSISTANT: ${{ secrets.WEBHOOK_URL_REPORT_ASSISTANT }}
2929
ADMIN_API_SECRET: ${{ secrets.ADMIN_API_SECRET }}
30+
USAGE_LIMITS: ${{ secrets.USAGE_LIMITS }}
31+
MESSAGES_BEFORE_LOGIN: ${{ secrets.MESSAGES_BEFORE_LOGIN }}
3032
run: npm run updateProdEnv
3133
sync-to-hub:
3234
runs-on: ubuntu-latest

scripts/updateProdEnv.ts

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const MONGODB_URL = process.env.MONGODB_URL;
88
const HF_TOKEN = process.env.HF_TOKEN ?? process.env.HF_ACCESS_TOKEN; // token used for API requests in prod
99
const WEBHOOK_URL_REPORT_ASSISTANT = process.env.WEBHOOK_URL_REPORT_ASSISTANT; // slack webhook url used to get "report assistant" events
1010
const ADMIN_API_SECRET = process.env.ADMIN_API_SECRET;
11+
const USAGE_LIMITS = process.env.USAGE_LIMITS;
12+
const MESSAGES_BEFORE_LOGIN = process.env.MESSAGES_BEFORE_LOGIN;
1113

1214
// Read the content of the file .env.template
1315
const PUBLIC_CONFIG = fs.readFileSync(".env.template", "utf8");
@@ -20,6 +22,8 @@ SERPER_API_KEY=${SERPER_API_KEY}
2022
HF_TOKEN=${HF_TOKEN}
2123
WEBHOOK_URL_REPORT_ASSISTANT=${WEBHOOK_URL_REPORT_ASSISTANT}
2224
ADMIN_API_SECRET=${ADMIN_API_SECRET}
25+
USAGE_LIMITS=${USAGE_LIMITS}
26+
MESSAGES_BEFORE_LOGIN=${MESSAGES_BEFORE_LOGIN}
2327
`;
2428

2529
// Make an HTTP POST request to add the space secrets

src/lib/server/usageLimits.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { z } from "zod";
2+
import { USAGE_LIMITS, RATE_LIMIT } from "$env/static/private";
3+
import JSON5 from "json5";
4+
5+
// RATE_LIMIT is the legacy way to define messages per minute limit
6+
export const usageLimitsSchema = z
7+
.object({
8+
conversations: z.coerce.number().optional(), // how many conversations
9+
messages: z.coerce.number().optional(), // how many messages in a conversation
10+
assistants: z.coerce.number().optional(), // how many assistants
11+
messageLength: z.coerce.number().optional(), // how long can a message be before we cut it off
12+
messagesPerMinute: z
13+
.preprocess((val) => {
14+
if (val === undefined) {
15+
return RATE_LIMIT;
16+
}
17+
return val;
18+
}, z.coerce.number().optional())
19+
.optional(), // how many messages per minute
20+
})
21+
.optional();
22+
23+
export const usageLimits = usageLimitsSchema.parse(JSON5.parse(USAGE_LIMITS));

src/routes/+page.svelte

+4-3
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@
5050
});
5151
5252
if (!res.ok) {
53-
error.set("Error while creating conversation, try again.");
54-
console.error("Error while creating conversation: " + (await res.text()));
53+
const errorMessage = (await res.json()).message || ERROR_MESSAGES.default;
54+
error.set(errorMessage);
55+
console.error("Error while creating conversation: ", errorMessage);
5556
return;
5657
}
5758
@@ -66,7 +67,7 @@
6667
// invalidateAll to update list of conversations
6768
await goto(`${base}/conversation/${conversationId}`, { invalidateAll: true });
6869
} catch (err) {
69-
error.set(ERROR_MESSAGES.default);
70+
error.set((err as Error).message || ERROR_MESSAGES.default);
7071
console.error(err);
7172
} finally {
7273
loading = false;

src/routes/assistants/+page.svelte

+19-13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import { PUBLIC_APP_ASSETS, PUBLIC_ORIGIN } from "$env/static/public";
55
import { isHuggingChat } from "$lib/utils/isHuggingChat";
66
7-
import { tick } from "svelte";
87
import { goto } from "$app/navigation";
98
import { base } from "$app/paths";
109
import { page } from "$app/stores";
@@ -29,7 +28,8 @@
2928
3029
const SEARCH_DEBOUNCE_DELAY = 400;
3130
let filterInputEl: HTMLInputElement;
32-
let searchDisabled = false;
31+
let filterValue = data.query;
32+
let isFilterInPorgress = false;
3333
3434
const onModelChange = (e: Event) => {
3535
const newUrl = getHref($page.url, {
@@ -39,19 +39,26 @@
3939
goto(newUrl);
4040
};
4141
42-
const filterOnName = debounce(async (e: Event) => {
43-
searchDisabled = true;
44-
const value = (e.target as HTMLInputElement).value;
42+
const filterOnName = debounce(async (value: string) => {
43+
filterValue = value;
44+
45+
if (isFilterInPorgress) {
46+
return;
47+
}
48+
49+
isFilterInPorgress = true;
4550
const newUrl = getHref($page.url, {
4651
newKeys: { q: value },
4752
existingKeys: { behaviour: "delete", keys: ["p"] },
4853
});
4954
await goto(newUrl);
50-
setTimeout(async () => {
51-
searchDisabled = false;
52-
await tick();
53-
filterInputEl.focus();
54-
}, 0);
55+
setTimeout(() => filterInputEl.focus(), 0);
56+
isFilterInPorgress = false;
57+
58+
// there was a new filter query before server returned response
59+
if (filterValue !== value) {
60+
filterOnName(filterValue);
61+
}
5562
}, SEARCH_DEBOUNCE_DELAY);
5663
5764
const settings = useSettingsStore();
@@ -171,12 +178,11 @@
171178
<input
172179
class="h-[30px] w-full bg-transparent pl-5 focus:outline-none"
173180
placeholder="Filter by name"
174-
value={data.query}
175-
on:input={filterOnName}
181+
value={filterValue}
182+
on:input={(e) => filterOnName(e.currentTarget.value)}
176183
bind:this={filterInputEl}
177184
maxlength="150"
178185
type="search"
179-
disabled={searchDisabled}
180186
/>
181187
</div>
182188
</div>

src/routes/conversation/+server.ts

+11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import type { Message } from "$lib/types/Message";
88
import { models, validateModel } from "$lib/server/models";
99
import { defaultEmbeddingModel } from "$lib/server/embeddingModels";
1010
import { v4 } from "uuid";
11+
import { authCondition } from "$lib/server/auth";
12+
import { usageLimits } from "$lib/server/usageLimits";
1113

1214
export const POST: RequestHandler = async ({ locals, request }) => {
1315
const body = await request.text();
@@ -23,6 +25,15 @@ export const POST: RequestHandler = async ({ locals, request }) => {
2325
})
2426
.parse(JSON.parse(body));
2527

28+
const convCount = await collections.conversations.countDocuments(authCondition(locals));
29+
30+
if (usageLimits?.conversations && convCount > usageLimits?.conversations) {
31+
throw error(
32+
429,
33+
"You have reached the maximum number of conversations. Delete some to continue."
34+
);
35+
}
36+
2637
let messages: Message[] = [
2738
{
2839
id: v4(),

src/routes/conversation/[id]/+page.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
});
4444
4545
if (!res.ok) {
46-
error.set("Error while creating conversation, try again.");
46+
error.set(await res.text());
4747
console.error("Error while creating conversation: " + (await res.text()));
4848
return;
4949
}

src/routes/conversation/[id]/+server.ts

+27-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MESSAGES_BEFORE_LOGIN, RATE_LIMIT } from "$env/static/private";
1+
import { MESSAGES_BEFORE_LOGIN } from "$env/static/private";
22
import { authCondition, requiresUser } from "$lib/server/auth";
33
import { collections } from "$lib/server/database";
44
import { models } from "$lib/server/models";
@@ -19,6 +19,7 @@ import { buildSubtree } from "$lib/utils/tree/buildSubtree.js";
1919
import { addChildren } from "$lib/utils/tree/addChildren.js";
2020
import { addSibling } from "$lib/utils/tree/addSibling.js";
2121
import { preprocessMessages } from "$lib/server/preprocessMessages.js";
22+
import { usageLimits } from "$lib/server/usageLimits";
2223

2324
export async function POST({ request, locals, params, getClientAddress }) {
2425
const id = z.string().parse(params.id);
@@ -95,14 +96,22 @@ export async function POST({ request, locals, params, getClientAddress }) {
9596
}
9697
}
9798

98-
// check if the user is rate limited
99-
const nEvents = Math.max(
100-
await collections.messageEvents.countDocuments({ userId }),
101-
await collections.messageEvents.countDocuments({ ip: getClientAddress() })
102-
);
99+
if (usageLimits?.messagesPerMinute) {
100+
// check if the user is rate limited
101+
const nEvents = Math.max(
102+
await collections.messageEvents.countDocuments({ userId }),
103+
await collections.messageEvents.countDocuments({ ip: getClientAddress() })
104+
);
105+
if (nEvents > usageLimits.messagesPerMinute) {
106+
throw error(429, ERROR_MESSAGES.rateLimited);
107+
}
108+
}
103109

104-
if (RATE_LIMIT != "" && nEvents > parseInt(RATE_LIMIT)) {
105-
throw error(429, ERROR_MESSAGES.rateLimited);
110+
if (usageLimits?.messages && conv.messages.length > usageLimits.messages) {
111+
throw error(
112+
429,
113+
`This conversation has more than ${usageLimits.messages} messages. Start a new one to continue`
114+
);
106115
}
107116

108117
// fetch the model
@@ -125,14 +134,23 @@ export async function POST({ request, locals, params, getClientAddress }) {
125134
} = z
126135
.object({
127136
id: z.string().uuid().refine(isMessageId).optional(), // parent message id to append to for a normal message, or the message id for a retry/continue
128-
inputs: z.optional(z.string().trim().min(1)),
137+
inputs: z.optional(
138+
z
139+
.string()
140+
.trim()
141+
.min(1)
142+
.transform((s) => s.replace(/\r\n/g, "\n"))
143+
),
129144
is_retry: z.optional(z.boolean()),
130145
is_continue: z.optional(z.boolean()),
131146
web_search: z.optional(z.boolean()),
132147
files: z.optional(z.array(z.string())),
133148
})
134149
.parse(json);
135150

151+
if (usageLimits?.messageLength && (newPrompt?.length ?? 0) > usageLimits.messageLength) {
152+
throw error(400, "Message too long.");
153+
}
136154
// files is an array of base64 strings encoding Blob objects
137155
// we need to convert this array to an array of File objects
138156

src/routes/settings/assistants/new/+page.server.ts

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ObjectId } from "mongodb";
77
import { z } from "zod";
88
import { sha256 } from "$lib/utils/sha256";
99
import sharp from "sharp";
10+
import { usageLimits } from "$lib/server/usageLimits";
1011
import { generateSearchTokens } from "$lib/utils/searchTokens";
1112

1213
const newAsssistantSchema = z.object({
@@ -62,6 +63,18 @@ export const actions: Actions = {
6263
return fail(400, { error: true, errors });
6364
}
6465

66+
const assistantsCount = await collections.assistants.countDocuments(authCondition(locals));
67+
68+
if (usageLimits?.assistants && assistantsCount > usageLimits.assistants) {
69+
const errors = [
70+
{
71+
field: "preprompt",
72+
message: "You have reached the maximum number of assistants. Delete some to continue.",
73+
},
74+
];
75+
return fail(400, { error: true, errors });
76+
}
77+
6578
const createdById = locals.user?._id ?? locals.sessionId;
6679

6780
const newAssistantId = new ObjectId();

0 commit comments

Comments
 (0)