nsarrazin HF Staff commited on
Commit
06feee8
·
unverified ·
1 Parent(s): 75663fd

Major bump to sveltekit & node-adapter versions (#1395)

Browse files

* version bump

* sveltekit2 bump

* formatting

* fix imports

* fix deps

* minor bump on chat-ui itself

* better error logging

* bump svelte-check version

Files changed (47) hide show
  1. package-lock.json +0 -0
  2. package.json +11 -9
  3. src/hooks.server.ts +3 -1
  4. src/lib/components/ExpandNavigation.svelte +1 -1
  5. src/lib/components/MobileNav.svelte +1 -1
  6. src/lib/components/NavConversationItem.svelte +1 -1
  7. src/lib/components/NavMenu.svelte +3 -3
  8. src/lib/components/Pagination.svelte +1 -1
  9. src/lib/components/chat/AssistantIntroduction.svelte +3 -3
  10. src/lib/components/chat/ChatIntroduction.svelte +1 -1
  11. src/lib/components/chat/ChatMessage.svelte +10 -10
  12. src/lib/components/chat/ChatWindow.svelte +4 -4
  13. src/lib/server/auth.ts +7 -1
  14. src/lib/server/endpoints/anthropic/utils.ts +1 -1
  15. src/lib/server/endpoints/cohere/endpointCohere.ts +2 -2
  16. src/lib/server/exitHandler.ts +1 -1
  17. src/lib/server/files/downloadFile.ts +2 -2
  18. src/routes/+layout.svelte +3 -3
  19. src/routes/admin/export/+server.ts +1 -1
  20. src/routes/assistant/[assistantId]/+page.server.ts +2 -2
  21. src/routes/assistant/[assistantId]/thumbnail.png/+server.ts +1 -1
  22. src/routes/assistants/+page.server.ts +2 -2
  23. src/routes/assistants/+page.svelte +4 -4
  24. src/routes/conversation/+server.ts +6 -9
  25. src/routes/conversation/[id]/+page.server.ts +5 -5
  26. src/routes/conversation/[id]/+server.ts +16 -16
  27. src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts +3 -3
  28. src/routes/conversation/[id]/message/[messageId]/vote/+server.ts +1 -1
  29. src/routes/conversation/[id]/output/[sha256]/+server.ts +3 -3
  30. src/routes/conversation/[id]/share/+server.ts +1 -1
  31. src/routes/conversation/[id]/stop-generating/+server.ts +1 -1
  32. src/routes/conversations/+page.server.ts +1 -1
  33. src/routes/login/+page.server.ts +1 -1
  34. src/routes/login/callback/+page.server.ts +6 -6
  35. src/routes/login/callback/updateUser.ts +1 -1
  36. src/routes/logout/+page.server.ts +1 -1
  37. src/routes/models/[...model]/+page.server.ts +1 -1
  38. src/routes/models/[...model]/thumbnail.png/+server.ts +1 -1
  39. src/routes/r/[id]/+page.ts +1 -1
  40. src/routes/settings/(nav)/+page.svelte +1 -1
  41. src/routes/settings/(nav)/[...model]/+page.ts +1 -1
  42. src/routes/settings/(nav)/assistants/[assistantId]/+page.server.ts +2 -2
  43. src/routes/settings/(nav)/assistants/[assistantId]/+page.ts +1 -1
  44. src/routes/settings/(nav)/assistants/[assistantId]/avatar.jpg/+server.ts +3 -3
  45. src/routes/settings/(nav)/assistants/[assistantId]/edit/+page.server.ts +1 -1
  46. src/routes/settings/(nav)/assistants/new/+page.server.ts +1 -1
  47. svelte.config.js +1 -1
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "chat-ui",
3
- "version": "0.9.1",
4
  "private": true,
5
  "packageManager": "[email protected]",
6
  "scripts": {
@@ -20,14 +20,15 @@
20
  "@faker-js/faker": "^8.4.1",
21
  "@iconify-json/carbon": "^1.1.16",
22
  "@iconify-json/eos-icons": "^1.1.6",
23
- "@sveltejs/adapter-node": "^1.3.1",
24
- "@sveltejs/kit": "^1.30.4",
25
  "@tailwindcss/typography": "^0.5.9",
26
  "@types/dompurify": "^3.0.5",
27
  "@types/express": "^4.17.21",
28
  "@types/js-yaml": "^4.0.9",
29
  "@types/jsdom": "^21.1.1",
30
  "@types/minimist": "^1.2.5",
 
31
  "@types/parquetjs": "^0.10.3",
32
  "@types/sbd": "^1.0.5",
33
  "@types/uuid": "^9.0.8",
@@ -44,13 +45,13 @@
44
  "prettier-plugin-svelte": "^2.10.1",
45
  "prettier-plugin-tailwindcss": "^0.2.7",
46
  "prom-client": "^15.1.2",
47
- "svelte": "^4.2.8",
48
- "svelte-check": "^3.6.2",
49
  "ts-node": "^10.9.1",
50
  "tslib": "^2.4.1",
51
  "typescript": "^5.0.0",
52
  "unplugin-icons": "^0.16.1",
53
- "vite": "^4.5.3",
54
  "vite-node": "^1.3.1",
55
  "vitest": "^0.31.0"
56
  },
@@ -72,6 +73,7 @@
72
  "dotenv": "^16.0.3",
73
  "express": "^4.19.2",
74
  "file-type": "^19.0.0",
 
75
  "handlebars": "^4.7.8",
76
  "highlight.js": "^11.7.0",
77
  "husky": "^9.0.11",
@@ -99,12 +101,12 @@
99
  "sharp": "^0.33.4",
100
  "tailwind-scrollbar": "^3.0.0",
101
  "tailwindcss": "^3.4.0",
102
- "uuid": "^9.0.1",
103
  "zod": "^3.22.3"
104
  },
105
  "optionalDependencies": {
106
- "@anthropic-ai/sdk": "^0.17.1",
107
- "@anthropic-ai/vertex-sdk": "^0.3.0",
108
  "@google-cloud/vertexai": "^1.1.0",
109
  "@google/generative-ai": "^0.14.1",
110
  "aws4fetch": "^1.0.17",
 
1
  {
2
  "name": "chat-ui",
3
+ "version": "0.9.2",
4
  "private": true,
5
  "packageManager": "[email protected]",
6
  "scripts": {
 
20
  "@faker-js/faker": "^8.4.1",
21
  "@iconify-json/carbon": "^1.1.16",
22
  "@iconify-json/eos-icons": "^1.1.6",
23
+ "@sveltejs/adapter-node": "^5.2.0",
24
+ "@sveltejs/kit": "^2.5.20",
25
  "@tailwindcss/typography": "^0.5.9",
26
  "@types/dompurify": "^3.0.5",
27
  "@types/express": "^4.17.21",
28
  "@types/js-yaml": "^4.0.9",
29
  "@types/jsdom": "^21.1.1",
30
  "@types/minimist": "^1.2.5",
31
+ "@types/node": "^22.1.0",
32
  "@types/parquetjs": "^0.10.3",
33
  "@types/sbd": "^1.0.5",
34
  "@types/uuid": "^9.0.8",
 
45
  "prettier-plugin-svelte": "^2.10.1",
46
  "prettier-plugin-tailwindcss": "^0.2.7",
47
  "prom-client": "^15.1.2",
48
+ "svelte": "^4.2.18",
49
+ "svelte-check": "^3.8.5",
50
  "ts-node": "^10.9.1",
51
  "tslib": "^2.4.1",
52
  "typescript": "^5.0.0",
53
  "unplugin-icons": "^0.16.1",
54
+ "vite": "^5.3.5",
55
  "vite-node": "^1.3.1",
56
  "vitest": "^0.31.0"
57
  },
 
73
  "dotenv": "^16.0.3",
74
  "express": "^4.19.2",
75
  "file-type": "^19.0.0",
76
+ "google-auth-library": "^9.13.0",
77
  "handlebars": "^4.7.8",
78
  "highlight.js": "^11.7.0",
79
  "husky": "^9.0.11",
 
101
  "sharp": "^0.33.4",
102
  "tailwind-scrollbar": "^3.0.0",
103
  "tailwindcss": "^3.4.0",
104
+ "uuid": "^10.0.0",
105
  "zod": "^3.22.3"
106
  },
107
  "optionalDependencies": {
108
+ "@anthropic-ai/sdk": "^0.25.0",
109
+ "@anthropic-ai/vertex-sdk": "^0.4.1",
110
  "@google-cloud/vertexai": "^1.1.0",
111
  "@google/generative-ai": "^0.14.1",
112
  "aws4fetch": "^1.0.17",
src/hooks.server.ts CHANGED
@@ -35,7 +35,7 @@ if (!building) {
35
  AbortedGenerations.getInstance();
36
  }
37
 
38
- export const handleError: HandleServerError = async ({ error, event }) => {
39
  // handle 404
40
 
41
  if (building) {
@@ -55,8 +55,10 @@ export const handleError: HandleServerError = async ({ error, event }) => {
55
  url: event.request.url,
56
  params: event.params,
57
  request: event.request,
 
58
  error,
59
  errorId,
 
60
  });
61
 
62
  return {
 
35
  AbortedGenerations.getInstance();
36
  }
37
 
38
+ export const handleError: HandleServerError = async ({ error, event, status, message }) => {
39
  // handle 404
40
 
41
  if (building) {
 
55
  url: event.request.url,
56
  params: event.params,
57
  request: event.request,
58
+ message,
59
  error,
60
  errorId,
61
+ status,
62
  });
63
 
64
  return {
src/lib/components/ExpandNavigation.svelte CHANGED
@@ -5,7 +5,7 @@
5
 
6
  <button
7
  on:click
8
- class="{classNames} group flex h-16 w-6 flex-col items-center justify-center -space-y-1 outline-none *:h-3 *:w-1 *:rounded-full *:hover:bg-gray-300 max-md:hidden dark:*:hover:bg-gray-600 {!isCollapsed
9
  ? '*:bg-gray-200/70 dark:*:bg-gray-800'
10
  : '*:bg-gray-200 dark:*:bg-gray-700'}"
11
  >
 
5
 
6
  <button
7
  on:click
8
+ class="{classNames} group flex h-16 w-6 flex-col items-center justify-center -space-y-1 outline-none *:h-3 *:w-1 *:rounded-full *:hover:bg-gray-300 dark:*:hover:bg-gray-600 max-md:hidden {!isCollapsed
9
  ? '*:bg-gray-200/70 dark:*:bg-gray-800'
10
  : '*:bg-gray-200 dark:*:bg-gray-700'}"
11
  >
src/lib/components/MobileNav.svelte CHANGED
@@ -31,7 +31,7 @@
31
  </script>
32
 
33
  <nav
34
- class="flex h-12 items-center justify-between border-b bg-gray-50 px-3 md:hidden dark:border-gray-800 dark:bg-gray-800/70"
35
  >
36
  <button
37
  type="button"
 
31
  </script>
32
 
33
  <nav
34
+ class="flex h-12 items-center justify-between border-b bg-gray-50 px-3 dark:border-gray-800 dark:bg-gray-800/70 md:hidden"
35
  >
36
  <button
37
  type="button"
src/lib/components/NavConversationItem.svelte CHANGED
@@ -25,7 +25,7 @@
25
  confirmDelete = false;
26
  }}
27
  href="{base}/conversation/{conv.id}"
28
- class="group flex h-10 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-600 hover:bg-gray-100 sm:h-[2.35rem] dark:text-gray-300 dark:hover:bg-gray-700 {conv.id ===
29
  $page.params.id
30
  ? 'bg-gray-100 dark:bg-gray-700'
31
  : ''}"
 
25
  confirmDelete = false;
26
  }}
27
  href="{base}/conversation/{conv.id}"
28
+ class="group flex h-10 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 sm:h-[2.35rem] {conv.id ===
29
  $page.params.id
30
  ? 'bg-gray-100 dark:bg-gray-700'
31
  : ''}"
src/lib/components/NavMenu.svelte CHANGED
@@ -57,13 +57,13 @@
57
  <a
58
  href={`${base}/`}
59
  on:click={handleNewChatClick}
60
- class="flex rounded-lg border bg-white px-2 py-0.5 text-center shadow-sm hover:shadow-none sm:text-smd dark:border-gray-600 dark:bg-gray-700"
61
  >
62
  New Chat
63
  </a>
64
  </div>
65
  <div
66
- class="scrollbar-custom flex flex-col gap-1 overflow-y-auto rounded-r-xl from-gray-50 px-3 pb-3 pt-2 text-[.9rem] max-sm:bg-gradient-to-t md:bg-gradient-to-l dark:from-gray-800/30"
67
  >
68
  {#each Object.entries(groupedConversations) as [group, convs]}
69
  {#if convs.length}
@@ -92,7 +92,7 @@
92
  {#if !user.logoutDisabled}
93
  <button
94
  type="submit"
95
- class="ml-auto h-6 flex-none items-center gap-1.5 rounded-md border bg-white px-2 text-gray-700 shadow-sm group-hover:flex hover:shadow-none md:hidden dark:border-gray-600 dark:bg-gray-600 dark:text-gray-400 dark:hover:text-gray-300"
96
  >
97
  Sign Out
98
  </button>
 
57
  <a
58
  href={`${base}/`}
59
  on:click={handleNewChatClick}
60
+ class="flex rounded-lg border bg-white px-2 py-0.5 text-center shadow-sm hover:shadow-none dark:border-gray-600 dark:bg-gray-700 sm:text-smd"
61
  >
62
  New Chat
63
  </a>
64
  </div>
65
  <div
66
+ class="scrollbar-custom flex flex-col gap-1 overflow-y-auto rounded-r-xl from-gray-50 px-3 pb-3 pt-2 text-[.9rem] dark:from-gray-800/30 max-sm:bg-gradient-to-t md:bg-gradient-to-l"
67
  >
68
  {#each Object.entries(groupedConversations) as [group, convs]}
69
  {#if convs.length}
 
92
  {#if !user.logoutDisabled}
93
  <button
94
  type="submit"
95
+ class="ml-auto h-6 flex-none items-center gap-1.5 rounded-md border bg-white px-2 text-gray-700 shadow-sm group-hover:flex hover:shadow-none dark:border-gray-600 dark:bg-gray-600 dark:text-gray-400 dark:hover:text-gray-300 md:hidden"
96
  >
97
  Sign Out
98
  </button>
src/lib/components/Pagination.svelte CHANGED
@@ -57,7 +57,7 @@
57
  {#if numTotalPages > 1}
58
  <nav>
59
  <ul
60
- class="flex select-none items-center justify-between space-x-2 text-gray-700 sm:justify-center dark:text-gray-300 {classNames}"
61
  >
62
  <li>
63
  <PaginationArrow
 
57
  {#if numTotalPages > 1}
58
  <nav>
59
  <ul
60
+ class="flex select-none items-center justify-between space-x-2 text-gray-700 dark:text-gray-300 sm:justify-center {classNames}"
61
  >
62
  <li>
63
  <PaginationArrow
src/lib/components/chat/AssistantIntroduction.svelte CHANGED
@@ -67,7 +67,7 @@
67
  />
68
  {:else}
69
  <div
70
- class="flex size-12 flex-none items-center justify-center rounded-full bg-gray-300 object-cover text-xl font-bold uppercase text-gray-500 max-sm:self-start sm:text-4xl md:size-32 dark:bg-gray-600"
71
  >
72
  {assistant?.name[0]}
73
  </div>
@@ -116,7 +116,7 @@
116
  <div class="absolute right-3 top-3 md:right-4 md:top-4">
117
  <div class="flex flex-row items-center gap-1">
118
  <button
119
- class="flex h-7 items-center gap-1.5 rounded-full border bg-white px-2.5 py-1 text-gray-800 shadow-sm hover:shadow-inner max-sm:px-1.5 md:text-sm dark:border-gray-700 dark:bg-gray-700 dark:text-gray-300/90 dark:hover:bg-gray-800"
120
  on:click={() => {
121
  if (!isCopied) {
122
  share(shareUrl, assistant.name);
@@ -137,7 +137,7 @@
137
  </button>
138
  <a
139
  href="{base}/settings/assistants/{assistant._id.toString()}"
140
- class="flex h-7 items-center gap-1.5 rounded-full border bg-white px-2.5 py-1 text-gray-800 shadow-sm hover:shadow-inner md:text-sm dark:border-gray-700 dark:bg-gray-700 dark:text-gray-300/90 dark:hover:bg-gray-800"
141
  ><IconGear class="text-xxs" />Settings</a
142
  >
143
  </div>
 
67
  />
68
  {:else}
69
  <div
70
+ class="flex size-12 flex-none items-center justify-center rounded-full bg-gray-300 object-cover text-xl font-bold uppercase text-gray-500 dark:bg-gray-600 max-sm:self-start sm:text-4xl md:size-32"
71
  >
72
  {assistant?.name[0]}
73
  </div>
 
116
  <div class="absolute right-3 top-3 md:right-4 md:top-4">
117
  <div class="flex flex-row items-center gap-1">
118
  <button
119
+ class="flex h-7 items-center gap-1.5 rounded-full border bg-white px-2.5 py-1 text-gray-800 shadow-sm hover:shadow-inner dark:border-gray-700 dark:bg-gray-700 dark:text-gray-300/90 dark:hover:bg-gray-800 max-sm:px-1.5 md:text-sm"
120
  on:click={() => {
121
  if (!isCopied) {
122
  share(shareUrl, assistant.name);
 
137
  </button>
138
  <a
139
  href="{base}/settings/assistants/{assistant._id.toString()}"
140
+ class="flex h-7 items-center gap-1.5 rounded-full border bg-white px-2.5 py-1 text-gray-800 shadow-sm hover:shadow-inner dark:border-gray-700 dark:bg-gray-700 dark:text-gray-300/90 dark:hover:bg-gray-800 md:text-sm"
141
  ><IconGear class="text-xxs" />Settings</a
142
  >
143
  </div>
src/lib/components/chat/ChatIntroduction.svelte CHANGED
@@ -86,7 +86,7 @@
86
  {#each currentModelMetadata.promptExamples as example}
87
  <button
88
  type="button"
89
- class="rounded-xl border bg-gray-50 p-3 text-gray-600 hover:bg-gray-100 max-xl:text-sm xl:p-3.5 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
90
  on:click={() => dispatch("message", example.prompt)}
91
  >
92
  {example.title}
 
86
  {#each currentModelMetadata.promptExamples as example}
87
  <button
88
  type="button"
89
+ class="rounded-xl border bg-gray-50 p-3 text-gray-600 hover:bg-gray-100 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 max-xl:text-sm xl:p-3.5"
90
  on:click={() => dispatch("message", example.prompt)}
91
  >
92
  {example.title}
src/lib/components/chat/ChatMessage.svelte CHANGED
@@ -295,7 +295,7 @@
295
  {/if}
296
 
297
  <div
298
- class="prose max-w-none max-sm:prose-sm dark:prose-invert prose-headings:font-semibold prose-h1:text-lg prose-h2:text-base prose-h3:text-base prose-pre:bg-gray-800 dark:prose-pre:bg-gray-900"
299
  bind:this={contentEl}
300
  >
301
  {#if isLast && loading && $settings.disableStream}
@@ -343,7 +343,7 @@
343
  >
344
  {#if isAuthor}
345
  <button
346
- class="btn rounded-sm p-1 text-sm text-gray-400 focus:ring-0 hover:text-gray-500 dark:text-gray-400 dark:hover:text-gray-300
347
  {message.score && message.score > 0
348
  ? 'text-green-500 hover:text-green-500 dark:text-green-400 hover:dark:text-green-400'
349
  : ''}"
@@ -355,7 +355,7 @@
355
  <CarbonThumbsUp class="h-[1.14em] w-[1.14em]" />
356
  </button>
357
  <button
358
- class="btn rounded-sm p-1 text-sm text-gray-400 focus:ring-0 hover:text-gray-500 dark:text-gray-400 dark:hover:text-gray-300
359
  {message.score && message.score < 0
360
  ? 'text-red-500 hover:text-red-500 dark:text-red-400 hover:dark:text-red-400'
361
  : ''}"
@@ -368,7 +368,7 @@
368
  </button>
369
  {/if}
370
  <button
371
- class="btn rounded-sm p-1 text-sm text-gray-400 focus:ring-0 hover:text-gray-500 dark:text-gray-400 dark:hover:text-gray-300"
372
  title="Retry"
373
  type="button"
374
  on:click={() => dispatch("retry", { id: message.id })}
@@ -439,7 +439,7 @@
439
  class="btn rounded-lg px-3 py-1.5 text-sm
440
  {loading
441
  ? 'bg-gray-300 text-gray-400 dark:bg-gray-700 dark:text-gray-600'
442
- : 'bg-gray-200 text-gray-600 focus:ring-0 hover:text-gray-800 dark:bg-gray-800 dark:text-gray-300 dark:hover:text-gray-200'}
443
  "
444
  disabled={loading}
445
  >
@@ -447,7 +447,7 @@
447
  </button>
448
  <button
449
  type="button"
450
- class="btn rounded-sm p-2 text-sm text-gray-400 focus:ring-0 hover:text-gray-500 dark:text-gray-400 dark:hover:text-gray-300"
451
  on:click={() => {
452
  $convTreeStore.editing = null;
453
  }}
@@ -469,7 +469,7 @@
469
  <div class="mx-auto flex flex-row flex-nowrap gap-2">
470
  {#if downloadLink}
471
  <a
472
- class="rounded-lg border border-gray-100 bg-gray-100 p-1 text-xs text-gray-400 group-hover:block hover:text-gray-500 max-sm:!hidden md:hidden dark:border-gray-800 dark:bg-gray-800 dark:text-gray-400 dark:hover:text-gray-300"
473
  title="Download prompt and parameters"
474
  type="button"
475
  target="_blank"
@@ -480,7 +480,7 @@
480
  {/if}
481
  {#if !readOnly}
482
  <button
483
- class="cursor-pointer rounded-lg border border-gray-100 bg-gray-100 p-1 text-xs text-gray-400 group-hover:block hover:text-gray-500 md:hidden lg:-right-2 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-400 dark:hover:text-gray-300"
484
  title="Branch"
485
  type="button"
486
  on:click={() => ($convTreeStore.editing = message.id)}
@@ -515,7 +515,7 @@
515
  class="font-white group/navbranch z-10 -mt-1 ml-3.5 mr-auto flex h-6 w-fit select-none flex-row items-center justify-center gap-1 text-sm"
516
  >
517
  <button
518
- class="inline text-lg font-thin text-gray-400 disabled:pointer-events-none disabled:opacity-25 hover:text-gray-800 dark:text-gray-500 dark:hover:text-gray-200"
519
  on:click={() => (childrenToRender = Math.max(0, childrenToRender - 1))}
520
  disabled={childrenToRender === 0 || loading}
521
  >
@@ -525,7 +525,7 @@
525
  {childrenToRender + 1} / {nChildren}
526
  </span>
527
  <button
528
- class="inline text-lg font-thin text-gray-400 disabled:pointer-events-none disabled:opacity-25 hover:text-gray-800 dark:text-gray-500 dark:hover:text-gray-200"
529
  on:click={() =>
530
  (childrenToRender = Math.min(
531
  message?.children?.length ?? 1 - 1,
 
295
  {/if}
296
 
297
  <div
298
+ class="prose max-w-none dark:prose-invert max-sm:prose-sm prose-headings:font-semibold prose-h1:text-lg prose-h2:text-base prose-h3:text-base prose-pre:bg-gray-800 dark:prose-pre:bg-gray-900"
299
  bind:this={contentEl}
300
  >
301
  {#if isLast && loading && $settings.disableStream}
 
343
  >
344
  {#if isAuthor}
345
  <button
346
+ class="btn rounded-sm p-1 text-sm text-gray-400 hover:text-gray-500 focus:ring-0 dark:text-gray-400 dark:hover:text-gray-300
347
  {message.score && message.score > 0
348
  ? 'text-green-500 hover:text-green-500 dark:text-green-400 hover:dark:text-green-400'
349
  : ''}"
 
355
  <CarbonThumbsUp class="h-[1.14em] w-[1.14em]" />
356
  </button>
357
  <button
358
+ class="btn rounded-sm p-1 text-sm text-gray-400 hover:text-gray-500 focus:ring-0 dark:text-gray-400 dark:hover:text-gray-300
359
  {message.score && message.score < 0
360
  ? 'text-red-500 hover:text-red-500 dark:text-red-400 hover:dark:text-red-400'
361
  : ''}"
 
368
  </button>
369
  {/if}
370
  <button
371
+ class="btn rounded-sm p-1 text-sm text-gray-400 hover:text-gray-500 focus:ring-0 dark:text-gray-400 dark:hover:text-gray-300"
372
  title="Retry"
373
  type="button"
374
  on:click={() => dispatch("retry", { id: message.id })}
 
439
  class="btn rounded-lg px-3 py-1.5 text-sm
440
  {loading
441
  ? 'bg-gray-300 text-gray-400 dark:bg-gray-700 dark:text-gray-600'
442
+ : 'bg-gray-200 text-gray-600 hover:text-gray-800 focus:ring-0 dark:bg-gray-800 dark:text-gray-300 dark:hover:text-gray-200'}
443
  "
444
  disabled={loading}
445
  >
 
447
  </button>
448
  <button
449
  type="button"
450
+ class="btn rounded-sm p-2 text-sm text-gray-400 hover:text-gray-500 focus:ring-0 dark:text-gray-400 dark:hover:text-gray-300"
451
  on:click={() => {
452
  $convTreeStore.editing = null;
453
  }}
 
469
  <div class="mx-auto flex flex-row flex-nowrap gap-2">
470
  {#if downloadLink}
471
  <a
472
+ class="rounded-lg border border-gray-100 bg-gray-100 p-1 text-xs text-gray-400 group-hover:block hover:text-gray-500 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-400 dark:hover:text-gray-300 max-sm:!hidden md:hidden"
473
  title="Download prompt and parameters"
474
  type="button"
475
  target="_blank"
 
480
  {/if}
481
  {#if !readOnly}
482
  <button
483
+ class="cursor-pointer rounded-lg border border-gray-100 bg-gray-100 p-1 text-xs text-gray-400 group-hover:block hover:text-gray-500 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-400 dark:hover:text-gray-300 md:hidden lg:-right-2"
484
  title="Branch"
485
  type="button"
486
  on:click={() => ($convTreeStore.editing = message.id)}
 
515
  class="font-white group/navbranch z-10 -mt-1 ml-3.5 mr-auto flex h-6 w-fit select-none flex-row items-center justify-center gap-1 text-sm"
516
  >
517
  <button
518
+ class="inline text-lg font-thin text-gray-400 hover:text-gray-800 disabled:pointer-events-none disabled:opacity-25 dark:text-gray-500 dark:hover:text-gray-200"
519
  on:click={() => (childrenToRender = Math.max(0, childrenToRender - 1))}
520
  disabled={childrenToRender === 0 || loading}
521
  >
 
525
  {childrenToRender + 1} / {nChildren}
526
  </span>
527
  <button
528
+ class="inline text-lg font-thin text-gray-400 hover:text-gray-800 disabled:pointer-events-none disabled:opacity-25 dark:text-gray-500 dark:hover:text-gray-200"
529
  on:click={() =>
530
  (childrenToRender = Math.min(
531
  message?.children?.length ?? 1 - 1,
src/lib/components/chat/ChatWindow.svelte CHANGED
@@ -281,7 +281,7 @@
281
  />
282
  </div>
283
  <div
284
- class="dark:via-gray-80 pointer-events-none absolute inset-x-0 bottom-0 z-0 mx-auto flex w-full max-w-3xl flex-col items-center justify-center bg-gradient-to-t from-white via-white/80 to-white/0 px-3.5 py-4 max-md:border-t max-md:bg-white sm:px-5 md:py-8 xl:max-w-4xl dark:border-gray-800 dark:from-gray-900 dark:to-gray-900/0 max-md:dark:bg-gray-900 [&>*]:pointer-events-auto"
285
  >
286
  {#if sources?.length}
287
  <div class="flex flex-row flex-wrap justify-center gap-2.5 max-md:pb-3">
@@ -373,19 +373,19 @@
373
 
374
  {#if loading}
375
  <button
376
- class="btn mx-1 my-1 inline-block h-[2.4rem] self-end rounded-lg bg-transparent p-1 px-[0.7rem] text-gray-400 disabled:opacity-60 enabled:hover:text-gray-700 md:hidden dark:disabled:opacity-40 enabled:dark:hover:text-gray-100"
377
  on:click={() => dispatch("stop")}
378
  >
379
  <CarbonStopFilledAlt />
380
  </button>
381
  <div
382
- class="mx-1 my-1 hidden h-[2.4rem] items-center p-1 px-[0.7rem] text-gray-400 disabled:opacity-60 enabled:hover:text-gray-700 md:flex dark:disabled:opacity-40 enabled:dark:hover:text-gray-100"
383
  >
384
  <EosIconsLoading />
385
  </div>
386
  {:else}
387
  <button
388
- class="btn mx-1 my-1 h-[2.4rem] self-end rounded-lg bg-transparent p-1 px-[0.7rem] text-gray-400 disabled:opacity-60 enabled:hover:text-gray-700 dark:disabled:opacity-40 enabled:dark:hover:text-gray-100"
389
  disabled={!message || isReadOnly}
390
  type="submit"
391
  >
 
281
  />
282
  </div>
283
  <div
284
+ class="dark:via-gray-80 pointer-events-none absolute inset-x-0 bottom-0 z-0 mx-auto flex w-full max-w-3xl flex-col items-center justify-center bg-gradient-to-t from-white via-white/80 to-white/0 px-3.5 py-4 dark:border-gray-800 dark:from-gray-900 dark:to-gray-900/0 max-md:border-t max-md:bg-white max-md:dark:bg-gray-900 sm:px-5 md:py-8 xl:max-w-4xl [&>*]:pointer-events-auto"
285
  >
286
  {#if sources?.length}
287
  <div class="flex flex-row flex-wrap justify-center gap-2.5 max-md:pb-3">
 
373
 
374
  {#if loading}
375
  <button
376
+ class="btn mx-1 my-1 inline-block h-[2.4rem] self-end rounded-lg bg-transparent p-1 px-[0.7rem] text-gray-400 enabled:hover:text-gray-700 disabled:opacity-60 enabled:dark:hover:text-gray-100 dark:disabled:opacity-40 md:hidden"
377
  on:click={() => dispatch("stop")}
378
  >
379
  <CarbonStopFilledAlt />
380
  </button>
381
  <div
382
+ class="mx-1 my-1 hidden h-[2.4rem] items-center p-1 px-[0.7rem] text-gray-400 enabled:hover:text-gray-700 disabled:opacity-60 enabled:dark:hover:text-gray-100 dark:disabled:opacity-40 md:flex"
383
  >
384
  <EosIconsLoading />
385
  </div>
386
  {:else}
387
  <button
388
+ class="btn mx-1 my-1 h-[2.4rem] self-end rounded-lg bg-transparent p-1 px-[0.7rem] text-gray-400 enabled:hover:text-gray-700 disabled:opacity-60 enabled:dark:hover:text-gray-100 dark:disabled:opacity-40"
389
  disabled={!message || isReadOnly}
390
  type="submit"
391
  >
src/lib/server/auth.ts CHANGED
@@ -1,4 +1,10 @@
1
- import { Issuer, BaseClient, type UserinfoResponse, TokenSet, custom } from "openid-client";
 
 
 
 
 
 
2
  import { addHours, addWeeks } from "date-fns";
3
  import { env } from "$env/dynamic/private";
4
  import { sha256 } from "$lib/utils/sha256";
 
1
+ import {
2
+ Issuer,
3
+ type BaseClient,
4
+ type UserinfoResponse,
5
+ type TokenSet,
6
+ custom,
7
+ } from "openid-client";
8
  import { addHours, addWeeks } from "date-fns";
9
  import { env } from "$env/dynamic/private";
10
  import { sha256 } from "$lib/utils/sha256";
src/lib/server/endpoints/anthropic/utils.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { ImageBlockParam, MessageParam } from "@anthropic-ai/sdk/resources";
2
  import { makeImageProcessor, type ImageProcessorOptions } from "../images";
3
  import type { EndpointMessage } from "../endpoints";
4
  import type { MessageFile } from "$lib/types/Message";
 
5
 
6
  export async function fileToImageBlock(
7
  file: MessageFile,
 
 
1
  import { makeImageProcessor, type ImageProcessorOptions } from "../images";
2
  import type { EndpointMessage } from "../endpoints";
3
  import type { MessageFile } from "$lib/types/Message";
4
+ import type { ImageBlockParam, MessageParam } from "@anthropic-ai/sdk/resources/messages.mjs";
5
 
6
  export async function fileToImageBlock(
7
  file: MessageFile,
src/lib/server/endpoints/cohere/endpointCohere.ts CHANGED
@@ -5,7 +5,7 @@ import type { TextGenerationStreamOutput } from "@huggingface/inference";
5
  import type { Cohere, CohereClient } from "cohere-ai";
6
  import { buildPrompt } from "$lib/buildPrompt";
7
  import { ToolResultStatus, type ToolCall } from "$lib/types/Tool";
8
- import { pipeline, Writable, Readable } from "node:stream";
9
  import { toolHasName } from "$lib/utils/tools";
10
 
11
  export const endpointCohereParametersSchema = z.object({
@@ -78,7 +78,7 @@ export async function endpointCohere(
78
  .map((message) => ({
79
  role: message.from === "user" ? "USER" : "CHATBOT",
80
  message: message.content,
81
- })) satisfies Cohere.ChatMessage[];
82
 
83
  stream = await cohere
84
  .chatStream({
 
5
  import type { Cohere, CohereClient } from "cohere-ai";
6
  import { buildPrompt } from "$lib/buildPrompt";
7
  import { ToolResultStatus, type ToolCall } from "$lib/types/Tool";
8
+ import { pipeline, Writable, type Readable } from "node:stream";
9
  import { toolHasName } from "$lib/utils/tools";
10
 
11
  export const endpointCohereParametersSchema = z.object({
 
78
  .map((message) => ({
79
  role: message.from === "user" ? "USER" : "CHATBOT",
80
  message: message.content,
81
+ })) satisfies Cohere.Message[];
82
 
83
  stream = await cohere
84
  .chatStream({
src/lib/server/exitHandler.ts CHANGED
@@ -17,7 +17,7 @@ export function onExit(cb: ExitHandler): ExitHandlerUnsubscribe {
17
 
18
  async function runExitHandler(handler: ExitHandler): Promise<void> {
19
  return timeout(Promise.resolve().then(handler), 30_000).catch((err) => {
20
- logger.error("Exit handler failed to run", err);
21
  });
22
  }
23
 
 
17
 
18
  async function runExitHandler(handler: ExitHandler): Promise<void> {
19
  return timeout(Promise.resolve().then(handler), 30_000).catch((err) => {
20
+ logger.error(err, "Exit handler failed to run");
21
  });
22
  }
23
 
src/lib/server/files/downloadFile.ts CHANGED
@@ -12,10 +12,10 @@ export async function downloadFile(
12
 
13
  const file = await fileId.next();
14
  if (!file) {
15
- throw error(404, "File not found");
16
  }
17
  if (file.metadata?.conversation !== convId.toString()) {
18
- throw error(403, "You don't have access to this file.");
19
  }
20
 
21
  const mime = file.metadata?.mime;
 
12
 
13
  const file = await fileId.next();
14
  if (!file) {
15
+ error(404, "File not found");
16
  }
17
  if (file.metadata?.conversation !== convId.toString()) {
18
+ error(403, "You don't have access to this file.");
19
  }
20
 
21
  const mime = file.metadata?.mime;
src/routes/+layout.svelte CHANGED
@@ -207,7 +207,7 @@
207
  <div
208
  class="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd {!isNavCollapsed
209
  ? 'md:grid-cols-[280px,1fr]'
210
- : 'md:grid-cols-[0px,1fr]'} transition-[300ms] [transition-property:grid-template-columns] md:grid-rows-[1fr] dark:text-gray-300"
211
  >
212
  <MobileNav isOpen={isNavOpen} on:toggle={(ev) => (isNavOpen = ev.detail)} title={mobileNavTitle}>
213
  <NavMenu
@@ -242,8 +242,8 @@
242
  href="https://play.google.com/store/apps/details?id=co.huggingface.chat_ui_android"
243
  class="fixed left-0 right-0 top-0 mx-auto flex h-fit min-h-12 w-screen flex-nowrap items-center justify-evenly gap-4 bg-gray-200 px-4 py-4 text-gray-900 shadow-lg backdrop-blur-md
244
  hover:bg-gray-100
245
- sm:top-5 sm:max-w-fit sm:gap-4 sm:rounded-lg
246
- dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
247
  >
248
  <button
249
  class="border-r-2 border-black/20 pr-4 text-2xl"
 
207
  <div
208
  class="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd {!isNavCollapsed
209
  ? 'md:grid-cols-[280px,1fr]'
210
+ : 'md:grid-cols-[0px,1fr]'} transition-[300ms] [transition-property:grid-template-columns] dark:text-gray-300 md:grid-rows-[1fr]"
211
  >
212
  <MobileNav isOpen={isNavOpen} on:toggle={(ev) => (isNavOpen = ev.detail)} title={mobileNavTitle}>
213
  <NavMenu
 
242
  href="https://play.google.com/store/apps/details?id=co.huggingface.chat_ui_android"
243
  class="fixed left-0 right-0 top-0 mx-auto flex h-fit min-h-12 w-screen flex-nowrap items-center justify-evenly gap-4 bg-gray-200 px-4 py-4 text-gray-900 shadow-lg backdrop-blur-md
244
  hover:bg-gray-100
245
+ dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 sm:top-5
246
+ sm:max-w-fit sm:gap-4 sm:rounded-lg"
247
  >
248
  <button
249
  class="border-r-2 border-black/20 pr-4 text-2xl"
src/routes/admin/export/+server.ts CHANGED
@@ -14,7 +14,7 @@ import { logger } from "$lib/server/logger.js";
14
 
15
  export async function POST({ request }) {
16
  if (!env.PARQUET_EXPORT_DATASET || !env.PARQUET_EXPORT_HF_TOKEN) {
17
- throw error(500, "Parquet export is not configured.");
18
  }
19
 
20
  const { model } = z
 
14
 
15
  export async function POST({ request }) {
16
  if (!env.PARQUET_EXPORT_DATASET || !env.PARQUET_EXPORT_HF_TOKEN) {
17
+ error(500, "Parquet export is not configured.");
18
  }
19
 
20
  const { model } = z
src/routes/assistant/[assistantId]/+page.server.ts CHANGED
@@ -10,11 +10,11 @@ export const load = async ({ params }) => {
10
  });
11
 
12
  if (!assistant) {
13
- throw redirect(302, `${base}`);
14
  }
15
 
16
  return { assistant: JSON.parse(JSON.stringify(assistant)) };
17
  } catch {
18
- throw redirect(302, `${base}`);
19
  }
20
  };
 
10
  });
11
 
12
  if (!assistant) {
13
+ redirect(302, `${base}`);
14
  }
15
 
16
  return { assistant: JSON.parse(JSON.stringify(assistant)) };
17
  } catch {
18
+ redirect(302, `${base}`);
19
  }
20
  };
src/routes/assistant/[assistantId]/thumbnail.png/+server.ts CHANGED
@@ -18,7 +18,7 @@ export const GET: RequestHandler = (async ({ params }) => {
18
  });
19
 
20
  if (!assistant) {
21
- throw error(404, "Assistant not found.");
22
  }
23
 
24
  let avatar = "";
 
18
  });
19
 
20
  if (!assistant) {
21
+ error(404, "Assistant not found.");
22
  }
23
 
24
  let avatar = "";
src/routes/assistants/+page.server.ts CHANGED
@@ -11,7 +11,7 @@ const NUM_PER_PAGE = 24;
11
 
12
  export const load = async ({ url, locals }) => {
13
  if (!env.ENABLE_ASSISTANTS) {
14
- throw redirect(302, `${base}/`);
15
  }
16
 
17
  const modelId = url.searchParams.get("modelId");
@@ -28,7 +28,7 @@ export const load = async ({ url, locals }) => {
28
  { projection: { _id: 1 } }
29
  );
30
  if (!user) {
31
- throw error(404, `User "${username}" doesn't exist`);
32
  }
33
  }
34
 
 
11
 
12
  export const load = async ({ url, locals }) => {
13
  if (!env.ENABLE_ASSISTANTS) {
14
+ redirect(302, `${base}/`);
15
  }
16
 
17
  const modelId = url.searchParams.get("modelId");
 
28
  { projection: { _id: 1 } }
29
  );
30
  if (!user) {
31
+ error(404, `User "${username}" doesn't exist`);
32
  }
33
  }
34
 
src/routes/assistants/+page.svelte CHANGED
@@ -195,7 +195,7 @@
195
  {/if}
196
  {/if}
197
  <div
198
- class="relative ml-auto flex h-[30px] w-40 items-center rounded-full border px-2 has-[:focus]:border-gray-400 sm:w-64 dark:border-gray-600"
199
  >
200
  <CarbonSearch class="pointer-events-none absolute left-2 text-xs text-gray-400" />
201
  <input
@@ -227,7 +227,7 @@
227
  !!assistant?.dynamicPrompt}
228
 
229
  <button
230
- class="relative flex flex-col items-center justify-center overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 py-6 text-center shadow hover:bg-gray-50 hover:shadow-inner max-sm:px-4 sm:h-64 sm:pb-4 xl:pt-8 dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40"
231
  on:click={() => {
232
  if (data.settings.assistants.includes(assistant._id.toString())) {
233
  settings.instantSet({ activeModel: assistant._id.toString() });
@@ -263,7 +263,7 @@
263
  />
264
  {:else}
265
  <div
266
- class="mb-2 flex aspect-square size-12 flex-none items-center justify-center rounded-full bg-gray-300 text-2xl font-bold uppercase text-gray-500 sm:mb-6 sm:size-20 dark:bg-gray-800"
267
  >
268
  {assistant.name[0]}
269
  </div>
@@ -273,7 +273,7 @@
273
  >
274
  {assistant.name}
275
  </h3>
276
- <p class="line-clamp-4 text-xs text-gray-700 sm:line-clamp-2 dark:text-gray-400">
277
  {assistant.description}
278
  </p>
279
  {#if assistant.createdByName}
 
195
  {/if}
196
  {/if}
197
  <div
198
+ class="relative ml-auto flex h-[30px] w-40 items-center rounded-full border px-2 has-[:focus]:border-gray-400 dark:border-gray-600 sm:w-64"
199
  >
200
  <CarbonSearch class="pointer-events-none absolute left-2 text-xs text-gray-400" />
201
  <input
 
227
  !!assistant?.dynamicPrompt}
228
 
229
  <button
230
+ class="relative flex flex-col items-center justify-center overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 py-6 text-center shadow hover:bg-gray-50 hover:shadow-inner dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40 max-sm:px-4 sm:h-64 sm:pb-4 xl:pt-8"
231
  on:click={() => {
232
  if (data.settings.assistants.includes(assistant._id.toString())) {
233
  settings.instantSet({ activeModel: assistant._id.toString() });
 
263
  />
264
  {:else}
265
  <div
266
+ class="mb-2 flex aspect-square size-12 flex-none items-center justify-center rounded-full bg-gray-300 text-2xl font-bold uppercase text-gray-500 dark:bg-gray-800 sm:mb-6 sm:size-20"
267
  >
268
  {assistant.name[0]}
269
  </div>
 
273
  >
274
  {assistant.name}
275
  </h3>
276
+ <p class="line-clamp-4 text-xs text-gray-700 dark:text-gray-400 sm:line-clamp-2">
277
  {assistant.description}
278
  </p>
279
  {#if assistant.createdByName}
src/routes/conversation/+server.ts CHANGED
@@ -27,23 +27,20 @@ export const POST: RequestHandler = async ({ locals, request }) => {
27
  .safeParse(JSON.parse(body));
28
 
29
  if (!parsedBody.success) {
30
- throw error(400, "Invalid request");
31
  }
32
  const values = parsedBody.data;
33
 
34
  const convCount = await collections.conversations.countDocuments(authCondition(locals));
35
 
36
  if (usageLimits?.conversations && convCount > usageLimits?.conversations) {
37
- throw error(
38
- 429,
39
- "You have reached the maximum number of conversations. Delete some to continue."
40
- );
41
  }
42
 
43
  const model = models.find((m) => (m.id || m.name) === values.model);
44
 
45
  if (!model) {
46
- throw error(400, "Invalid model");
47
  }
48
 
49
  let messages: Message[] = [
@@ -67,7 +64,7 @@ export const POST: RequestHandler = async ({ locals, request }) => {
67
  });
68
 
69
  if (!conversation) {
70
- throw error(404, "Conversation not found");
71
  }
72
 
73
  title = conversation.title;
@@ -82,7 +79,7 @@ export const POST: RequestHandler = async ({ locals, request }) => {
82
  embeddingModel ??= model.embeddingModel ?? defaultEmbeddingModel.name;
83
 
84
  if (model.unlisted) {
85
- throw error(400, "Can't start a conversation with an unlisted model");
86
  }
87
 
88
  // get preprompt from assistant if it exists
@@ -127,5 +124,5 @@ export const POST: RequestHandler = async ({ locals, request }) => {
127
  };
128
 
129
  export const GET: RequestHandler = async () => {
130
- throw redirect(302, `${base}/`);
131
  };
 
27
  .safeParse(JSON.parse(body));
28
 
29
  if (!parsedBody.success) {
30
+ error(400, "Invalid request");
31
  }
32
  const values = parsedBody.data;
33
 
34
  const convCount = await collections.conversations.countDocuments(authCondition(locals));
35
 
36
  if (usageLimits?.conversations && convCount > usageLimits?.conversations) {
37
+ error(429, "You have reached the maximum number of conversations. Delete some to continue.");
 
 
 
38
  }
39
 
40
  const model = models.find((m) => (m.id || m.name) === values.model);
41
 
42
  if (!model) {
43
+ error(400, "Invalid model");
44
  }
45
 
46
  let messages: Message[] = [
 
64
  });
65
 
66
  if (!conversation) {
67
+ error(404, "Conversation not found");
68
  }
69
 
70
  title = conversation.title;
 
79
  embeddingModel ??= model.embeddingModel ?? defaultEmbeddingModel.name;
80
 
81
  if (model.unlisted) {
82
+ error(400, "Can't start a conversation with an unlisted model");
83
  }
84
 
85
  // get preprompt from assistant if it exists
 
124
  };
125
 
126
  export const GET: RequestHandler = async () => {
127
+ redirect(302, `${base}/`);
128
  };
src/routes/conversation/[id]/+page.server.ts CHANGED
@@ -18,7 +18,7 @@ export const load = async ({ params, depends, locals }) => {
18
  shared = true;
19
 
20
  if (!conversation) {
21
- throw error(404, "Conversation not found");
22
  }
23
  } else {
24
  // todo: add validation on params.id
@@ -36,13 +36,13 @@ export const load = async ({ params, depends, locals }) => {
36
  })) !== 0;
37
 
38
  if (conversationExists) {
39
- throw error(
40
  403,
41
  "You don't have access to this conversation. If someone gave you this link, ask them to use the 'share' feature instead."
42
  );
43
  }
44
 
45
- throw error(404, "Conversation not found.");
46
  }
47
  }
48
 
@@ -73,7 +73,7 @@ export const actions = {
73
  const messageId = data.get("messageId");
74
 
75
  if (!messageId || typeof messageId !== "string") {
76
- throw error(400, "Invalid message id");
77
  }
78
 
79
  const conversation = await collections.conversations.findOne({
@@ -82,7 +82,7 @@ export const actions = {
82
  });
83
 
84
  if (!conversation) {
85
- throw error(404, "Conversation not found");
86
  }
87
 
88
  const filteredMessages = conversation.messages
 
18
  shared = true;
19
 
20
  if (!conversation) {
21
+ error(404, "Conversation not found");
22
  }
23
  } else {
24
  // todo: add validation on params.id
 
36
  })) !== 0;
37
 
38
  if (conversationExists) {
39
+ error(
40
  403,
41
  "You don't have access to this conversation. If someone gave you this link, ask them to use the 'share' feature instead."
42
  );
43
  }
44
 
45
+ error(404, "Conversation not found.");
46
  }
47
  }
48
 
 
73
  const messageId = data.get("messageId");
74
 
75
  if (!messageId || typeof messageId !== "string") {
76
+ error(400, "Invalid message id");
77
  }
78
 
79
  const conversation = await collections.conversations.findOne({
 
82
  });
83
 
84
  if (!conversation) {
85
+ error(404, "Conversation not found");
86
  }
87
 
88
  const filteredMessages = conversation.messages
src/routes/conversation/[id]/+server.ts CHANGED
@@ -33,7 +33,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
33
 
34
  // check user
35
  if (!userId) {
36
- throw error(401, "Unauthorized");
37
  }
38
 
39
  // check if the user has access to the conversation
@@ -56,7 +56,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
56
  );
57
 
58
  if (!res.acknowledged) {
59
- throw error(500, "Failed to convert conversation");
60
  }
61
  }
62
 
@@ -66,7 +66,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
66
  });
67
 
68
  if (!conv) {
69
- throw error(404, "Conversation not found");
70
  }
71
 
72
  // register the event for ratelimiting
@@ -95,7 +95,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
95
  )[0]?.messages ?? 0;
96
 
97
  if (totalMessages > messagesBeforeLogin) {
98
- throw error(429, "Exceeded number of messages before login");
99
  }
100
  }
101
 
@@ -112,12 +112,12 @@ export async function POST({ request, locals, params, getClientAddress }) {
112
  })
113
  );
114
  if (nEvents > usageLimits.messagesPerMinute) {
115
- throw error(429, ERROR_MESSAGES.rateLimited);
116
  }
117
  }
118
 
119
  if (usageLimits?.messages && conv.messages.length > usageLimits.messages) {
120
- throw error(
121
  429,
122
  `This conversation has more than ${usageLimits.messages} messages. Start a new one to continue`
123
  );
@@ -127,7 +127,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
127
  const model = models.find((m) => m.id === conv.model);
128
 
129
  if (!model) {
130
- throw error(410, "Model not available anymore");
131
  }
132
 
133
  // finally parse the content of the request
@@ -136,7 +136,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
136
  const json = form.get("data");
137
 
138
  if (!json || typeof json !== "string") {
139
- throw error(400, "Invalid request");
140
  }
141
 
142
  const {
@@ -185,7 +185,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
185
  );
186
 
187
  if (usageLimits?.messageLength && (newPrompt?.length ?? 0) > usageLimits.messageLength) {
188
- throw error(400, "Message too long.");
189
  }
190
 
191
  // each file is either:
@@ -203,7 +203,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
203
  // check sizes
204
  // todo: make configurable
205
  if (b64Files.some((file) => file.size > 10 * 1024 * 1024)) {
206
- throw error(413, "File too large, should be <10MB");
207
  }
208
 
209
  const uploadedFiles = await Promise.all(b64Files.map((file) => uploadFile(file, conv))).then(
@@ -219,7 +219,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
219
  // if it's the last message and we continue then we build the prompt up to the last message
220
  // we will strip the end tokens afterwards when the prompt is built
221
  if ((conv.messages.find((msg) => msg.id === messageId)?.children?.length ?? 0) > 0) {
222
- throw error(400, "Can only continue the last message");
223
  }
224
  messageToWriteToId = messageId;
225
  messagesForPrompt = buildSubtree(conv, messageId);
@@ -232,7 +232,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
232
  const messageToRetry = conv.messages.find((message) => message.id === messageId);
233
 
234
  if (!messageToRetry) {
235
- throw error(404, "Message not found");
236
  }
237
 
238
  if (messageToRetry.from === "user" && newPrompt) {
@@ -302,10 +302,10 @@ export async function POST({ request, locals, params, getClientAddress }) {
302
 
303
  const messageToWriteTo = conv.messages.find((message) => message.id === messageToWriteToId);
304
  if (!messageToWriteTo) {
305
- throw error(500, "Failed to create message");
306
  }
307
  if (messagesForPrompt.length === 0) {
308
- throw error(500, "Failed to create prompt");
309
  }
310
 
311
  // update the conversation with the new messages
@@ -491,7 +491,7 @@ export async function DELETE({ locals, params }) {
491
  });
492
 
493
  if (!conv) {
494
- throw error(404, "Conversation not found");
495
  }
496
 
497
  await collections.conversations.deleteOne({ _id: conv._id });
@@ -512,7 +512,7 @@ export async function PATCH({ request, locals, params }) {
512
  });
513
 
514
  if (!conv) {
515
- throw error(404, "Conversation not found");
516
  }
517
 
518
  await collections.conversations.updateOne(
 
33
 
34
  // check user
35
  if (!userId) {
36
+ error(401, "Unauthorized");
37
  }
38
 
39
  // check if the user has access to the conversation
 
56
  );
57
 
58
  if (!res.acknowledged) {
59
+ error(500, "Failed to convert conversation");
60
  }
61
  }
62
 
 
66
  });
67
 
68
  if (!conv) {
69
+ error(404, "Conversation not found");
70
  }
71
 
72
  // register the event for ratelimiting
 
95
  )[0]?.messages ?? 0;
96
 
97
  if (totalMessages > messagesBeforeLogin) {
98
+ error(429, "Exceeded number of messages before login");
99
  }
100
  }
101
 
 
112
  })
113
  );
114
  if (nEvents > usageLimits.messagesPerMinute) {
115
+ error(429, ERROR_MESSAGES.rateLimited);
116
  }
117
  }
118
 
119
  if (usageLimits?.messages && conv.messages.length > usageLimits.messages) {
120
+ error(
121
  429,
122
  `This conversation has more than ${usageLimits.messages} messages. Start a new one to continue`
123
  );
 
127
  const model = models.find((m) => m.id === conv.model);
128
 
129
  if (!model) {
130
+ error(410, "Model not available anymore");
131
  }
132
 
133
  // finally parse the content of the request
 
136
  const json = form.get("data");
137
 
138
  if (!json || typeof json !== "string") {
139
+ error(400, "Invalid request");
140
  }
141
 
142
  const {
 
185
  );
186
 
187
  if (usageLimits?.messageLength && (newPrompt?.length ?? 0) > usageLimits.messageLength) {
188
+ error(400, "Message too long.");
189
  }
190
 
191
  // each file is either:
 
203
  // check sizes
204
  // todo: make configurable
205
  if (b64Files.some((file) => file.size > 10 * 1024 * 1024)) {
206
+ error(413, "File too large, should be <10MB");
207
  }
208
 
209
  const uploadedFiles = await Promise.all(b64Files.map((file) => uploadFile(file, conv))).then(
 
219
  // if it's the last message and we continue then we build the prompt up to the last message
220
  // we will strip the end tokens afterwards when the prompt is built
221
  if ((conv.messages.find((msg) => msg.id === messageId)?.children?.length ?? 0) > 0) {
222
+ error(400, "Can only continue the last message");
223
  }
224
  messageToWriteToId = messageId;
225
  messagesForPrompt = buildSubtree(conv, messageId);
 
232
  const messageToRetry = conv.messages.find((message) => message.id === messageId);
233
 
234
  if (!messageToRetry) {
235
+ error(404, "Message not found");
236
  }
237
 
238
  if (messageToRetry.from === "user" && newPrompt) {
 
302
 
303
  const messageToWriteTo = conv.messages.find((message) => message.id === messageToWriteToId);
304
  if (!messageToWriteTo) {
305
+ error(500, "Failed to create message");
306
  }
307
  if (messagesForPrompt.length === 0) {
308
+ error(500, "Failed to create prompt");
309
  }
310
 
311
  // update the conversation with the new messages
 
491
  });
492
 
493
  if (!conv) {
494
+ error(404, "Conversation not found");
495
  }
496
 
497
  await collections.conversations.deleteOne({ _id: conv._id });
 
512
  });
513
 
514
  if (!conv) {
515
+ error(404, "Conversation not found");
516
  }
517
 
518
  await collections.conversations.updateOne(
src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts CHANGED
@@ -19,7 +19,7 @@ export async function GET({ params, locals }) {
19
  });
20
 
21
  if (conv === null) {
22
- throw error(404, "Conversation not found");
23
  }
24
 
25
  const messageId = params.messageId;
@@ -27,13 +27,13 @@ export async function GET({ params, locals }) {
27
  const messageIndex = conv.messages.findIndex((msg) => msg.id === messageId);
28
 
29
  if (!isMessageId(messageId) || messageIndex === -1) {
30
- throw error(404, "Message not found");
31
  }
32
 
33
  const model = models.find((m) => m.id === conv.model);
34
 
35
  if (!model) {
36
- throw error(404, "Conversation model not found");
37
  }
38
 
39
  const messagesUpTo = buildSubtree(conv, messageId);
 
19
  });
20
 
21
  if (conv === null) {
22
+ error(404, "Conversation not found");
23
  }
24
 
25
  const messageId = params.messageId;
 
27
  const messageIndex = conv.messages.findIndex((msg) => msg.id === messageId);
28
 
29
  if (!isMessageId(messageId) || messageIndex === -1) {
30
+ error(404, "Message not found");
31
  }
32
 
33
  const model = models.find((m) => m.id === conv.model);
34
 
35
  if (!model) {
36
+ error(404, "Conversation model not found");
37
  }
38
 
39
  const messagesUpTo = buildSubtree(conv, messageId);
src/routes/conversation/[id]/message/[messageId]/vote/+server.ts CHANGED
@@ -31,7 +31,7 @@ export async function POST({ params, request, locals }) {
31
  );
32
 
33
  if (!document.matchedCount) {
34
- throw error(404, "Message not found");
35
  }
36
 
37
  return new Response();
 
31
  );
32
 
33
  if (!document.matchedCount) {
34
+ error(404, "Message not found");
35
  }
36
 
37
  return new Response();
src/routes/conversation/[id]/output/[sha256]/+server.ts CHANGED
@@ -13,7 +13,7 @@ export const GET: RequestHandler = async ({ locals, params }) => {
13
 
14
  // check user
15
  if (!userId) {
16
- throw error(401, "Unauthorized");
17
  }
18
 
19
  if (params.id.length !== 7) {
@@ -26,7 +26,7 @@ export const GET: RequestHandler = async ({ locals, params }) => {
26
  });
27
 
28
  if (!conv) {
29
- throw error(404, "Conversation not found");
30
  }
31
  } else {
32
  // look for the conversation in shared conversations
@@ -35,7 +35,7 @@ export const GET: RequestHandler = async ({ locals, params }) => {
35
  });
36
 
37
  if (!conv) {
38
- throw error(404, "Conversation not found");
39
  }
40
  }
41
 
 
13
 
14
  // check user
15
  if (!userId) {
16
+ error(401, "Unauthorized");
17
  }
18
 
19
  if (params.id.length !== 7) {
 
26
  });
27
 
28
  if (!conv) {
29
+ error(404, "Conversation not found");
30
  }
31
  } else {
32
  // look for the conversation in shared conversations
 
35
  });
36
 
37
  if (!conv) {
38
+ error(404, "Conversation not found");
39
  }
40
  }
41
 
src/routes/conversation/[id]/share/+server.ts CHANGED
@@ -14,7 +14,7 @@ export async function POST({ params, url, locals }) {
14
  });
15
 
16
  if (!conversation) {
17
- throw error(404, "Conversation not found");
18
  }
19
 
20
  const hash = await hashConv(conversation);
 
14
  });
15
 
16
  if (!conversation) {
17
+ error(404, "Conversation not found");
18
  }
19
 
20
  const hash = await hashConv(conversation);
src/routes/conversation/[id]/stop-generating/+server.ts CHANGED
@@ -15,7 +15,7 @@ export async function POST({ params, locals }) {
15
  });
16
 
17
  if (!conversation) {
18
- throw error(404, "Conversation not found");
19
  }
20
 
21
  await collections.abortedGenerations.updateOne(
 
15
  });
16
 
17
  if (!conversation) {
18
+ error(404, "Conversation not found");
19
  }
20
 
21
  await collections.abortedGenerations.updateOne(
src/routes/conversations/+page.server.ts CHANGED
@@ -12,6 +12,6 @@ export const actions = {
12
  });
13
  }
14
 
15
- throw redirect(303, `${base}/`);
16
  },
17
  };
 
12
  });
13
  }
14
 
15
+ redirect(303, `${base}/`);
16
  },
17
  };
src/routes/login/+page.server.ts CHANGED
@@ -22,6 +22,6 @@ export const actions = {
22
  { sessionId: locals.sessionId }
23
  );
24
 
25
- throw redirect(303, authorizationUrl);
26
  },
27
  };
 
22
  { sessionId: locals.sessionId }
23
  );
24
 
25
+ redirect(303, authorizationUrl);
26
  },
27
  };
src/routes/login/callback/+page.server.ts CHANGED
@@ -22,7 +22,7 @@ export async function load({ url, locals, cookies, request, getClientAddress })
22
  .parse(Object.fromEntries(url.searchParams.entries()));
23
 
24
  if (errorName) {
25
- throw error(400, errorName + (errorDescription ? ": " + errorDescription : ""));
26
  }
27
 
28
  const { code, state, iss } = z
@@ -38,7 +38,7 @@ export async function load({ url, locals, cookies, request, getClientAddress })
38
  const validatedToken = await validateAndParseCsrfToken(csrfToken, locals.sessionId);
39
 
40
  if (!validatedToken) {
41
- throw error(403, "Invalid or expired CSRF token");
42
  }
43
 
44
  const { userData } = await getOIDCUserData(
@@ -50,14 +50,14 @@ export async function load({ url, locals, cookies, request, getClientAddress })
50
  // Filter by allowed user emails
51
  if (allowedUserEmails.length > 0) {
52
  if (!userData.email) {
53
- throw error(403, "User not allowed: email not returned");
54
  }
55
  const emailVerified = userData.email_verified ?? true;
56
  if (!emailVerified) {
57
- throw error(403, "User not allowed: email not verified");
58
  }
59
  if (!allowedUserEmails.includes(userData.email)) {
60
- throw error(403, "User not allowed");
61
  }
62
  }
63
 
@@ -71,5 +71,5 @@ export async function load({ url, locals, cookies, request, getClientAddress })
71
  ip: getClientAddress(),
72
  });
73
 
74
- throw redirect(302, `${base}/`);
75
  }
 
22
  .parse(Object.fromEntries(url.searchParams.entries()));
23
 
24
  if (errorName) {
25
+ error(400, errorName + (errorDescription ? ": " + errorDescription : ""));
26
  }
27
 
28
  const { code, state, iss } = z
 
38
  const validatedToken = await validateAndParseCsrfToken(csrfToken, locals.sessionId);
39
 
40
  if (!validatedToken) {
41
+ error(403, "Invalid or expired CSRF token");
42
  }
43
 
44
  const { userData } = await getOIDCUserData(
 
50
  // Filter by allowed user emails
51
  if (allowedUserEmails.length > 0) {
52
  if (!userData.email) {
53
+ error(403, "User not allowed: email not returned");
54
  }
55
  const emailVerified = userData.email_verified ?? true;
56
  if (!emailVerified) {
57
+ error(403, "User not allowed: email not verified");
58
  }
59
  if (!allowedUserEmails.includes(userData.email)) {
60
+ error(403, "User not allowed");
61
  }
62
  }
63
 
 
71
  ip: getClientAddress(),
72
  });
73
 
74
+ redirect(302, `${base}/`);
75
  }
src/routes/login/callback/updateUser.ts CHANGED
@@ -103,7 +103,7 @@ export async function updateUser(params: {
103
  const sessionId = await sha256(secretSessionId);
104
 
105
  if (await collections.sessions.findOne({ sessionId })) {
106
- throw error(500, "Session ID collision");
107
  }
108
 
109
  locals.sessionId = sessionId;
 
103
  const sessionId = await sha256(secretSessionId);
104
 
105
  if (await collections.sessions.findOne({ sessionId })) {
106
+ error(500, "Session ID collision");
107
  }
108
 
109
  locals.sessionId = sessionId;
src/routes/logout/+page.server.ts CHANGED
@@ -15,6 +15,6 @@ export const actions = {
15
  secure: !dev && !(env.ALLOW_INSECURE_COOKIES === "true"),
16
  httpOnly: true,
17
  });
18
- throw redirect(303, `${base}/`);
19
  },
20
  };
 
15
  secure: !dev && !(env.ALLOW_INSECURE_COOKIES === "true"),
16
  httpOnly: true,
17
  });
18
+ redirect(303, `${base}/`);
19
  },
20
  };
src/routes/models/[...model]/+page.server.ts CHANGED
@@ -9,7 +9,7 @@ export async function load({ params, locals, parent }) {
9
  const data = await parent();
10
 
11
  if (!model || model.unlisted) {
12
- throw redirect(302, `${base}/`);
13
  }
14
 
15
  if (locals.user?._id ?? locals.sessionId) {
 
9
  const data = await parent();
10
 
11
  if (!model || model.unlisted) {
12
+ redirect(302, `${base}/`);
13
  }
14
 
15
  if (locals.user?._id ?? locals.sessionId) {
src/routes/models/[...model]/thumbnail.png/+server.ts CHANGED
@@ -15,7 +15,7 @@ export const GET: RequestHandler = (async ({ params }) => {
15
  const model = models.find(({ id }) => id === params.model);
16
 
17
  if (!model || model.unlisted) {
18
- throw redirect(302, `${base}/`);
19
  }
20
  const renderedComponent = (ModelThumbnail as unknown as SvelteComponent).render({
21
  name: model.name,
 
15
  const model = models.find(({ id }) => id === params.model);
16
 
17
  if (!model || model.unlisted) {
18
+ redirect(302, `${base}/`);
19
  }
20
  const renderedComponent = (ModelThumbnail as unknown as SvelteComponent).render({
21
  name: model.name,
src/routes/r/[id]/+page.ts CHANGED
@@ -1,5 +1,5 @@
1
  import { redirect } from "@sveltejs/kit";
2
 
3
  export const load = async ({ params }) => {
4
- throw redirect(302, "../conversation/" + params.id);
5
  };
 
1
  import { redirect } from "@sveltejs/kit";
2
 
3
  export const load = async ({ params }) => {
4
+ redirect(302, "../conversation/" + params.id);
5
  };
src/routes/settings/(nav)/+page.svelte CHANGED
@@ -96,7 +96,7 @@
96
  </p>
97
  <button
98
  type="submit"
99
- class="mt-2 rounded-full bg-red-700 px-5 py-2 text-lg font-semibold text-gray-100 ring-gray-400 ring-offset-1 transition-all focus-visible:outline-none focus-visible:ring hover:ring"
100
  >
101
  Confirm deletion
102
  </button>
 
96
  </p>
97
  <button
98
  type="submit"
99
+ class="mt-2 rounded-full bg-red-700 px-5 py-2 text-lg font-semibold text-gray-100 ring-gray-400 ring-offset-1 transition-all hover:ring focus-visible:outline-none focus-visible:ring"
100
  >
101
  Confirm deletion
102
  </button>
src/routes/settings/(nav)/[...model]/+page.ts CHANGED
@@ -7,7 +7,7 @@ export async function load({ parent, params }) {
7
  const model = data.models.find((m: { id: string }) => m.id === params.model);
8
 
9
  if (!model || model.unlisted) {
10
- throw redirect(302, `${base}/settings`);
11
  }
12
 
13
  return data;
 
7
  const model = data.models.find((m: { id: string }) => m.id === params.model);
8
 
9
  if (!model || model.unlisted) {
10
+ redirect(302, `${base}/settings`);
11
  }
12
 
13
  return data;
src/routes/settings/(nav)/assistants/[assistantId]/+page.server.ts CHANGED
@@ -57,7 +57,7 @@ export const actions: Actions = {
57
  fileId = await fileCursor.next();
58
  }
59
 
60
- throw redirect(302, `${base}/settings`);
61
  },
62
  report: async ({ request, params, locals, url }) => {
63
  // is there already a report from this user for this model ?
@@ -168,7 +168,7 @@ export const actions: Actions = {
168
  await collections.assistants.updateOne({ _id: assistant._id }, { $inc: { userCount: -1 } });
169
  }
170
 
171
- throw redirect(302, `${base}/settings`);
172
  },
173
 
174
  unfeature: async ({ params, locals }) => {
 
57
  fileId = await fileCursor.next();
58
  }
59
 
60
+ redirect(302, `${base}/settings`);
61
  },
62
  report: async ({ request, params, locals, url }) => {
63
  // is there already a report from this user for this model ?
 
168
  await collections.assistants.updateOne({ _id: assistant._id }, { $inc: { userCount: -1 } });
169
  }
170
 
171
+ redirect(302, `${base}/settings`);
172
  },
173
 
174
  unfeature: async ({ params, locals }) => {
src/routes/settings/(nav)/assistants/[assistantId]/+page.ts CHANGED
@@ -7,7 +7,7 @@ export async function load({ parent, params }) {
7
  const assistant = data.settings.assistants.find((id) => id === params.assistantId);
8
 
9
  if (!assistant) {
10
- throw redirect(302, `${base}/assistant/${params.assistantId}`);
11
  }
12
 
13
  return data;
 
7
  const assistant = data.settings.assistants.find((id) => id === params.assistantId);
8
 
9
  if (!assistant) {
10
+ redirect(302, `${base}/assistant/${params.assistantId}`);
11
  }
12
 
13
  return data;
src/routes/settings/(nav)/assistants/[assistantId]/avatar.jpg/+server.ts CHANGED
@@ -8,18 +8,18 @@ export const GET: RequestHandler = async ({ params }) => {
8
  });
9
 
10
  if (!assistant) {
11
- throw error(404, "No assistant found");
12
  }
13
 
14
  if (!assistant.avatar) {
15
- throw error(404, "No avatar found");
16
  }
17
 
18
  const fileId = collections.bucket.find({ filename: assistant._id.toString() });
19
 
20
  const content = await fileId.next().then(async (file) => {
21
  if (!file?._id) {
22
- throw error(404, "Avatar not found");
23
  }
24
 
25
  const fileStream = collections.bucket.openDownloadStream(file?._id);
 
8
  });
9
 
10
  if (!assistant) {
11
+ error(404, "No assistant found");
12
  }
13
 
14
  if (!assistant.avatar) {
15
+ error(404, "No avatar found");
16
  }
17
 
18
  const fileId = collections.bucket.find({ filename: assistant._id.toString() });
19
 
20
  const content = await fileId.next().then(async (file) => {
21
  if (!file?._id) {
22
+ error(404, "Avatar not found");
23
  }
24
 
25
  const fileStream = collections.bucket.openDownloadStream(file?._id);
src/routes/settings/(nav)/assistants/[assistantId]/edit/+page.server.ts CHANGED
@@ -168,7 +168,7 @@ export const actions: Actions = {
168
  );
169
 
170
  if (acknowledged) {
171
- throw redirect(302, `${base}/settings/assistants/${assistant._id}`);
172
  } else {
173
  throw Error("Update failed");
174
  }
 
168
  );
169
 
170
  if (acknowledged) {
171
+ redirect(302, `${base}/settings/assistants/${assistant._id}`);
172
  } else {
173
  throw Error("Update failed");
174
  }
src/routes/settings/(nav)/assistants/new/+page.server.ts CHANGED
@@ -154,6 +154,6 @@ export const actions: Actions = {
154
  $addToSet: { assistants: insertedId },
155
  });
156
 
157
- throw redirect(302, `${base}/settings/assistants/${insertedId}`);
158
  },
159
  };
 
154
  $addToSet: { assistants: insertedId },
155
  });
156
 
157
+ redirect(302, `${base}/settings/assistants/${insertedId}`);
158
  },
159
  };
svelte.config.js CHANGED
@@ -1,5 +1,5 @@
1
  import adapter from "@sveltejs/adapter-node";
2
- import { vitePreprocess } from "@sveltejs/kit/vite";
3
  import dotenv from "dotenv";
4
 
5
  dotenv.config({ path: "./.env.local" });
 
1
  import adapter from "@sveltejs/adapter-node";
2
+ import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
3
  import dotenv from "dotenv";
4
 
5
  dotenv.config({ path: "./.env.local" });