Spaces:
Runtime error
Runtime error
refactor: api cleanup (#1849)
Browse files- package-lock.json +43 -0
- package.json +1 -0
- src/lib/APIClient.ts +12 -15
- src/lib/components/AssistantSettings.svelte +16 -3
- src/lib/components/NavConversationItem.svelte +3 -3
- src/lib/components/NavMenu.svelte +3 -9
- src/lib/components/ToolBadge.svelte +2 -2
- src/lib/components/chat/AssistantIntroduction.svelte +1 -2
- src/lib/components/chat/ChatInput.svelte +1 -2
- src/lib/components/chat/ChatWindow.svelte +1 -2
- src/lib/components/chat/Search.svelte +11 -17
- src/lib/server/api/index.ts +5 -0
- src/lib/server/api/routes/groups/assistants.ts +1 -12
- src/lib/server/api/routes/groups/conversations.ts +254 -8
- src/lib/server/api/routes/groups/tools.ts +3 -14
- src/lib/server/api/routes/groups/user.ts +1 -3
- src/lib/types/ConvSidebar.ts +4 -2
- src/lib/utils/fetchJSON.ts +2 -4
- src/lib/utils/serialize.ts +0 -13
- src/routes/+layout.ts +14 -16
- src/routes/api/conversations/search/+server.ts +0 -240
- src/routes/assistant/[assistantId]/+page.ts +2 -7
- src/routes/assistants/+page.ts +2 -2
- src/routes/conversation/[id]/+page.ts +2 -2
- src/routes/settings/(nav)/+layout.svelte +2 -2
- src/routes/settings/(nav)/assistants/[assistantId]/edit/+page.svelte +1 -10
- src/routes/settings/+layout.ts +5 -3
- src/routes/tools/+page.ts +2 -2
- src/routes/tools/ToolEdit.svelte +3 -3
- src/routes/tools/[toolId]/+layout.ts +2 -4
package-lock.json
CHANGED
|
@@ -104,6 +104,7 @@
|
|
| 104 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
| 105 |
"prom-client": "^15.1.2",
|
| 106 |
"sade": "^1.8.1",
|
|
|
|
| 107 |
"svelte": "^5.33.3",
|
| 108 |
"svelte-check": "^4.0.0",
|
| 109 |
"svelte-gestures": "^5.1.3",
|
|
@@ -8601,6 +8602,22 @@
|
|
| 8601 |
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
| 8602 |
"license": "MIT"
|
| 8603 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8604 |
"node_modules/cors": {
|
| 8605 |
"version": "2.8.5",
|
| 8606 |
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
|
@@ -11625,6 +11642,19 @@
|
|
| 11625 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 11626 |
}
|
| 11627 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11628 |
"node_modules/isexe": {
|
| 11629 |
"version": "2.0.0",
|
| 11630 |
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
|
@@ -17001,6 +17031,19 @@
|
|
| 17001 |
"node": ">=16 || 14 >=14.17"
|
| 17002 |
}
|
| 17003 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17004 |
"node_modules/supports-color": {
|
| 17005 |
"version": "7.2.0",
|
| 17006 |
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
|
|
|
| 104 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
| 105 |
"prom-client": "^15.1.2",
|
| 106 |
"sade": "^1.8.1",
|
| 107 |
+
"superjson": "^2.2.2",
|
| 108 |
"svelte": "^5.33.3",
|
| 109 |
"svelte-check": "^4.0.0",
|
| 110 |
"svelte-gestures": "^5.1.3",
|
|
|
|
| 8602 |
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
| 8603 |
"license": "MIT"
|
| 8604 |
},
|
| 8605 |
+
"node_modules/copy-anything": {
|
| 8606 |
+
"version": "3.0.5",
|
| 8607 |
+
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
|
| 8608 |
+
"integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
|
| 8609 |
+
"dev": true,
|
| 8610 |
+
"license": "MIT",
|
| 8611 |
+
"dependencies": {
|
| 8612 |
+
"is-what": "^4.1.8"
|
| 8613 |
+
},
|
| 8614 |
+
"engines": {
|
| 8615 |
+
"node": ">=12.13"
|
| 8616 |
+
},
|
| 8617 |
+
"funding": {
|
| 8618 |
+
"url": "https://github.com/sponsors/mesqueeb"
|
| 8619 |
+
}
|
| 8620 |
+
},
|
| 8621 |
"node_modules/cors": {
|
| 8622 |
"version": "2.8.5",
|
| 8623 |
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
|
|
|
| 11642 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 11643 |
}
|
| 11644 |
},
|
| 11645 |
+
"node_modules/is-what": {
|
| 11646 |
+
"version": "4.1.16",
|
| 11647 |
+
"resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
|
| 11648 |
+
"integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
|
| 11649 |
+
"dev": true,
|
| 11650 |
+
"license": "MIT",
|
| 11651 |
+
"engines": {
|
| 11652 |
+
"node": ">=12.13"
|
| 11653 |
+
},
|
| 11654 |
+
"funding": {
|
| 11655 |
+
"url": "https://github.com/sponsors/mesqueeb"
|
| 11656 |
+
}
|
| 11657 |
+
},
|
| 11658 |
"node_modules/isexe": {
|
| 11659 |
"version": "2.0.0",
|
| 11660 |
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
|
|
|
| 17031 |
"node": ">=16 || 14 >=14.17"
|
| 17032 |
}
|
| 17033 |
},
|
| 17034 |
+
"node_modules/superjson": {
|
| 17035 |
+
"version": "2.2.2",
|
| 17036 |
+
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
|
| 17037 |
+
"integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
|
| 17038 |
+
"dev": true,
|
| 17039 |
+
"license": "MIT",
|
| 17040 |
+
"dependencies": {
|
| 17041 |
+
"copy-anything": "^3.0.2"
|
| 17042 |
+
},
|
| 17043 |
+
"engines": {
|
| 17044 |
+
"node": ">=16"
|
| 17045 |
+
}
|
| 17046 |
+
},
|
| 17047 |
"node_modules/supports-color": {
|
| 17048 |
"version": "7.2.0",
|
| 17049 |
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
package.json
CHANGED
|
@@ -60,6 +60,7 @@
|
|
| 60 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
| 61 |
"prom-client": "^15.1.2",
|
| 62 |
"sade": "^1.8.1",
|
|
|
|
| 63 |
"svelte": "^5.33.3",
|
| 64 |
"svelte-check": "^4.0.0",
|
| 65 |
"svelte-gestures": "^5.1.3",
|
|
|
|
| 60 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
| 61 |
"prom-client": "^15.1.2",
|
| 62 |
"sade": "^1.8.1",
|
| 63 |
+
"superjson": "^2.2.2",
|
| 64 |
"svelte": "^5.33.3",
|
| 65 |
"svelte-check": "^4.0.0",
|
| 66 |
"svelte-gestures": "^5.1.3",
|
src/lib/APIClient.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { App } from "$api";
|
|
| 2 |
import { base } from "$app/paths";
|
| 3 |
import { treaty, type Treaty } from "@elysiajs/eden";
|
| 4 |
import { browser } from "$app/environment";
|
|
|
|
| 5 |
|
| 6 |
export function useAPIClient({ fetch }: { fetch?: Treaty.Config["fetcher"] } = {}) {
|
| 7 |
let url;
|
|
@@ -26,30 +27,26 @@ export function useAPIClient({ fetch }: { fetch?: Treaty.Config["fetcher"] } = {
|
|
| 26 |
url = `${window.location.origin}${base}/api/v2`;
|
| 27 |
}
|
| 28 |
const app = treaty<App>(url, { fetcher: fetch });
|
| 29 |
-
|
| 30 |
return app;
|
| 31 |
}
|
| 32 |
|
| 33 |
-
export function
|
| 34 |
response: Treaty.TreatyResponse<T>
|
| 35 |
): T[200] {
|
| 36 |
if (response.error) {
|
| 37 |
throw new Error(JSON.stringify(response.error));
|
| 38 |
}
|
| 39 |
|
| 40 |
-
return
|
|
|
|
|
|
|
| 41 |
}
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
if (response.data === null) {
|
| 51 |
-
throw new Error("No data received on API call");
|
| 52 |
}
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
}
|
|
|
|
| 2 |
import { base } from "$app/paths";
|
| 3 |
import { treaty, type Treaty } from "@elysiajs/eden";
|
| 4 |
import { browser } from "$app/environment";
|
| 5 |
+
import superjson from "superjson";
|
| 6 |
|
| 7 |
export function useAPIClient({ fetch }: { fetch?: Treaty.Config["fetcher"] } = {}) {
|
| 8 |
let url;
|
|
|
|
| 27 |
url = `${window.location.origin}${base}/api/v2`;
|
| 28 |
}
|
| 29 |
const app = treaty<App>(url, { fetcher: fetch });
|
|
|
|
| 30 |
return app;
|
| 31 |
}
|
| 32 |
|
| 33 |
+
export function handleResponse<T extends Record<number, unknown>>(
|
| 34 |
response: Treaty.TreatyResponse<T>
|
| 35 |
): T[200] {
|
| 36 |
if (response.error) {
|
| 37 |
throw new Error(JSON.stringify(response.error));
|
| 38 |
}
|
| 39 |
|
| 40 |
+
return superjson.parse(
|
| 41 |
+
typeof response.data === "string" ? response.data : JSON.stringify(response.data)
|
| 42 |
+
) as T[200];
|
| 43 |
}
|
| 44 |
|
| 45 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 46 |
+
export type Success<T extends (...args: any) => any> =
|
| 47 |
+
Awaited<ReturnType<T>> extends {
|
| 48 |
+
data: infer D;
|
| 49 |
+
error: unknown;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
+
? D
|
| 52 |
+
: never;
|
|
|
src/lib/components/AssistantSettings.svelte
CHANGED
|
@@ -55,8 +55,8 @@
|
|
| 55 |
let inputMessage3 = $state(assistant?.exampleInputs[2] ?? "");
|
| 56 |
let inputMessage4 = $state(assistant?.exampleInputs[3] ?? "");
|
| 57 |
|
| 58 |
-
function
|
| 59 |
-
errors =
|
| 60 |
}
|
| 61 |
|
| 62 |
function onFilesChange(e: Event) {
|
|
@@ -70,7 +70,7 @@
|
|
| 70 |
return;
|
| 71 |
}
|
| 72 |
files = inputEl.files;
|
| 73 |
-
|
| 74 |
deleteExistingAvatar = false;
|
| 75 |
}
|
| 76 |
}
|
|
@@ -164,6 +164,7 @@
|
|
| 164 |
} else {
|
| 165 |
$error = response.statusText;
|
| 166 |
}
|
|
|
|
| 167 |
}
|
| 168 |
} else {
|
| 169 |
response = await fetch(`${base}/api/assistant`, {
|
|
@@ -181,6 +182,7 @@
|
|
| 181 |
} else {
|
| 182 |
$error = response.statusText;
|
| 183 |
}
|
|
|
|
| 184 |
}
|
| 185 |
}
|
| 186 |
}}
|
|
@@ -245,6 +247,7 @@
|
|
| 245 |
e.stopPropagation();
|
| 246 |
files = null;
|
| 247 |
deleteExistingAvatar = true;
|
|
|
|
| 248 |
}}
|
| 249 |
class="mx-auto w-max text-center text-xs text-gray-600 hover:underline"
|
| 250 |
>
|
|
@@ -271,6 +274,7 @@
|
|
| 271 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 272 |
placeholder="Assistant Name"
|
| 273 |
value={assistant?.name ?? ""}
|
|
|
|
| 274 |
/>
|
| 275 |
<p class="text-xs text-red-500">{getError("name")}</p>
|
| 276 |
</label>
|
|
@@ -282,6 +286,7 @@
|
|
| 282 |
class="h-15 w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 283 |
placeholder="It knows everything about python"
|
| 284 |
value={assistant?.description ?? ""}
|
|
|
|
| 285 |
></textarea>
|
| 286 |
<p class="text-xs text-red-500">{getError("description")}</p>
|
| 287 |
</label>
|
|
@@ -293,6 +298,7 @@
|
|
| 293 |
name="modelId"
|
| 294 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 295 |
bind:value={modelId}
|
|
|
|
| 296 |
>
|
| 297 |
{#each models.filter((model) => !model.unlisted) as model}
|
| 298 |
<option value={model.id}>{model.displayName}</option>
|
|
@@ -415,12 +421,14 @@
|
|
| 415 |
placeholder="Start Message 1"
|
| 416 |
bind:value={inputMessage1}
|
| 417 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
|
|
|
| 418 |
/>
|
| 419 |
<input
|
| 420 |
name="exampleInput2"
|
| 421 |
placeholder="Start Message 2"
|
| 422 |
bind:value={inputMessage2}
|
| 423 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
|
|
|
| 424 |
/>
|
| 425 |
|
| 426 |
<input
|
|
@@ -428,12 +436,14 @@
|
|
| 428 |
placeholder="Start Message 3"
|
| 429 |
bind:value={inputMessage3}
|
| 430 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
|
|
|
| 431 |
/>
|
| 432 |
<input
|
| 433 |
name="exampleInput4"
|
| 434 |
placeholder="Start Message 4"
|
| 435 |
bind:value={inputMessage4}
|
| 436 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
|
|
|
| 437 |
/>
|
| 438 |
</div>
|
| 439 |
<p class="text-xs text-red-500">{getError("inputMessage1")}</p>
|
|
@@ -524,6 +534,7 @@
|
|
| 524 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 525 |
placeholder="wikipedia.org,bbc.com"
|
| 526 |
value={assistant?.rag?.allowedDomains?.join(",") ?? ""}
|
|
|
|
| 527 |
/>
|
| 528 |
<p class="text-xs text-red-500">{getError("ragDomainList")}</p>
|
| 529 |
{/if}
|
|
@@ -550,6 +561,7 @@
|
|
| 550 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 551 |
placeholder="https://raw.githubusercontent.com/huggingface/chat-ui/main/README.md"
|
| 552 |
value={assistant?.rag?.allowedLinks.join(",") ?? ""}
|
|
|
|
| 553 |
/>
|
| 554 |
<p class="text-xs text-red-500">{getError("ragLinkList")}</p>
|
| 555 |
{/if}
|
|
@@ -605,6 +617,7 @@
|
|
| 605 |
class="min-h-[8lh] flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
|
| 606 |
placeholder="You'll act as..."
|
| 607 |
bind:value={systemPrompt}
|
|
|
|
| 608 |
></textarea>
|
| 609 |
{#if modelId}
|
| 610 |
{@const model = models.find((_model) => _model.id === modelId)}
|
|
|
|
| 55 |
let inputMessage3 = $state(assistant?.exampleInputs[2] ?? "");
|
| 56 |
let inputMessage4 = $state(assistant?.exampleInputs[3] ?? "");
|
| 57 |
|
| 58 |
+
function clearError(field: string) {
|
| 59 |
+
errors = errors.filter((e) => e.field !== field);
|
| 60 |
}
|
| 61 |
|
| 62 |
function onFilesChange(e: Event) {
|
|
|
|
| 70 |
return;
|
| 71 |
}
|
| 72 |
files = inputEl.files;
|
| 73 |
+
clearError("avatar");
|
| 74 |
deleteExistingAvatar = false;
|
| 75 |
}
|
| 76 |
}
|
|
|
|
| 164 |
} else {
|
| 165 |
$error = response.statusText;
|
| 166 |
}
|
| 167 |
+
loading = false;
|
| 168 |
}
|
| 169 |
} else {
|
| 170 |
response = await fetch(`${base}/api/assistant`, {
|
|
|
|
| 182 |
} else {
|
| 183 |
$error = response.statusText;
|
| 184 |
}
|
| 185 |
+
loading = false;
|
| 186 |
}
|
| 187 |
}
|
| 188 |
}}
|
|
|
|
| 247 |
e.stopPropagation();
|
| 248 |
files = null;
|
| 249 |
deleteExistingAvatar = true;
|
| 250 |
+
clearError("avatar");
|
| 251 |
}}
|
| 252 |
class="mx-auto w-max text-center text-xs text-gray-600 hover:underline"
|
| 253 |
>
|
|
|
|
| 274 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 275 |
placeholder="Assistant Name"
|
| 276 |
value={assistant?.name ?? ""}
|
| 277 |
+
oninput={() => clearError("name")}
|
| 278 |
/>
|
| 279 |
<p class="text-xs text-red-500">{getError("name")}</p>
|
| 280 |
</label>
|
|
|
|
| 286 |
class="h-15 w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 287 |
placeholder="It knows everything about python"
|
| 288 |
value={assistant?.description ?? ""}
|
| 289 |
+
oninput={() => clearError("description")}
|
| 290 |
></textarea>
|
| 291 |
<p class="text-xs text-red-500">{getError("description")}</p>
|
| 292 |
</label>
|
|
|
|
| 298 |
name="modelId"
|
| 299 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 300 |
bind:value={modelId}
|
| 301 |
+
onchange={() => clearError("modelId")}
|
| 302 |
>
|
| 303 |
{#each models.filter((model) => !model.unlisted) as model}
|
| 304 |
<option value={model.id}>{model.displayName}</option>
|
|
|
|
| 421 |
placeholder="Start Message 1"
|
| 422 |
bind:value={inputMessage1}
|
| 423 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 424 |
+
oninput={() => clearError("inputMessage1")}
|
| 425 |
/>
|
| 426 |
<input
|
| 427 |
name="exampleInput2"
|
| 428 |
placeholder="Start Message 2"
|
| 429 |
bind:value={inputMessage2}
|
| 430 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 431 |
+
oninput={() => clearError("inputMessage1")}
|
| 432 |
/>
|
| 433 |
|
| 434 |
<input
|
|
|
|
| 436 |
placeholder="Start Message 3"
|
| 437 |
bind:value={inputMessage3}
|
| 438 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 439 |
+
oninput={() => clearError("inputMessage1")}
|
| 440 |
/>
|
| 441 |
<input
|
| 442 |
name="exampleInput4"
|
| 443 |
placeholder="Start Message 4"
|
| 444 |
bind:value={inputMessage4}
|
| 445 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 446 |
+
oninput={() => clearError("inputMessage1")}
|
| 447 |
/>
|
| 448 |
</div>
|
| 449 |
<p class="text-xs text-red-500">{getError("inputMessage1")}</p>
|
|
|
|
| 534 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 535 |
placeholder="wikipedia.org,bbc.com"
|
| 536 |
value={assistant?.rag?.allowedDomains?.join(",") ?? ""}
|
| 537 |
+
oninput={() => clearError("ragDomainList")}
|
| 538 |
/>
|
| 539 |
<p class="text-xs text-red-500">{getError("ragDomainList")}</p>
|
| 540 |
{/if}
|
|
|
|
| 561 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
| 562 |
placeholder="https://raw.githubusercontent.com/huggingface/chat-ui/main/README.md"
|
| 563 |
value={assistant?.rag?.allowedLinks.join(",") ?? ""}
|
| 564 |
+
oninput={() => clearError("ragLinkList")}
|
| 565 |
/>
|
| 566 |
<p class="text-xs text-red-500">{getError("ragLinkList")}</p>
|
| 567 |
{/if}
|
|
|
|
| 617 |
class="min-h-[8lh] flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
|
| 618 |
placeholder="You'll act as..."
|
| 619 |
bind:value={systemPrompt}
|
| 620 |
+
oninput={() => clearError("preprompt")}
|
| 621 |
></textarea>
|
| 622 |
{#if modelId}
|
| 623 |
{@const model = models.find((_model) => _model.id === modelId)}
|
src/lib/components/NavConversationItem.svelte
CHANGED
|
@@ -93,7 +93,7 @@
|
|
| 93 |
onclick={(e) => {
|
| 94 |
e.preventDefault();
|
| 95 |
confirmDelete = false;
|
| 96 |
-
dispatch("deleteConversation", conv.id);
|
| 97 |
}}
|
| 98 |
>
|
| 99 |
<CarbonCheckmark
|
|
@@ -109,7 +109,7 @@
|
|
| 109 |
e.preventDefault();
|
| 110 |
const newTitle = prompt("Edit this conversation title:", conv.title);
|
| 111 |
if (!newTitle) return;
|
| 112 |
-
dispatch("editConversationTitle", { id: conv.id, title: newTitle });
|
| 113 |
}}
|
| 114 |
>
|
| 115 |
<CarbonEdit class="text-xs text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" />
|
|
@@ -122,7 +122,7 @@
|
|
| 122 |
onclick={(event) => {
|
| 123 |
event.preventDefault();
|
| 124 |
if (event.shiftKey) {
|
| 125 |
-
dispatch("deleteConversation", conv.id);
|
| 126 |
} else {
|
| 127 |
confirmDelete = true;
|
| 128 |
}
|
|
|
|
| 93 |
onclick={(e) => {
|
| 94 |
e.preventDefault();
|
| 95 |
confirmDelete = false;
|
| 96 |
+
dispatch("deleteConversation", conv.id.toString());
|
| 97 |
}}
|
| 98 |
>
|
| 99 |
<CarbonCheckmark
|
|
|
|
| 109 |
e.preventDefault();
|
| 110 |
const newTitle = prompt("Edit this conversation title:", conv.title);
|
| 111 |
if (!newTitle) return;
|
| 112 |
+
dispatch("editConversationTitle", { id: conv.id.toString(), title: newTitle });
|
| 113 |
}}
|
| 114 |
>
|
| 115 |
<CarbonEdit class="text-xs text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" />
|
|
|
|
| 122 |
onclick={(event) => {
|
| 123 |
event.preventDefault();
|
| 124 |
if (event.shiftKey) {
|
| 125 |
+
dispatch("deleteConversation", conv.id.toString());
|
| 126 |
} else {
|
| 127 |
confirmDelete = true;
|
| 128 |
}
|
src/lib/components/NavMenu.svelte
CHANGED
|
@@ -29,8 +29,7 @@
|
|
| 29 |
import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";
|
| 30 |
|
| 31 |
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
| 32 |
-
import { useAPIClient,
|
| 33 |
-
import { jsonSerialize } from "$lib/utils/serialize";
|
| 34 |
|
| 35 |
const publicConfig = usePublicConfig();
|
| 36 |
const client = useAPIClient();
|
|
@@ -77,13 +76,8 @@
|
|
| 77 |
p,
|
| 78 |
},
|
| 79 |
})
|
| 80 |
-
.then(
|
| 81 |
-
.then((
|
| 82 |
-
conversations.map((conv) => ({
|
| 83 |
-
...jsonSerialize(conv),
|
| 84 |
-
updatedAt: new Date(conv.updatedAt),
|
| 85 |
-
}))
|
| 86 |
-
)
|
| 87 |
.catch(() => []);
|
| 88 |
|
| 89 |
if (newConvs.length === 0) {
|
|
|
|
| 29 |
import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";
|
| 30 |
|
| 31 |
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
| 32 |
+
import { useAPIClient, handleResponse } from "$lib/APIClient";
|
|
|
|
| 33 |
|
| 34 |
const publicConfig = usePublicConfig();
|
| 35 |
const client = useAPIClient();
|
|
|
|
| 76 |
p,
|
| 77 |
},
|
| 78 |
})
|
| 79 |
+
.then(handleResponse)
|
| 80 |
+
.then((r) => r.conversations)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
.catch(() => []);
|
| 82 |
|
| 83 |
if (newConvs.length === 0) {
|
src/lib/components/ToolBadge.svelte
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import ToolLogo from "./ToolLogo.svelte";
|
| 3 |
import { base } from "$app/paths";
|
| 4 |
import { browser } from "$app/environment";
|
| 5 |
-
import {
|
| 6 |
|
| 7 |
interface Props {
|
| 8 |
toolId: string;
|
|
@@ -17,7 +17,7 @@
|
|
| 17 |
class="relative flex items-center justify-center space-x-2 rounded border border-gray-300 bg-gray-200 px-2 py-1"
|
| 18 |
>
|
| 19 |
{#if browser}
|
| 20 |
-
{#await client.tools({ id: toolId }).get().then(
|
| 21 |
{#key value.color + value.icon}
|
| 22 |
<ToolLogo color={value.color} icon={value.icon} size="sm" />
|
| 23 |
{/key}
|
|
|
|
| 2 |
import ToolLogo from "./ToolLogo.svelte";
|
| 3 |
import { base } from "$app/paths";
|
| 4 |
import { browser } from "$app/environment";
|
| 5 |
+
import { handleResponse, useAPIClient } from "$lib/APIClient";
|
| 6 |
|
| 7 |
interface Props {
|
| 8 |
toolId: string;
|
|
|
|
| 17 |
class="relative flex items-center justify-center space-x-2 rounded border border-gray-300 bg-gray-200 px-2 py-1"
|
| 18 |
>
|
| 19 |
{#if browser}
|
| 20 |
+
{#await client.tools({ id: toolId }).get().then(handleResponse) then value}
|
| 21 |
{#key value.color + value.icon}
|
| 22 |
<ToolLogo color={value.color} icon={value.icon} size="sm" />
|
| 23 |
{/key}
|
src/lib/components/chat/AssistantIntroduction.svelte
CHANGED
|
@@ -18,14 +18,13 @@
|
|
| 18 |
import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";
|
| 19 |
|
| 20 |
import { page } from "$app/state";
|
| 21 |
-
import type { Serialize } from "$lib/utils/serialize";
|
| 22 |
|
| 23 |
const publicConfig = usePublicConfig();
|
| 24 |
|
| 25 |
interface Props {
|
| 26 |
models: Model[];
|
| 27 |
assistant: Pick<
|
| 28 |
-
|
| 29 |
| "avatar"
|
| 30 |
| "name"
|
| 31 |
| "rag"
|
|
|
|
| 18 |
import { usePublicConfig } from "$lib/utils/PublicConfig.svelte";
|
| 19 |
|
| 20 |
import { page } from "$app/state";
|
|
|
|
| 21 |
|
| 22 |
const publicConfig = usePublicConfig();
|
| 23 |
|
| 24 |
interface Props {
|
| 25 |
models: Model[];
|
| 26 |
assistant: Pick<
|
| 27 |
+
Assistant,
|
| 28 |
| "avatar"
|
| 29 |
| "name"
|
| 30 |
| "rag"
|
src/lib/components/chat/ChatInput.svelte
CHANGED
|
@@ -23,7 +23,6 @@
|
|
| 23 |
import { captureScreen } from "$lib/utils/screenshot";
|
| 24 |
import IconScreenshot from "../icons/IconScreenshot.svelte";
|
| 25 |
import { loginModalOpen } from "$lib/stores/loginModal";
|
| 26 |
-
import type { Serialize } from "$lib/utils/serialize";
|
| 27 |
|
| 28 |
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
| 29 |
interface Props {
|
|
@@ -33,7 +32,7 @@
|
|
| 33 |
placeholder?: string;
|
| 34 |
loading?: boolean;
|
| 35 |
disabled?: boolean;
|
| 36 |
-
assistant?:
|
| 37 |
modelHasTools?: boolean;
|
| 38 |
modelIsMultimodal?: boolean;
|
| 39 |
children?: import("svelte").Snippet;
|
|
|
|
| 23 |
import { captureScreen } from "$lib/utils/screenshot";
|
| 24 |
import IconScreenshot from "../icons/IconScreenshot.svelte";
|
| 25 |
import { loginModalOpen } from "$lib/stores/loginModal";
|
|
|
|
| 26 |
|
| 27 |
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
| 28 |
interface Props {
|
|
|
|
| 32 |
placeholder?: string;
|
| 33 |
loading?: boolean;
|
| 34 |
disabled?: boolean;
|
| 35 |
+
assistant?: Assistant | undefined;
|
| 36 |
modelHasTools?: boolean;
|
| 37 |
modelIsMultimodal?: boolean;
|
| 38 |
children?: import("svelte").Snippet;
|
src/lib/components/chat/ChatWindow.svelte
CHANGED
|
@@ -37,7 +37,6 @@
|
|
| 37 |
import { cubicInOut } from "svelte/easing";
|
| 38 |
import type { ToolFront } from "$lib/types/Tool";
|
| 39 |
import { loginModalOpen } from "$lib/stores/loginModal";
|
| 40 |
-
import type { Serialize } from "$lib/utils/serialize";
|
| 41 |
import { beforeNavigate } from "$app/navigation";
|
| 42 |
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
| 43 |
|
|
@@ -49,7 +48,7 @@
|
|
| 49 |
shared?: boolean;
|
| 50 |
currentModel: Model;
|
| 51 |
models: Model[];
|
| 52 |
-
assistant?:
|
| 53 |
preprompt?: string | undefined;
|
| 54 |
files?: File[];
|
| 55 |
}
|
|
|
|
| 37 |
import { cubicInOut } from "svelte/easing";
|
| 38 |
import type { ToolFront } from "$lib/types/Tool";
|
| 39 |
import { loginModalOpen } from "$lib/stores/loginModal";
|
|
|
|
| 40 |
import { beforeNavigate } from "$app/navigation";
|
| 41 |
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
| 42 |
|
|
|
|
| 48 |
shared?: boolean;
|
| 49 |
currentModel: Model;
|
| 50 |
models: Model[];
|
| 51 |
+
assistant?: Assistant | undefined;
|
| 52 |
preprompt?: string | undefined;
|
| 53 |
files?: File[];
|
| 54 |
}
|
src/lib/components/chat/Search.svelte
CHANGED
|
@@ -7,11 +7,7 @@
|
|
| 7 |
</script>
|
| 8 |
|
| 9 |
<script lang="ts">
|
| 10 |
-
import { base } from "$app/paths";
|
| 11 |
-
|
| 12 |
import { debounce } from "$lib/utils/debounce";
|
| 13 |
-
|
| 14 |
-
import type { GETSearchEndpointReturn } from "../../../routes/api/conversations/search/+server";
|
| 15 |
import NavConversationItem from "../NavConversationItem.svelte";
|
| 16 |
import { titles } from "../NavMenu.svelte";
|
| 17 |
import { beforeNavigate } from "$app/navigation";
|
|
@@ -19,6 +15,9 @@
|
|
| 19 |
import CarbonClose from "~icons/carbon/close";
|
| 20 |
import { fly } from "svelte/transition";
|
| 21 |
import InfiniteScroll from "../InfiniteScroll.svelte";
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
let searchContainer: HTMLDivElement | undefined = $state(undefined);
|
| 24 |
let inputElement: HTMLInputElement | undefined = $state(undefined);
|
|
@@ -29,7 +28,7 @@
|
|
| 29 |
|
| 30 |
let pending: boolean = $state(false);
|
| 31 |
|
| 32 |
-
let conversations:
|
| 33 |
|
| 34 |
let page: number = $state(0);
|
| 35 |
|
|
@@ -75,19 +74,14 @@
|
|
| 75 |
};
|
| 76 |
|
| 77 |
async function handleVisible(v: string) {
|
| 78 |
-
const newConvs = await
|
| 79 |
-
.
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
updatedAt: new Date(conv.updatedAt),
|
| 85 |
-
}))
|
| 86 |
-
);
|
| 87 |
-
} else {
|
| 88 |
-
return [];
|
| 89 |
-
}
|
| 90 |
})
|
|
|
|
| 91 |
.catch(() => []);
|
| 92 |
|
| 93 |
if (newConvs.length === 0) {
|
|
|
|
| 7 |
</script>
|
| 8 |
|
| 9 |
<script lang="ts">
|
|
|
|
|
|
|
| 10 |
import { debounce } from "$lib/utils/debounce";
|
|
|
|
|
|
|
| 11 |
import NavConversationItem from "../NavConversationItem.svelte";
|
| 12 |
import { titles } from "../NavMenu.svelte";
|
| 13 |
import { beforeNavigate } from "$app/navigation";
|
|
|
|
| 15 |
import CarbonClose from "~icons/carbon/close";
|
| 16 |
import { fly } from "svelte/transition";
|
| 17 |
import InfiniteScroll from "../InfiniteScroll.svelte";
|
| 18 |
+
import { handleResponse, useAPIClient, type Success } from "$lib/APIClient";
|
| 19 |
+
|
| 20 |
+
const client = useAPIClient();
|
| 21 |
|
| 22 |
let searchContainer: HTMLDivElement | undefined = $state(undefined);
|
| 23 |
let inputElement: HTMLInputElement | undefined = $state(undefined);
|
|
|
|
| 28 |
|
| 29 |
let pending: boolean = $state(false);
|
| 30 |
|
| 31 |
+
let conversations: NonNullable<Success<typeof client.conversations.search.get>> = $state([]);
|
| 32 |
|
| 33 |
let page: number = $state(0);
|
| 34 |
|
|
|
|
| 74 |
};
|
| 75 |
|
| 76 |
async function handleVisible(v: string) {
|
| 77 |
+
const newConvs = await client.conversations.search
|
| 78 |
+
.get({
|
| 79 |
+
query: {
|
| 80 |
+
q: v,
|
| 81 |
+
p: page++,
|
| 82 |
+
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
})
|
| 84 |
+
.then(handleResponse)
|
| 85 |
.catch(() => []);
|
| 86 |
|
| 87 |
if (newConvs.length === 0) {
|
src/lib/server/api/index.ts
CHANGED
|
@@ -11,9 +11,14 @@ import { base } from "$app/paths";
|
|
| 11 |
import { swagger } from "@elysiajs/swagger";
|
| 12 |
import { config } from "$lib/server/config";
|
| 13 |
|
|
|
|
|
|
|
| 14 |
const prefix = `${base}/api/v2` as unknown as "";
|
| 15 |
|
| 16 |
export const app = new Elysia({ prefix })
|
|
|
|
|
|
|
|
|
|
| 17 |
.use(
|
| 18 |
swagger({
|
| 19 |
documentation: {
|
|
|
|
| 11 |
import { swagger } from "@elysiajs/swagger";
|
| 12 |
import { config } from "$lib/server/config";
|
| 13 |
|
| 14 |
+
import superjson from "superjson";
|
| 15 |
+
|
| 16 |
const prefix = `${base}/api/v2` as unknown as "";
|
| 17 |
|
| 18 |
export const app = new Elysia({ prefix })
|
| 19 |
+
.mapResponse(({ response }) => {
|
| 20 |
+
return new Response(superjson.stringify(response));
|
| 21 |
+
})
|
| 22 |
.use(
|
| 23 |
swagger({
|
| 24 |
documentation: {
|
src/lib/server/api/routes/groups/assistants.ts
CHANGED
|
@@ -7,19 +7,8 @@ import { SortKey, type Assistant } from "$lib/types/Assistant";
|
|
| 7 |
import type { User } from "$lib/types/User";
|
| 8 |
import { ReviewStatus } from "$lib/types/Review";
|
| 9 |
import { generateQueryTokens } from "$lib/utils/searchTokens";
|
| 10 |
-
import { jsonSerialize, type Serialize } from "$lib/utils/serialize";
|
| 11 |
import { config } from "$lib/server/config";
|
| 12 |
|
| 13 |
-
export type GETAssistantsSearchResponse = {
|
| 14 |
-
assistants: Array<Serialize<Assistant>>;
|
| 15 |
-
selectedModel: string;
|
| 16 |
-
numTotalItems: number;
|
| 17 |
-
numItemsPerPage: number;
|
| 18 |
-
query: string | null;
|
| 19 |
-
sort: SortKey;
|
| 20 |
-
showUnfeatured: boolean;
|
| 21 |
-
};
|
| 22 |
-
|
| 23 |
const NUM_PER_PAGE = 24;
|
| 24 |
|
| 25 |
export const assistantGroup = new Elysia().use(authPlugin).group("/assistants", (app) => {
|
|
@@ -95,7 +84,7 @@ export const assistantGroup = new Elysia().use(authPlugin).group("/assistants",
|
|
| 95 |
const numTotalItems = await collections.assistants.countDocuments(filter);
|
| 96 |
|
| 97 |
return {
|
| 98 |
-
assistants
|
| 99 |
selectedModel: modelId ?? "",
|
| 100 |
numTotalItems,
|
| 101 |
numItemsPerPage: NUM_PER_PAGE,
|
|
|
|
| 7 |
import type { User } from "$lib/types/User";
|
| 8 |
import { ReviewStatus } from "$lib/types/Review";
|
| 9 |
import { generateQueryTokens } from "$lib/utils/searchTokens";
|
|
|
|
| 10 |
import { config } from "$lib/server/config";
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
const NUM_PER_PAGE = 24;
|
| 13 |
|
| 14 |
export const assistantGroup = new Elysia().use(authPlugin).group("/assistants", (app) => {
|
|
|
|
| 84 |
const numTotalItems = await collections.assistants.countDocuments(filter);
|
| 85 |
|
| 86 |
return {
|
| 87 |
+
assistants,
|
| 88 |
selectedModel: modelId ?? "",
|
| 89 |
numTotalItems,
|
| 90 |
numItemsPerPage: NUM_PER_PAGE,
|
src/lib/server/api/routes/groups/conversations.ts
CHANGED
|
@@ -6,8 +6,10 @@ import { authCondition } from "$lib/server/auth";
|
|
| 6 |
import { models } from "$lib/server/models";
|
| 7 |
import { convertLegacyConversation } from "$lib/utils/tree/convertLegacyConversation";
|
| 8 |
import type { Conversation } from "$lib/types/Conversation";
|
| 9 |
-
|
| 10 |
import { CONV_NUM_PER_PAGE } from "$lib/constants/pagination";
|
|
|
|
|
|
|
| 11 |
|
| 12 |
export const conversationGroup = new Elysia().use(authPlugin).group("/conversations", (app) => {
|
| 13 |
return app
|
|
@@ -64,6 +66,252 @@ export const conversationGroup = new Elysia().use(authPlugin).group("/conversati
|
|
| 64 |
});
|
| 65 |
return res.deletedCount;
|
| 66 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
.group(
|
| 68 |
"/:id",
|
| 69 |
{
|
|
@@ -126,18 +374,16 @@ export const conversationGroup = new Elysia().use(authPlugin).group("/conversati
|
|
| 126 |
return { conversation: convertedConv };
|
| 127 |
})
|
| 128 |
.get("", async ({ conversation }) => {
|
| 129 |
-
return
|
| 130 |
messages: conversation.messages,
|
| 131 |
title: conversation.title,
|
| 132 |
model: conversation.model,
|
| 133 |
preprompt: conversation.preprompt,
|
| 134 |
rootMessageId: conversation.rootMessageId,
|
| 135 |
assistant: conversation.assistantId
|
| 136 |
-
?
|
| 137 |
-
(
|
| 138 |
-
|
| 139 |
-
})) ?? undefined
|
| 140 |
-
)
|
| 141 |
: undefined,
|
| 142 |
id: conversation._id.toString(),
|
| 143 |
updatedAt: conversation.updatedAt,
|
|
@@ -145,7 +391,7 @@ export const conversationGroup = new Elysia().use(authPlugin).group("/conversati
|
|
| 145 |
assistantId: conversation.assistantId,
|
| 146 |
modelTools: models.find((m) => m.id == conversation.model)?.tools ?? false,
|
| 147 |
shared: conversation.shared,
|
| 148 |
-
}
|
| 149 |
})
|
| 150 |
.post("", () => {
|
| 151 |
// todo: post new message
|
|
|
|
| 6 |
import { models } from "$lib/server/models";
|
| 7 |
import { convertLegacyConversation } from "$lib/utils/tree/convertLegacyConversation";
|
| 8 |
import type { Conversation } from "$lib/types/Conversation";
|
| 9 |
+
|
| 10 |
import { CONV_NUM_PER_PAGE } from "$lib/constants/pagination";
|
| 11 |
+
import pkg from "natural";
|
| 12 |
+
const { PorterStemmer } = pkg;
|
| 13 |
|
| 14 |
export const conversationGroup = new Elysia().use(authPlugin).group("/conversations", (app) => {
|
| 15 |
return app
|
|
|
|
| 66 |
});
|
| 67 |
return res.deletedCount;
|
| 68 |
})
|
| 69 |
+
.get(
|
| 70 |
+
"/search",
|
| 71 |
+
async ({ locals, query }) => {
|
| 72 |
+
const searchQuery = query.q;
|
| 73 |
+
const p = query.p ?? 0;
|
| 74 |
+
|
| 75 |
+
if (!searchQuery || searchQuery.length < 3) {
|
| 76 |
+
return [];
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
if (!locals.user?._id && !locals.sessionId) {
|
| 80 |
+
throw new Error("Must have a valid session or user");
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
const convs = await collections.conversations
|
| 84 |
+
.find({
|
| 85 |
+
sessionId: undefined,
|
| 86 |
+
...authCondition(locals),
|
| 87 |
+
$text: { $search: searchQuery },
|
| 88 |
+
})
|
| 89 |
+
.sort({
|
| 90 |
+
updatedAt: -1, // Sort by date updated in descending order
|
| 91 |
+
})
|
| 92 |
+
.project<
|
| 93 |
+
Pick<
|
| 94 |
+
Conversation,
|
| 95 |
+
"_id" | "title" | "updatedAt" | "model" | "assistantId" | "messages" | "userId"
|
| 96 |
+
>
|
| 97 |
+
>({
|
| 98 |
+
title: 1,
|
| 99 |
+
updatedAt: 1,
|
| 100 |
+
model: 1,
|
| 101 |
+
assistantId: 1,
|
| 102 |
+
messages: 1,
|
| 103 |
+
userId: 1,
|
| 104 |
+
})
|
| 105 |
+
.skip(p * 5)
|
| 106 |
+
.limit(5)
|
| 107 |
+
.toArray()
|
| 108 |
+
.then((convs) =>
|
| 109 |
+
convs.map((conv) => {
|
| 110 |
+
let matchedContent = "";
|
| 111 |
+
let matchedText = "";
|
| 112 |
+
|
| 113 |
+
// Find the best match using stemming to handle MongoDB's text search behavior
|
| 114 |
+
let bestMatch = null;
|
| 115 |
+
let bestMatchLength = 0;
|
| 116 |
+
|
| 117 |
+
// Simple function to find the best match in content
|
| 118 |
+
const findBestMatch = (
|
| 119 |
+
content: string,
|
| 120 |
+
query: string
|
| 121 |
+
): { start: number; end: number; text: string } | null => {
|
| 122 |
+
const contentLower = content.toLowerCase();
|
| 123 |
+
const queryLower = query.toLowerCase();
|
| 124 |
+
|
| 125 |
+
// Try exact word boundary match first
|
| 126 |
+
const wordRegex = new RegExp(
|
| 127 |
+
`\\b${queryLower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`,
|
| 128 |
+
"gi"
|
| 129 |
+
);
|
| 130 |
+
const wordMatch = wordRegex.exec(content);
|
| 131 |
+
if (wordMatch) {
|
| 132 |
+
return {
|
| 133 |
+
start: wordMatch.index,
|
| 134 |
+
end: wordMatch.index + wordMatch[0].length - 1,
|
| 135 |
+
text: wordMatch[0],
|
| 136 |
+
};
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// Try simple substring match
|
| 140 |
+
const index = contentLower.indexOf(queryLower);
|
| 141 |
+
if (index !== -1) {
|
| 142 |
+
return {
|
| 143 |
+
start: index,
|
| 144 |
+
end: index + queryLower.length - 1,
|
| 145 |
+
text: content.substring(index, index + queryLower.length),
|
| 146 |
+
};
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
return null;
|
| 150 |
+
};
|
| 151 |
+
|
| 152 |
+
// Create search variations
|
| 153 |
+
const searchVariations = [searchQuery.toLowerCase()];
|
| 154 |
+
|
| 155 |
+
// Add stemmed variations
|
| 156 |
+
try {
|
| 157 |
+
const stemmed = PorterStemmer.stem(searchQuery.toLowerCase());
|
| 158 |
+
if (stemmed !== searchQuery.toLowerCase()) {
|
| 159 |
+
searchVariations.push(stemmed);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
// Find actual words in conversations that stem to the same root
|
| 163 |
+
for (const message of conv.messages) {
|
| 164 |
+
if (message.content) {
|
| 165 |
+
const words = message.content.toLowerCase().match(/\b\w+\b/g) || [];
|
| 166 |
+
words.forEach((word: string) => {
|
| 167 |
+
if (
|
| 168 |
+
PorterStemmer.stem(word) === stemmed &&
|
| 169 |
+
!searchVariations.includes(word)
|
| 170 |
+
) {
|
| 171 |
+
searchVariations.push(word);
|
| 172 |
+
}
|
| 173 |
+
});
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
} catch (e) {
|
| 177 |
+
console.warn("Stemming failed for:", searchQuery, e);
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
// Add simple variations
|
| 181 |
+
const query = searchQuery.toLowerCase();
|
| 182 |
+
if (query.endsWith("s") && query.length > 3) {
|
| 183 |
+
searchVariations.push(query.slice(0, -1));
|
| 184 |
+
} else if (!query.endsWith("s")) {
|
| 185 |
+
searchVariations.push(query + "s");
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
// Search through all messages for the best match
|
| 189 |
+
for (const message of conv.messages) {
|
| 190 |
+
if (!message.content) continue;
|
| 191 |
+
|
| 192 |
+
// Try each variation in order of preference
|
| 193 |
+
for (const variation of searchVariations) {
|
| 194 |
+
const match = findBestMatch(message.content, variation);
|
| 195 |
+
if (match) {
|
| 196 |
+
const isExactQuery = variation === searchQuery.toLowerCase();
|
| 197 |
+
const priority = isExactQuery ? 1000 : match.text.length;
|
| 198 |
+
|
| 199 |
+
if (priority > bestMatchLength) {
|
| 200 |
+
bestMatch = {
|
| 201 |
+
content: message.content,
|
| 202 |
+
matchStart: match.start,
|
| 203 |
+
matchEnd: match.end,
|
| 204 |
+
matchedText: match.text,
|
| 205 |
+
};
|
| 206 |
+
bestMatchLength = priority;
|
| 207 |
+
|
| 208 |
+
// If we found exact query match, we're done
|
| 209 |
+
if (isExactQuery) break;
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
// Stop if we found an exact match
|
| 215 |
+
if (bestMatchLength >= 1000) break;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
if (bestMatch) {
|
| 219 |
+
const { content, matchStart, matchEnd } = bestMatch;
|
| 220 |
+
matchedText = bestMatch.matchedText;
|
| 221 |
+
|
| 222 |
+
// Create centered context around the match
|
| 223 |
+
const maxContextLength = 160; // Maximum length of actual content (no padding)
|
| 224 |
+
const matchLength = matchEnd - matchStart + 1;
|
| 225 |
+
|
| 226 |
+
// Calculate context window - don't exceed maxContextLength even if content is longer
|
| 227 |
+
const availableForContext =
|
| 228 |
+
Math.min(maxContextLength, content.length) - matchLength;
|
| 229 |
+
const contextPerSide = Math.floor(availableForContext / 2);
|
| 230 |
+
|
| 231 |
+
// Calculate snippet boundaries to center the match within maxContextLength
|
| 232 |
+
let snippetStart = Math.max(0, matchStart - contextPerSide);
|
| 233 |
+
let snippetEnd = Math.min(
|
| 234 |
+
content.length,
|
| 235 |
+
matchStart + matchLength + contextPerSide
|
| 236 |
+
);
|
| 237 |
+
|
| 238 |
+
// Ensure we don't exceed maxContextLength
|
| 239 |
+
if (snippetEnd - snippetStart > maxContextLength) {
|
| 240 |
+
if (matchStart - contextPerSide < 0) {
|
| 241 |
+
// Match is near start, extend end but limit to maxContextLength
|
| 242 |
+
snippetEnd = Math.min(content.length, snippetStart + maxContextLength);
|
| 243 |
+
} else {
|
| 244 |
+
// Match is not near start, limit to maxContextLength from match start
|
| 245 |
+
snippetEnd = Math.min(content.length, snippetStart + maxContextLength);
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
// Adjust to word boundaries if possible (but don't move more than 15 chars)
|
| 250 |
+
const originalStart = snippetStart;
|
| 251 |
+
const originalEnd = snippetEnd;
|
| 252 |
+
|
| 253 |
+
while (
|
| 254 |
+
snippetStart > 0 &&
|
| 255 |
+
content[snippetStart] !== " " &&
|
| 256 |
+
content[snippetStart] !== "\n" &&
|
| 257 |
+
originalStart - snippetStart < 15
|
| 258 |
+
) {
|
| 259 |
+
snippetStart--;
|
| 260 |
+
}
|
| 261 |
+
while (
|
| 262 |
+
snippetEnd < content.length &&
|
| 263 |
+
content[snippetEnd] !== " " &&
|
| 264 |
+
content[snippetEnd] !== "\n" &&
|
| 265 |
+
snippetEnd - originalEnd < 15
|
| 266 |
+
) {
|
| 267 |
+
snippetEnd++;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
// Extract the content
|
| 271 |
+
let extractedContent = content.substring(snippetStart, snippetEnd).trim();
|
| 272 |
+
// Add ellipsis indicators only
|
| 273 |
+
if (snippetStart > 0) {
|
| 274 |
+
extractedContent = "..." + extractedContent;
|
| 275 |
+
}
|
| 276 |
+
if (snippetEnd < content.length) {
|
| 277 |
+
extractedContent = extractedContent + "...";
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
matchedContent = extractedContent;
|
| 281 |
+
} else {
|
| 282 |
+
// Fallback: use beginning of the first message if no match found
|
| 283 |
+
const firstMessage = conv.messages[0];
|
| 284 |
+
if (firstMessage?.content) {
|
| 285 |
+
const content = firstMessage.content;
|
| 286 |
+
matchedContent =
|
| 287 |
+
content.length > 200 ? content.substring(0, 200) + "..." : content;
|
| 288 |
+
matchedText = searchQuery; // Fallback to search query
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
return {
|
| 293 |
+
_id: conv._id,
|
| 294 |
+
id: conv._id,
|
| 295 |
+
title: conv.title,
|
| 296 |
+
content: matchedContent,
|
| 297 |
+
matchedText,
|
| 298 |
+
updatedAt: conv.updatedAt,
|
| 299 |
+
model: conv.model,
|
| 300 |
+
assistantId: conv.assistantId,
|
| 301 |
+
modelTools: models.find((m) => m.id == conv.model)?.tools ?? false,
|
| 302 |
+
};
|
| 303 |
+
})
|
| 304 |
+
);
|
| 305 |
+
|
| 306 |
+
return convs;
|
| 307 |
+
},
|
| 308 |
+
{
|
| 309 |
+
query: t.Object({
|
| 310 |
+
q: t.String(),
|
| 311 |
+
p: t.Optional(t.Number()),
|
| 312 |
+
}),
|
| 313 |
+
}
|
| 314 |
+
)
|
| 315 |
.group(
|
| 316 |
"/:id",
|
| 317 |
{
|
|
|
|
| 374 |
return { conversation: convertedConv };
|
| 375 |
})
|
| 376 |
.get("", async ({ conversation }) => {
|
| 377 |
+
return {
|
| 378 |
messages: conversation.messages,
|
| 379 |
title: conversation.title,
|
| 380 |
model: conversation.model,
|
| 381 |
preprompt: conversation.preprompt,
|
| 382 |
rootMessageId: conversation.rootMessageId,
|
| 383 |
assistant: conversation.assistantId
|
| 384 |
+
? ((await collections.assistants.findOne({
|
| 385 |
+
_id: new ObjectId(conversation.assistantId),
|
| 386 |
+
})) ?? undefined)
|
|
|
|
|
|
|
| 387 |
: undefined,
|
| 388 |
id: conversation._id.toString(),
|
| 389 |
updatedAt: conversation.updatedAt,
|
|
|
|
| 391 |
assistantId: conversation.assistantId,
|
| 392 |
modelTools: models.find((m) => m.id == conversation.model)?.tools ?? false,
|
| 393 |
shared: conversation.shared,
|
| 394 |
+
};
|
| 395 |
})
|
| 396 |
.post("", () => {
|
| 397 |
// todo: post new message
|
src/lib/server/api/routes/groups/tools.ts
CHANGED
|
@@ -4,27 +4,16 @@ import { ReviewStatus } from "$lib/types/Review";
|
|
| 4 |
import { toolFromConfigs } from "$lib/server/tools";
|
| 5 |
import { collections } from "$lib/server/database";
|
| 6 |
import { ObjectId, type Filter } from "mongodb";
|
| 7 |
-
import type { CommunityToolDB,
|
| 8 |
import { MetricsServer } from "$lib/server/metrics";
|
| 9 |
import { authCondition } from "$lib/server/auth";
|
| 10 |
import { SortKey } from "$lib/types/Assistant";
|
| 11 |
import type { User } from "$lib/types/User";
|
| 12 |
import { generateQueryTokens, generateSearchTokens } from "$lib/utils/searchTokens";
|
| 13 |
-
import { jsonSerialize, type Serialize } from "$lib/utils/serialize";
|
| 14 |
import { config } from "$lib/server/config";
|
| 15 |
|
| 16 |
const NUM_PER_PAGE = 16;
|
| 17 |
|
| 18 |
-
export type GETToolsResponse = Array<ToolFront>;
|
| 19 |
-
export type GETToolsSearchResponse = {
|
| 20 |
-
tools: Array<Serialize<ConfigTool | CommunityToolDB>>;
|
| 21 |
-
numTotalItems: number;
|
| 22 |
-
numItemsPerPage: number;
|
| 23 |
-
query: string | null;
|
| 24 |
-
sort: SortKey;
|
| 25 |
-
showUnfeatured: boolean;
|
| 26 |
-
};
|
| 27 |
-
|
| 28 |
export const toolGroup = new Elysia().use(authPlugin).group("/tools", (app) => {
|
| 29 |
return app
|
| 30 |
.get("/active", async ({ locals }) => {
|
|
@@ -154,13 +143,13 @@ export const toolGroup = new Elysia().use(authPlugin).group("/tools", (app) => {
|
|
| 154 |
(await collections.tools.countDocuments(filter)) + toolFromConfigs.length;
|
| 155 |
|
| 156 |
return {
|
| 157 |
-
tools
|
| 158 |
numTotalItems,
|
| 159 |
numItemsPerPage: NUM_PER_PAGE,
|
| 160 |
query: search,
|
| 161 |
sort,
|
| 162 |
showUnfeatured,
|
| 163 |
-
}
|
| 164 |
},
|
| 165 |
{
|
| 166 |
query: t.Object({
|
|
|
|
| 4 |
import { toolFromConfigs } from "$lib/server/tools";
|
| 5 |
import { collections } from "$lib/server/database";
|
| 6 |
import { ObjectId, type Filter } from "mongodb";
|
| 7 |
+
import type { CommunityToolDB, ToolFront, ToolInputFile } from "$lib/types/Tool";
|
| 8 |
import { MetricsServer } from "$lib/server/metrics";
|
| 9 |
import { authCondition } from "$lib/server/auth";
|
| 10 |
import { SortKey } from "$lib/types/Assistant";
|
| 11 |
import type { User } from "$lib/types/User";
|
| 12 |
import { generateQueryTokens, generateSearchTokens } from "$lib/utils/searchTokens";
|
|
|
|
| 13 |
import { config } from "$lib/server/config";
|
| 14 |
|
| 15 |
const NUM_PER_PAGE = 16;
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
export const toolGroup = new Elysia().use(authPlugin).group("/tools", (app) => {
|
| 18 |
return app
|
| 19 |
.get("/active", async ({ locals }) => {
|
|
|
|
| 143 |
(await collections.tools.countDocuments(filter)) + toolFromConfigs.length;
|
| 144 |
|
| 145 |
return {
|
| 146 |
+
tools,
|
| 147 |
numTotalItems,
|
| 148 |
numItemsPerPage: NUM_PER_PAGE,
|
| 149 |
query: search,
|
| 150 |
sort,
|
| 151 |
showUnfeatured,
|
| 152 |
+
};
|
| 153 |
},
|
| 154 |
{
|
| 155 |
query: t.Object({
|
src/lib/server/api/routes/groups/user.ts
CHANGED
|
@@ -8,7 +8,6 @@ import { DEFAULT_SETTINGS, type SettingsEditable } from "$lib/types/Settings";
|
|
| 8 |
import { toolFromConfigs } from "$lib/server/tools";
|
| 9 |
import { ObjectId } from "mongodb";
|
| 10 |
import { z } from "zod";
|
| 11 |
-
import { jsonSerialize } from "$lib/utils/serialize";
|
| 12 |
|
| 13 |
export const userGroup = new Elysia()
|
| 14 |
.use(authPlugin)
|
|
@@ -149,8 +148,7 @@ export const userGroup = new Elysia()
|
|
| 149 |
.find({
|
| 150 |
createdBy: locals.user?._id ?? locals.sessionId,
|
| 151 |
})
|
| 152 |
-
.toArray()
|
| 153 |
-
.then((el) => el.map((el) => jsonSerialize(el)));
|
| 154 |
return reports;
|
| 155 |
})
|
| 156 |
.get("/assistant/active", async ({ locals }) => {
|
|
|
|
| 8 |
import { toolFromConfigs } from "$lib/server/tools";
|
| 9 |
import { ObjectId } from "mongodb";
|
| 10 |
import { z } from "zod";
|
|
|
|
| 11 |
|
| 12 |
export const userGroup = new Elysia()
|
| 13 |
.use(authPlugin)
|
|
|
|
| 148 |
.find({
|
| 149 |
createdBy: locals.user?._id ?? locals.sessionId,
|
| 150 |
})
|
| 151 |
+
.toArray();
|
|
|
|
| 152 |
return reports;
|
| 153 |
})
|
| 154 |
.get("/assistant/active", async ({ locals }) => {
|
src/lib/types/ConvSidebar.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
|
|
|
|
|
| 1 |
export interface ConvSidebar {
|
| 2 |
-
id: string;
|
| 3 |
title: string;
|
| 4 |
updatedAt: Date;
|
| 5 |
model?: string;
|
| 6 |
-
assistantId?: string;
|
| 7 |
avatarUrl?: string | Promise<string | undefined>;
|
| 8 |
}
|
|
|
|
| 1 |
+
import type { ObjectId } from "bson";
|
| 2 |
+
|
| 3 |
export interface ConvSidebar {
|
| 4 |
+
id: ObjectId | string;
|
| 5 |
title: string;
|
| 6 |
updatedAt: Date;
|
| 7 |
model?: string;
|
| 8 |
+
assistantId?: ObjectId | string;
|
| 9 |
avatarUrl?: string | Promise<string | undefined>;
|
| 10 |
}
|
src/lib/utils/fetchJSON.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
| 1 |
-
import type { Serialize } from "./serialize";
|
| 2 |
-
|
| 3 |
export async function fetchJSON<T>(
|
| 4 |
url: string,
|
| 5 |
options?: {
|
| 6 |
fetch?: typeof window.fetch;
|
| 7 |
allowNull?: boolean;
|
| 8 |
}
|
| 9 |
-
): Promise<
|
| 10 |
const response = await (options?.fetch ?? fetch)(url);
|
| 11 |
if (!response.ok) {
|
| 12 |
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
@@ -16,7 +14,7 @@ export async function fetchJSON<T>(
|
|
| 16 |
const text = await response.text();
|
| 17 |
if (!text || text.trim() === "") {
|
| 18 |
if (options?.allowNull) {
|
| 19 |
-
return null as
|
| 20 |
}
|
| 21 |
throw new Error(`Received empty response from ${url} but allowNull is not set to true`);
|
| 22 |
}
|
|
|
|
|
|
|
|
|
|
| 1 |
export async function fetchJSON<T>(
|
| 2 |
url: string,
|
| 3 |
options?: {
|
| 4 |
fetch?: typeof window.fetch;
|
| 5 |
allowNull?: boolean;
|
| 6 |
}
|
| 7 |
+
): Promise<T> {
|
| 8 |
const response = await (options?.fetch ?? fetch)(url);
|
| 9 |
if (!response.ok) {
|
| 10 |
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
|
|
| 14 |
const text = await response.text();
|
| 15 |
if (!text || text.trim() === "") {
|
| 16 |
if (options?.allowNull) {
|
| 17 |
+
return null as T;
|
| 18 |
}
|
| 19 |
throw new Error(`Received empty response from ${url} but allowNull is not set to true`);
|
| 20 |
}
|
src/lib/utils/serialize.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
import type { ObjectId } from "mongodb";
|
| 2 |
-
|
| 3 |
-
export type Serialize<T> = T extends ObjectId | Date
|
| 4 |
-
? string
|
| 5 |
-
: T extends Array<infer U>
|
| 6 |
-
? Array<Serialize<U>>
|
| 7 |
-
: T extends object
|
| 8 |
-
? { [K in keyof T]: Serialize<T[K]> }
|
| 9 |
-
: T;
|
| 10 |
-
|
| 11 |
-
export function jsonSerialize<T>(data: T): Serialize<T> {
|
| 12 |
-
return JSON.parse(JSON.stringify(data)) as Serialize<T>;
|
| 13 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/routes/+layout.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
| 2 |
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
| 3 |
-
import {
|
| 4 |
-
import { useAPIClient, throwOnError, throwOnErrorNullable } from "$lib/APIClient";
|
| 5 |
import { getConfigManager } from "$lib/utils/PublicConfig.svelte";
|
| 6 |
|
| 7 |
export const load = async ({ depends, fetch }) => {
|
|
@@ -21,16 +20,16 @@ export const load = async ({ depends, fetch }) => {
|
|
| 21 |
featureFlags,
|
| 22 |
conversationsData,
|
| 23 |
] = await Promise.all([
|
| 24 |
-
client.user.settings.get().then(
|
| 25 |
-
client.models.get().then(
|
| 26 |
-
client.user.assistants.get().then(
|
| 27 |
-
client.models.old.get().then(
|
| 28 |
-
client.tools.active.get().then(
|
| 29 |
-
client.tools.count.get().then(
|
| 30 |
-
client.user.get().then(
|
| 31 |
-
client["public-config"].get().then(
|
| 32 |
-
client["feature-flags"].get().then(
|
| 33 |
-
client.conversations.get({ query: { p: 0 } }).then(
|
| 34 |
]);
|
| 35 |
|
| 36 |
const defaultModel = models[0];
|
|
@@ -57,9 +56,9 @@ export const load = async ({ depends, fetch }) => {
|
|
| 57 |
avatarUrl: client
|
| 58 |
.assistants({ id: conv.assistantId.toString() })
|
| 59 |
.get()
|
| 60 |
-
.then(
|
| 61 |
.then((assistant) => {
|
| 62 |
-
if (!assistant.avatar) {
|
| 63 |
return undefined;
|
| 64 |
}
|
| 65 |
return `/settings/assistants/${conv.assistantId}/avatar.jpg?hash=${assistant.avatar}`;
|
|
@@ -76,8 +75,7 @@ export const load = async ({ depends, fetch }) => {
|
|
| 76 |
? await client
|
| 77 |
.assistants({ id: settings?.activeModel })
|
| 78 |
.get()
|
| 79 |
-
.then(
|
| 80 |
-
.then(jsonSerialize)
|
| 81 |
.catch(() => undefined)
|
| 82 |
: undefined,
|
| 83 |
assistants,
|
|
|
|
| 1 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
| 2 |
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
| 3 |
+
import { useAPIClient, handleResponse } from "$lib/APIClient";
|
|
|
|
| 4 |
import { getConfigManager } from "$lib/utils/PublicConfig.svelte";
|
| 5 |
|
| 6 |
export const load = async ({ depends, fetch }) => {
|
|
|
|
| 20 |
featureFlags,
|
| 21 |
conversationsData,
|
| 22 |
] = await Promise.all([
|
| 23 |
+
client.user.settings.get().then(handleResponse),
|
| 24 |
+
client.models.get().then(handleResponse),
|
| 25 |
+
client.user.assistants.get().then(handleResponse),
|
| 26 |
+
client.models.old.get().then(handleResponse),
|
| 27 |
+
client.tools.active.get().then(handleResponse),
|
| 28 |
+
client.tools.count.get().then(handleResponse),
|
| 29 |
+
client.user.get().then(handleResponse),
|
| 30 |
+
client["public-config"].get().then(handleResponse),
|
| 31 |
+
client["feature-flags"].get().then(handleResponse),
|
| 32 |
+
client.conversations.get({ query: { p: 0 } }).then(handleResponse),
|
| 33 |
]);
|
| 34 |
|
| 35 |
const defaultModel = models[0];
|
|
|
|
| 56 |
avatarUrl: client
|
| 57 |
.assistants({ id: conv.assistantId.toString() })
|
| 58 |
.get()
|
| 59 |
+
.then(handleResponse)
|
| 60 |
.then((assistant) => {
|
| 61 |
+
if (!assistant || !assistant.avatar) {
|
| 62 |
return undefined;
|
| 63 |
}
|
| 64 |
return `/settings/assistants/${conv.assistantId}/avatar.jpg?hash=${assistant.avatar}`;
|
|
|
|
| 75 |
? await client
|
| 76 |
.assistants({ id: settings?.activeModel })
|
| 77 |
.get()
|
| 78 |
+
.then(handleResponse)
|
|
|
|
| 79 |
.catch(() => undefined)
|
| 80 |
: undefined,
|
| 81 |
assistants,
|
src/routes/api/conversations/search/+server.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
| 1 |
-
import { authCondition } from "$lib/server/auth";
|
| 2 |
-
import { collections } from "$lib/server/database";
|
| 3 |
-
import { models } from "$lib/server/models";
|
| 4 |
-
import type { RequestHandler } from "@sveltejs/kit";
|
| 5 |
-
import pkg from "natural";
|
| 6 |
-
const { PorterStemmer } = pkg;
|
| 7 |
-
|
| 8 |
-
export type GETSearchEndpointReturn = Array<{
|
| 9 |
-
id: string;
|
| 10 |
-
title: string;
|
| 11 |
-
content: string;
|
| 12 |
-
matchedText: string;
|
| 13 |
-
updatedAt: Date;
|
| 14 |
-
model: string;
|
| 15 |
-
assistantId?: string;
|
| 16 |
-
mdoelTools?: boolean;
|
| 17 |
-
}>;
|
| 18 |
-
|
| 19 |
-
export const GET: RequestHandler = async ({ locals, url }) => {
|
| 20 |
-
const searchQuery = url.searchParams.get("q");
|
| 21 |
-
const p = parseInt(url.searchParams.get("p") ?? "0");
|
| 22 |
-
|
| 23 |
-
if (!searchQuery || searchQuery.length < 3) {
|
| 24 |
-
return Response.json([]);
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
if (locals.user?._id || locals.sessionId) {
|
| 28 |
-
const convs = await collections.conversations
|
| 29 |
-
.find({
|
| 30 |
-
sessionId: undefined,
|
| 31 |
-
...authCondition(locals),
|
| 32 |
-
$text: { $search: searchQuery },
|
| 33 |
-
})
|
| 34 |
-
.sort({
|
| 35 |
-
updatedAt: -1, // Sort by date updated in descending order
|
| 36 |
-
})
|
| 37 |
-
.project({
|
| 38 |
-
title: 1,
|
| 39 |
-
updatedAt: 1,
|
| 40 |
-
model: 1,
|
| 41 |
-
assistantId: 1,
|
| 42 |
-
messages: 1,
|
| 43 |
-
userId: 1,
|
| 44 |
-
})
|
| 45 |
-
.skip(p * 5)
|
| 46 |
-
.limit(5)
|
| 47 |
-
.toArray()
|
| 48 |
-
.then((convs) =>
|
| 49 |
-
convs.map((conv) => {
|
| 50 |
-
let matchedContent = "";
|
| 51 |
-
let matchedText = "";
|
| 52 |
-
|
| 53 |
-
// Find the best match using stemming to handle MongoDB's text search behavior
|
| 54 |
-
let bestMatch = null;
|
| 55 |
-
let bestMatchLength = 0;
|
| 56 |
-
|
| 57 |
-
// Simple function to find the best match in content
|
| 58 |
-
const findBestMatch = (
|
| 59 |
-
content: string,
|
| 60 |
-
query: string
|
| 61 |
-
): { start: number; end: number; text: string } | null => {
|
| 62 |
-
const contentLower = content.toLowerCase();
|
| 63 |
-
const queryLower = query.toLowerCase();
|
| 64 |
-
|
| 65 |
-
// Try exact word boundary match first
|
| 66 |
-
const wordRegex = new RegExp(
|
| 67 |
-
`\\b${queryLower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`,
|
| 68 |
-
"gi"
|
| 69 |
-
);
|
| 70 |
-
const wordMatch = wordRegex.exec(content);
|
| 71 |
-
if (wordMatch) {
|
| 72 |
-
return {
|
| 73 |
-
start: wordMatch.index,
|
| 74 |
-
end: wordMatch.index + wordMatch[0].length - 1,
|
| 75 |
-
text: wordMatch[0],
|
| 76 |
-
};
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
// Try simple substring match
|
| 80 |
-
const index = contentLower.indexOf(queryLower);
|
| 81 |
-
if (index !== -1) {
|
| 82 |
-
return {
|
| 83 |
-
start: index,
|
| 84 |
-
end: index + queryLower.length - 1,
|
| 85 |
-
text: content.substring(index, index + queryLower.length),
|
| 86 |
-
};
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
return null;
|
| 90 |
-
};
|
| 91 |
-
|
| 92 |
-
// Create search variations
|
| 93 |
-
const searchVariations = [searchQuery.toLowerCase()];
|
| 94 |
-
|
| 95 |
-
// Add stemmed variations
|
| 96 |
-
try {
|
| 97 |
-
const stemmed = PorterStemmer.stem(searchQuery.toLowerCase());
|
| 98 |
-
if (stemmed !== searchQuery.toLowerCase()) {
|
| 99 |
-
searchVariations.push(stemmed);
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
// Find actual words in conversations that stem to the same root
|
| 103 |
-
for (const message of conv.messages) {
|
| 104 |
-
if (message.content) {
|
| 105 |
-
const words = message.content.toLowerCase().match(/\b\w+\b/g) || [];
|
| 106 |
-
words.forEach((word: string) => {
|
| 107 |
-
if (PorterStemmer.stem(word) === stemmed && !searchVariations.includes(word)) {
|
| 108 |
-
searchVariations.push(word);
|
| 109 |
-
}
|
| 110 |
-
});
|
| 111 |
-
}
|
| 112 |
-
}
|
| 113 |
-
} catch (e) {
|
| 114 |
-
console.warn("Stemming failed for:", searchQuery, e);
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
// Add simple variations
|
| 118 |
-
const query = searchQuery.toLowerCase();
|
| 119 |
-
if (query.endsWith("s") && query.length > 3) {
|
| 120 |
-
searchVariations.push(query.slice(0, -1));
|
| 121 |
-
} else if (!query.endsWith("s")) {
|
| 122 |
-
searchVariations.push(query + "s");
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
-
// Search through all messages for the best match
|
| 126 |
-
for (const message of conv.messages) {
|
| 127 |
-
if (!message.content) continue;
|
| 128 |
-
|
| 129 |
-
// Try each variation in order of preference
|
| 130 |
-
for (const variation of searchVariations) {
|
| 131 |
-
const match = findBestMatch(message.content, variation);
|
| 132 |
-
if (match) {
|
| 133 |
-
const isExactQuery = variation === searchQuery.toLowerCase();
|
| 134 |
-
const priority = isExactQuery ? 1000 : match.text.length;
|
| 135 |
-
|
| 136 |
-
if (priority > bestMatchLength) {
|
| 137 |
-
bestMatch = {
|
| 138 |
-
content: message.content,
|
| 139 |
-
matchStart: match.start,
|
| 140 |
-
matchEnd: match.end,
|
| 141 |
-
matchedText: match.text,
|
| 142 |
-
};
|
| 143 |
-
bestMatchLength = priority;
|
| 144 |
-
|
| 145 |
-
// If we found exact query match, we're done
|
| 146 |
-
if (isExactQuery) break;
|
| 147 |
-
}
|
| 148 |
-
}
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
// Stop if we found an exact match
|
| 152 |
-
if (bestMatchLength >= 1000) break;
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
if (bestMatch) {
|
| 156 |
-
const { content, matchStart, matchEnd } = bestMatch;
|
| 157 |
-
matchedText = bestMatch.matchedText;
|
| 158 |
-
|
| 159 |
-
// Create centered context around the match
|
| 160 |
-
const maxContextLength = 160; // Maximum length of actual content (no padding)
|
| 161 |
-
const matchLength = matchEnd - matchStart + 1;
|
| 162 |
-
|
| 163 |
-
// Calculate context window - don't exceed maxContextLength even if content is longer
|
| 164 |
-
const availableForContext = Math.min(maxContextLength, content.length) - matchLength;
|
| 165 |
-
const contextPerSide = Math.floor(availableForContext / 2);
|
| 166 |
-
|
| 167 |
-
// Calculate snippet boundaries to center the match within maxContextLength
|
| 168 |
-
let snippetStart = Math.max(0, matchStart - contextPerSide);
|
| 169 |
-
let snippetEnd = Math.min(content.length, matchStart + matchLength + contextPerSide);
|
| 170 |
-
|
| 171 |
-
// Ensure we don't exceed maxContextLength
|
| 172 |
-
if (snippetEnd - snippetStart > maxContextLength) {
|
| 173 |
-
if (matchStart - contextPerSide < 0) {
|
| 174 |
-
// Match is near start, extend end but limit to maxContextLength
|
| 175 |
-
snippetEnd = Math.min(content.length, snippetStart + maxContextLength);
|
| 176 |
-
} else {
|
| 177 |
-
// Match is not near start, limit to maxContextLength from match start
|
| 178 |
-
snippetEnd = Math.min(content.length, snippetStart + maxContextLength);
|
| 179 |
-
}
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
// Adjust to word boundaries if possible (but don't move more than 15 chars)
|
| 183 |
-
const originalStart = snippetStart;
|
| 184 |
-
const originalEnd = snippetEnd;
|
| 185 |
-
|
| 186 |
-
while (
|
| 187 |
-
snippetStart > 0 &&
|
| 188 |
-
content[snippetStart] !== " " &&
|
| 189 |
-
content[snippetStart] !== "\n" &&
|
| 190 |
-
originalStart - snippetStart < 15
|
| 191 |
-
) {
|
| 192 |
-
snippetStart--;
|
| 193 |
-
}
|
| 194 |
-
while (
|
| 195 |
-
snippetEnd < content.length &&
|
| 196 |
-
content[snippetEnd] !== " " &&
|
| 197 |
-
content[snippetEnd] !== "\n" &&
|
| 198 |
-
snippetEnd - originalEnd < 15
|
| 199 |
-
) {
|
| 200 |
-
snippetEnd++;
|
| 201 |
-
}
|
| 202 |
-
|
| 203 |
-
// Extract the content
|
| 204 |
-
let extractedContent = content.substring(snippetStart, snippetEnd).trim();
|
| 205 |
-
// Add ellipsis indicators only
|
| 206 |
-
if (snippetStart > 0) {
|
| 207 |
-
extractedContent = "..." + extractedContent;
|
| 208 |
-
}
|
| 209 |
-
if (snippetEnd < content.length) {
|
| 210 |
-
extractedContent = extractedContent + "...";
|
| 211 |
-
}
|
| 212 |
-
|
| 213 |
-
matchedContent = extractedContent;
|
| 214 |
-
} else {
|
| 215 |
-
// Fallback: use beginning of the first message if no match found
|
| 216 |
-
const firstMessage = conv.messages[0];
|
| 217 |
-
if (firstMessage?.content) {
|
| 218 |
-
const content = firstMessage.content;
|
| 219 |
-
matchedContent = content.length > 200 ? content.substring(0, 200) + "..." : content;
|
| 220 |
-
matchedText = searchQuery; // Fallback to search query
|
| 221 |
-
}
|
| 222 |
-
}
|
| 223 |
-
|
| 224 |
-
return {
|
| 225 |
-
_id: conv._id,
|
| 226 |
-
id: conv._id,
|
| 227 |
-
title: conv.title,
|
| 228 |
-
content: matchedContent,
|
| 229 |
-
matchedText,
|
| 230 |
-
updatedAt: conv.updatedAt,
|
| 231 |
-
model: conv.model,
|
| 232 |
-
assistantId: conv.assistantId,
|
| 233 |
-
modelTools: models.find((m) => m.id == conv.model)?.tools ?? false,
|
| 234 |
-
};
|
| 235 |
-
})
|
| 236 |
-
);
|
| 237 |
-
return Response.json(convs as GETSearchEndpointReturn);
|
| 238 |
-
}
|
| 239 |
-
return Response.json([]);
|
| 240 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/routes/assistant/[assistantId]/+page.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
| 1 |
-
import { useAPIClient,
|
| 2 |
-
import { jsonSerialize } from "$lib/utils/serialize";
|
| 3 |
|
| 4 |
export async function load({ fetch, params }) {
|
| 5 |
const client = useAPIClient({ fetch });
|
| 6 |
|
| 7 |
-
const data = client
|
| 8 |
-
.assistants({ id: params.assistantId })
|
| 9 |
-
.get()
|
| 10 |
-
.then(throwOnError)
|
| 11 |
-
.then(jsonSerialize);
|
| 12 |
|
| 13 |
await client.assistants({ id: params.assistantId }).follow.post();
|
| 14 |
|
|
|
|
| 1 |
+
import { useAPIClient, handleResponse } from "$lib/APIClient";
|
|
|
|
| 2 |
|
| 3 |
export async function load({ fetch, params }) {
|
| 4 |
const client = useAPIClient({ fetch });
|
| 5 |
|
| 6 |
+
const data = client.assistants({ id: params.assistantId }).get().then(handleResponse);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
await client.assistants({ id: params.assistantId }).follow.post();
|
| 9 |
|
src/routes/assistants/+page.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
-
import { useAPIClient,
|
| 2 |
|
| 3 |
export const load = async ({ url, fetch }) => {
|
| 4 |
const client = useAPIClient({ fetch });
|
| 5 |
|
| 6 |
const data = client.assistants.search
|
| 7 |
.get({ query: Object.fromEntries(url.searchParams.entries()) })
|
| 8 |
-
.then(
|
| 9 |
|
| 10 |
return data;
|
| 11 |
};
|
|
|
|
| 1 |
+
import { useAPIClient, handleResponse } from "$lib/APIClient";
|
| 2 |
|
| 3 |
export const load = async ({ url, fetch }) => {
|
| 4 |
const client = useAPIClient({ fetch });
|
| 5 |
|
| 6 |
const data = client.assistants.search
|
| 7 |
.get({ query: Object.fromEntries(url.searchParams.entries()) })
|
| 8 |
+
.then(handleResponse);
|
| 9 |
|
| 10 |
return data;
|
| 11 |
};
|
src/routes/conversation/[id]/+page.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { useAPIClient,
|
| 2 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
| 3 |
import { redirect } from "@sveltejs/kit";
|
| 4 |
|
|
@@ -8,7 +8,7 @@ export const load = async ({ params, depends, fetch }) => {
|
|
| 8 |
const client = useAPIClient({ fetch });
|
| 9 |
|
| 10 |
try {
|
| 11 |
-
return await client.conversations({ id: params.id }).get().then(
|
| 12 |
} catch {
|
| 13 |
redirect(302, "/");
|
| 14 |
}
|
|
|
|
| 1 |
+
import { useAPIClient, handleResponse } from "$lib/APIClient";
|
| 2 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
| 3 |
import { redirect } from "@sveltejs/kit";
|
| 4 |
|
|
|
|
| 8 |
const client = useAPIClient({ fetch });
|
| 9 |
|
| 10 |
try {
|
| 11 |
+
return await client.conversations({ id: params.id }).get().then(handleResponse);
|
| 12 |
} catch {
|
| 13 |
redirect(302, "/");
|
| 14 |
}
|
src/routes/settings/(nav)/+layout.svelte
CHANGED
|
@@ -18,7 +18,7 @@
|
|
| 18 |
import { debounce } from "$lib/utils/debounce";
|
| 19 |
|
| 20 |
import { fly } from "svelte/transition";
|
| 21 |
-
import {
|
| 22 |
|
| 23 |
interface Props {
|
| 24 |
data: LayoutData;
|
|
@@ -255,7 +255,7 @@
|
|
| 255 |
id: assistant._id,
|
| 256 |
})
|
| 257 |
.follow.delete()
|
| 258 |
-
.then(
|
| 259 |
.then(() => {
|
| 260 |
if (assistant._id.toString() === page.params.assistantId) {
|
| 261 |
goto(`${base}/settings`, { invalidateAll: true });
|
|
|
|
| 18 |
import { debounce } from "$lib/utils/debounce";
|
| 19 |
|
| 20 |
import { fly } from "svelte/transition";
|
| 21 |
+
import { handleResponse, useAPIClient } from "$lib/APIClient";
|
| 22 |
|
| 23 |
interface Props {
|
| 24 |
data: LayoutData;
|
|
|
|
| 255 |
id: assistant._id,
|
| 256 |
})
|
| 257 |
.follow.delete()
|
| 258 |
+
.then(handleResponse)
|
| 259 |
.then(() => {
|
| 260 |
if (assistant._id.toString() === page.params.assistantId) {
|
| 261 |
goto(`${base}/settings`, { invalidateAll: true });
|
src/routes/settings/(nav)/assistants/[assistantId]/edit/+page.svelte
CHANGED
|
@@ -12,13 +12,4 @@
|
|
| 12 |
let assistant = data.assistants.find((el) => el._id.toString() === page.params.assistantId);
|
| 13 |
</script>
|
| 14 |
|
| 15 |
-
<AssistantSettings
|
| 16 |
-
assistant={assistant
|
| 17 |
-
? {
|
| 18 |
-
...assistant,
|
| 19 |
-
updatedAt: new Date(assistant.updatedAt),
|
| 20 |
-
createdAt: new Date(assistant.createdAt),
|
| 21 |
-
}
|
| 22 |
-
: undefined}
|
| 23 |
-
models={data.models}
|
| 24 |
-
/>
|
|
|
|
| 12 |
let assistant = data.assistants.find((el) => el._id.toString() === page.params.assistantId);
|
| 13 |
</script>
|
| 14 |
|
| 15 |
+
<AssistantSettings {assistant} models={data.models} />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/routes/settings/+layout.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
| 1 |
-
import { useAPIClient,
|
| 2 |
|
| 3 |
export const load = async ({ parent, fetch }) => {
|
| 4 |
const client = useAPIClient({ fetch });
|
| 5 |
|
| 6 |
-
const reports = await client.user.reports.get().then(
|
| 7 |
|
| 8 |
return {
|
| 9 |
assistants: (await parent().then((data) => data.assistants)).map((el) => ({
|
| 10 |
...el,
|
| 11 |
-
reported: reports.some(
|
|
|
|
|
|
|
| 12 |
})),
|
| 13 |
};
|
| 14 |
};
|
|
|
|
| 1 |
+
import { useAPIClient, handleResponse } from "$lib/APIClient";
|
| 2 |
|
| 3 |
export const load = async ({ parent, fetch }) => {
|
| 4 |
const client = useAPIClient({ fetch });
|
| 5 |
|
| 6 |
+
const reports = await client.user.reports.get().then(handleResponse);
|
| 7 |
|
| 8 |
return {
|
| 9 |
assistants: (await parent().then((data) => data.assistants)).map((el) => ({
|
| 10 |
...el,
|
| 11 |
+
reported: reports.some(
|
| 12 |
+
(r) => r.contentId.toString() === el._id.toString() && r.object === "assistant"
|
| 13 |
+
),
|
| 14 |
})),
|
| 15 |
};
|
| 16 |
};
|
src/routes/tools/+page.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
-
import {
|
| 2 |
|
| 3 |
export const load = async ({ url, fetch }) => {
|
| 4 |
const client = useAPIClient({ fetch });
|
| 5 |
|
| 6 |
return client.tools.search
|
| 7 |
.get({ query: Object.fromEntries(url.searchParams.entries()) })
|
| 8 |
-
.then(
|
| 9 |
};
|
|
|
|
| 1 |
+
import { handleResponse, useAPIClient } from "$lib/APIClient";
|
| 2 |
|
| 3 |
export const load = async ({ url, fetch }) => {
|
| 4 |
const client = useAPIClient({ fetch });
|
| 5 |
|
| 6 |
return client.tools.search
|
| 7 |
.get({ query: Object.fromEntries(url.searchParams.entries()) })
|
| 8 |
+
.then(handleResponse);
|
| 9 |
};
|
src/routes/tools/ToolEdit.svelte
CHANGED
|
@@ -15,7 +15,7 @@
|
|
| 15 |
|
| 16 |
import CarbonInformation from "~icons/carbon/information";
|
| 17 |
import { page } from "$app/state";
|
| 18 |
-
import {
|
| 19 |
|
| 20 |
interface Props {
|
| 21 |
tool?: CommunityToolEditable | undefined;
|
|
@@ -76,7 +76,7 @@
|
|
| 76 |
space: editableTool.baseUrl,
|
| 77 |
},
|
| 78 |
})
|
| 79 |
-
.then(
|
| 80 |
|
| 81 |
const newInputs = api.named_endpoints[editableTool.endpoint].parameters.map((param, idx) => {
|
| 82 |
if (tool?.inputs[idx]?.name === param.parameter_name) {
|
|
@@ -328,7 +328,7 @@
|
|
| 328 |
{/if}
|
| 329 |
|
| 330 |
{#if editableTool.baseUrl}
|
| 331 |
-
{#await client["spaces-config"].get({ query: { space: spaceUrl } }).then(
|
| 332 |
<p class="text-sm text-gray-500">Loading...</p>
|
| 333 |
{:then api}
|
| 334 |
<div class="flex flex-row flex-wrap gap-4">
|
|
|
|
| 15 |
|
| 16 |
import CarbonInformation from "~icons/carbon/information";
|
| 17 |
import { page } from "$app/state";
|
| 18 |
+
import { handleResponse, useAPIClient } from "$lib/APIClient";
|
| 19 |
|
| 20 |
interface Props {
|
| 21 |
tool?: CommunityToolEditable | undefined;
|
|
|
|
| 76 |
space: editableTool.baseUrl,
|
| 77 |
},
|
| 78 |
})
|
| 79 |
+
.then(handleResponse);
|
| 80 |
|
| 81 |
const newInputs = api.named_endpoints[editableTool.endpoint].parameters.map((param, idx) => {
|
| 82 |
if (tool?.inputs[idx]?.name === param.parameter_name) {
|
|
|
|
| 328 |
{/if}
|
| 329 |
|
| 330 |
{#if editableTool.baseUrl}
|
| 331 |
+
{#await client["spaces-config"].get({ query: { space: spaceUrl } }).then(handleResponse)}
|
| 332 |
<p class="text-sm text-gray-500">Loading...</p>
|
| 333 |
{:then api}
|
| 334 |
<div class="flex flex-row flex-wrap gap-4">
|
src/routes/tools/[toolId]/+layout.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
-
import { useAPIClient,
|
| 2 |
-
import { jsonSerialize } from "$lib/utils/serialize";
|
| 3 |
|
| 4 |
export const load = async ({ params, fetch }) => {
|
| 5 |
const client = useAPIClient({ fetch });
|
|
@@ -9,8 +8,7 @@ export const load = async ({ params, fetch }) => {
|
|
| 9 |
id: params.toolId,
|
| 10 |
})
|
| 11 |
.get()
|
| 12 |
-
.then(
|
| 13 |
-
.then(jsonSerialize);
|
| 14 |
|
| 15 |
return { tool: await data };
|
| 16 |
};
|
|
|
|
| 1 |
+
import { useAPIClient, handleResponse } from "$lib/APIClient";
|
|
|
|
| 2 |
|
| 3 |
export const load = async ({ params, fetch }) => {
|
| 4 |
const client = useAPIClient({ fetch });
|
|
|
|
| 8 |
id: params.toolId,
|
| 9 |
})
|
| 10 |
.get()
|
| 11 |
+
.then(handleResponse);
|
|
|
|
| 12 |
|
| 13 |
return { tool: await data };
|
| 14 |
};
|