Spaces:
Sleeping
Sleeping
feat(assistants): use community tools in assistants (#1421)
Browse files* wip: give assistant tool access
* add api endpoints
* add/delete tools from assistant settings
* make tools work with assistants
* feat(tools): add more tools indicator throughout UI
* formatting
- src/lib/components/AssistantSettings.svelte +33 -19
- src/lib/components/AssistantToolPicker.svelte +124 -0
- src/lib/components/ToolBadge.svelte +35 -0
- src/lib/components/ToolLogo.svelte +3 -1
- src/lib/components/chat/AssistantIntroduction.svelte +11 -0
- src/lib/server/models.ts +0 -1
- src/lib/server/textGeneration/assistant.ts +2 -2
- src/lib/server/textGeneration/index.ts +3 -3
- src/lib/server/textGeneration/tools.ts +17 -8
- src/lib/server/textGeneration/types.ts +1 -1
- src/lib/types/Assistant.ts +1 -0
- src/routes/api/tools/[toolId]/+server.ts +49 -0
- src/routes/api/tools/search/+server.ts +57 -0
- src/routes/assistants/+page.svelte +20 -8
- src/routes/conversation/[id]/+server.ts +1 -0
- src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte +23 -0
- src/routes/settings/(nav)/assistants/[assistantId]/edit/+page.server.ts +19 -1
- src/routes/settings/(nav)/assistants/[assistantId]/edit/[email protected] +1 -1
- src/routes/settings/(nav)/assistants/new/+page.server.ts +19 -1
src/lib/components/AssistantSettings.svelte
CHANGED
@@ -11,6 +11,7 @@
|
|
11 |
import CarbonUpload from "~icons/carbon/upload";
|
12 |
import CarbonHelpFilled from "~icons/carbon/help";
|
13 |
import CarbonSettingsAdjust from "~icons/carbon/settings-adjust";
|
|
|
14 |
|
15 |
import { useSettingsStore } from "$lib/stores/settings";
|
16 |
import { isHuggingChat } from "$lib/utils/isHuggingChat";
|
@@ -18,6 +19,7 @@
|
|
18 |
import TokensCounter from "./TokensCounter.svelte";
|
19 |
import HoverTooltip from "./HoverTooltip.svelte";
|
20 |
import { findCurrentModel } from "$lib/utils/models";
|
|
|
21 |
|
22 |
type ActionData = {
|
23 |
error: boolean;
|
@@ -93,7 +95,9 @@
|
|
93 |
? "domains"
|
94 |
: false;
|
95 |
|
|
|
96 |
const regex = /{{\s?url=(.+?)\s?}}/g;
|
|
|
97 |
$: templateVariables = [...systemPrompt.matchAll(regex)].map((match) => match[1]);
|
98 |
$: selectedModel = models.find((m) => m.id === modelId);
|
99 |
</script>
|
@@ -146,6 +150,8 @@
|
|
146 |
formData.set("ragLinkList", "");
|
147 |
}
|
148 |
|
|
|
|
|
149 |
return async ({ result }) => {
|
150 |
loading = false;
|
151 |
await applyAction(result);
|
@@ -403,16 +409,27 @@
|
|
403 |
</div>
|
404 |
<p class="text-xs text-red-500">{getError("inputMessage1", form)}</p>
|
405 |
</label>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
406 |
{#if $page.data.enableAssistantsRAG}
|
407 |
-
<div class="
|
408 |
<span class="mt-2 text-smd font-semibold"
|
409 |
>Internet access
|
410 |
<IconInternet classNames="inline text-sm text-blue-600" />
|
411 |
|
412 |
-
<span class="ml-1 rounded bg-gray-100 px-1 py-0.5 text-xxs font-normal text-gray-600"
|
413 |
-
>Experimental</span
|
414 |
-
>
|
415 |
-
|
416 |
{#if isHuggingChat}
|
417 |
<a
|
418 |
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions/385"
|
@@ -507,26 +524,13 @@
|
|
507 |
/>
|
508 |
<p class="text-xs text-red-500">{getError("ragLinkList", form)}</p>
|
509 |
{/if}
|
510 |
-
|
511 |
-
<!-- divider -->
|
512 |
-
<div class="my-3 ml-0 mr-6 w-full border border-gray-200" />
|
513 |
-
|
514 |
-
<label class="text-sm has-[:checked]:font-semibold">
|
515 |
-
<input type="checkbox" name="dynamicPrompt" bind:checked={dynamicPrompt} />
|
516 |
-
Dynamic Prompt
|
517 |
-
<p class="mb-2 text-xs font-normal text-gray-500">
|
518 |
-
Allow the use of template variables {"{{url=https://example.com/path}}"}
|
519 |
-
to insert dynamic content into your prompt by making GET requests to specified URLs on
|
520 |
-
each inference.
|
521 |
-
</p>
|
522 |
-
</label>
|
523 |
</div>
|
524 |
{/if}
|
525 |
</div>
|
526 |
|
527 |
<div class="relative col-span-1 flex h-full flex-col">
|
528 |
<div class="mb-1 flex justify-between text-sm">
|
529 |
-
<span class="font-semibold"> Instructions (System Prompt) </span>
|
530 |
{#if dynamicPrompt && templateVariables.length}
|
531 |
<div class="relative">
|
532 |
<button
|
@@ -549,6 +553,16 @@
|
|
549 |
</div>
|
550 |
{/if}
|
551 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
552 |
<div class="relative mb-20 flex h-full flex-col gap-2">
|
553 |
<textarea
|
554 |
name="preprompt"
|
|
|
11 |
import CarbonUpload from "~icons/carbon/upload";
|
12 |
import CarbonHelpFilled from "~icons/carbon/help";
|
13 |
import CarbonSettingsAdjust from "~icons/carbon/settings-adjust";
|
14 |
+
import CarbonTools from "~icons/carbon/tools";
|
15 |
|
16 |
import { useSettingsStore } from "$lib/stores/settings";
|
17 |
import { isHuggingChat } from "$lib/utils/isHuggingChat";
|
|
|
19 |
import TokensCounter from "./TokensCounter.svelte";
|
20 |
import HoverTooltip from "./HoverTooltip.svelte";
|
21 |
import { findCurrentModel } from "$lib/utils/models";
|
22 |
+
import AssistantToolPicker from "./AssistantToolPicker.svelte";
|
23 |
|
24 |
type ActionData = {
|
25 |
error: boolean;
|
|
|
95 |
? "domains"
|
96 |
: false;
|
97 |
|
98 |
+
let tools = assistant?.tools ?? [];
|
99 |
const regex = /{{\s?url=(.+?)\s?}}/g;
|
100 |
+
|
101 |
$: templateVariables = [...systemPrompt.matchAll(regex)].map((match) => match[1]);
|
102 |
$: selectedModel = models.find((m) => m.id === modelId);
|
103 |
</script>
|
|
|
150 |
formData.set("ragLinkList", "");
|
151 |
}
|
152 |
|
153 |
+
formData.set("tools", tools.join(","));
|
154 |
+
|
155 |
return async ({ result }) => {
|
156 |
loading = false;
|
157 |
await applyAction(result);
|
|
|
409 |
</div>
|
410 |
<p class="text-xs text-red-500">{getError("inputMessage1", form)}</p>
|
411 |
</label>
|
412 |
+
{#if $page.data.user?.isEarlyAccess && selectedModel?.tools}
|
413 |
+
<div>
|
414 |
+
<span class="text-smd font-semibold"
|
415 |
+
>Tools
|
416 |
+
<CarbonTools class="inline text-xs text-purple-600" />
|
417 |
+
<span class="ml-1 rounded bg-gray-100 px-1 py-0.5 text-xxs font-normal text-gray-600"
|
418 |
+
>Experimental</span
|
419 |
+
>
|
420 |
+
</span>
|
421 |
+
<p class="text-xs text-gray-500">
|
422 |
+
Choose up to 3 community tools that will be used with this assistant.
|
423 |
+
</p>
|
424 |
+
</div>
|
425 |
+
<AssistantToolPicker bind:toolIds={tools} />
|
426 |
+
{/if}
|
427 |
{#if $page.data.enableAssistantsRAG}
|
428 |
+
<div class="flex flex-col flex-nowrap pb-4">
|
429 |
<span class="mt-2 text-smd font-semibold"
|
430 |
>Internet access
|
431 |
<IconInternet classNames="inline text-sm text-blue-600" />
|
432 |
|
|
|
|
|
|
|
|
|
433 |
{#if isHuggingChat}
|
434 |
<a
|
435 |
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions/385"
|
|
|
524 |
/>
|
525 |
<p class="text-xs text-red-500">{getError("ragLinkList", form)}</p>
|
526 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
527 |
</div>
|
528 |
{/if}
|
529 |
</div>
|
530 |
|
531 |
<div class="relative col-span-1 flex h-full flex-col">
|
532 |
<div class="mb-1 flex justify-between text-sm">
|
533 |
+
<span class="block font-semibold"> Instructions (System Prompt) </span>
|
534 |
{#if dynamicPrompt && templateVariables.length}
|
535 |
<div class="relative">
|
536 |
<button
|
|
|
553 |
</div>
|
554 |
{/if}
|
555 |
</div>
|
556 |
+
<label class="pb-2 text-sm has-[:checked]:font-semibold">
|
557 |
+
<input type="checkbox" name="dynamicPrompt" bind:checked={dynamicPrompt} />
|
558 |
+
Dynamic Prompt
|
559 |
+
<p class="mb-2 text-xs font-normal text-gray-500">
|
560 |
+
Allow the use of template variables {"{{url=https://example.com/path}}"}
|
561 |
+
to insert dynamic content into your prompt by making GET requests to specified URLs on each
|
562 |
+
inference.
|
563 |
+
</p>
|
564 |
+
</label>
|
565 |
+
|
566 |
<div class="relative mb-20 flex h-full flex-col gap-2">
|
567 |
<textarea
|
568 |
name="preprompt"
|
src/lib/components/AssistantToolPicker.svelte
ADDED
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { base } from "$app/paths";
|
3 |
+
import type { ToolLogoColor, ToolLogoIcon } from "$lib/types/Tool";
|
4 |
+
import { debounce } from "$lib/utils/debounce";
|
5 |
+
import { onMount } from "svelte";
|
6 |
+
import ToolLogo from "./ToolLogo.svelte";
|
7 |
+
|
8 |
+
import CarbonClose from "~icons/carbon/close";
|
9 |
+
|
10 |
+
interface ToolSuggestion {
|
11 |
+
_id: string;
|
12 |
+
displayName: string;
|
13 |
+
createdByName: string;
|
14 |
+
color: ToolLogoColor;
|
15 |
+
icon: ToolLogoIcon;
|
16 |
+
}
|
17 |
+
|
18 |
+
export let toolIds: string[] = [];
|
19 |
+
|
20 |
+
let selectedValues: ToolSuggestion[] = [];
|
21 |
+
|
22 |
+
onMount(async () => {
|
23 |
+
selectedValues = await Promise.all(
|
24 |
+
toolIds.map(async (id) => await fetch(`${base}/api/tools/${id}`).then((res) => res.json()))
|
25 |
+
);
|
26 |
+
|
27 |
+
await fetchSuggestions("");
|
28 |
+
});
|
29 |
+
|
30 |
+
let inputValue = "";
|
31 |
+
let maxValues = 3;
|
32 |
+
|
33 |
+
let suggestions: ToolSuggestion[] = [];
|
34 |
+
|
35 |
+
async function fetchSuggestions(query: string) {
|
36 |
+
suggestions = (await fetch(`${base}/api/tools/search?q=${query}`).then((res) =>
|
37 |
+
res.json()
|
38 |
+
)) satisfies ToolSuggestion[];
|
39 |
+
}
|
40 |
+
|
41 |
+
const debouncedFetch = debounce((query: string) => fetchSuggestions(query), 300);
|
42 |
+
|
43 |
+
function addValue(value: ToolSuggestion) {
|
44 |
+
if (selectedValues.length < maxValues && !selectedValues.includes(value)) {
|
45 |
+
selectedValues = [...selectedValues, value];
|
46 |
+
toolIds = [...toolIds, value._id];
|
47 |
+
inputValue = "";
|
48 |
+
suggestions = [];
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
function removeValue(id: ToolSuggestion["_id"]) {
|
53 |
+
selectedValues = selectedValues.filter((v) => v._id !== id);
|
54 |
+
toolIds = selectedValues.map((value) => value._id);
|
55 |
+
}
|
56 |
+
</script>
|
57 |
+
|
58 |
+
{#if selectedValues.length > 0}
|
59 |
+
<div class="flex flex-wrap items-center justify-center gap-2">
|
60 |
+
{#each selectedValues as value}
|
61 |
+
<div
|
62 |
+
class="flex items-center justify-center space-x-2 rounded border border-gray-300 bg-gray-200 px-2 py-1"
|
63 |
+
>
|
64 |
+
<ToolLogo color={value.color} icon={value.icon} size="sm" />
|
65 |
+
<div class="flex flex-col items-center justify-center py-1">
|
66 |
+
<a
|
67 |
+
href={`${base}/tools/${value._id}`}
|
68 |
+
target="_blank"
|
69 |
+
class="line-clamp-1 truncate font-semibold text-blue-600 hover:underline"
|
70 |
+
>{value.displayName}</a
|
71 |
+
>
|
72 |
+
{#if value.createdByName}
|
73 |
+
<p class="text-center text-xs text-gray-500">
|
74 |
+
Created by
|
75 |
+
<a class="underline" href="{base}/tools?user={value.createdByName}" target="_blank"
|
76 |
+
>{value.createdByName}</a
|
77 |
+
>
|
78 |
+
</p>
|
79 |
+
{:else}
|
80 |
+
<p class="text-center text-xs text-gray-500">Official HuggingChat tool</p>
|
81 |
+
{/if}
|
82 |
+
</div>
|
83 |
+
<button
|
84 |
+
on:click|stopPropagation|preventDefault={() => removeValue(value._id)}
|
85 |
+
class="text-lg text-gray-600"
|
86 |
+
>
|
87 |
+
<CarbonClose />
|
88 |
+
</button>
|
89 |
+
</div>
|
90 |
+
{/each}
|
91 |
+
</div>
|
92 |
+
{/if}
|
93 |
+
|
94 |
+
{#if selectedValues.length < maxValues}
|
95 |
+
<div class="group relative block">
|
96 |
+
<input
|
97 |
+
type="text"
|
98 |
+
bind:value={inputValue}
|
99 |
+
on:input={(ev) => {
|
100 |
+
inputValue = ev.currentTarget.value;
|
101 |
+
debouncedFetch(inputValue);
|
102 |
+
}}
|
103 |
+
disabled={selectedValues.length >= maxValues}
|
104 |
+
class="w-full rounded border border-gray-200 bg-gray-100 px-3 py-2"
|
105 |
+
class:opacity-50={selectedValues.length >= maxValues}
|
106 |
+
class:bg-gray-100={selectedValues.length >= maxValues}
|
107 |
+
placeholder="Type to search tools..."
|
108 |
+
/>
|
109 |
+
{#if suggestions.length > 0}
|
110 |
+
<div
|
111 |
+
class="invisible absolute z-10 mt-1 w-full rounded border border-gray-300 bg-white shadow-lg group-focus-within:visible"
|
112 |
+
>
|
113 |
+
{#each suggestions as suggestion}
|
114 |
+
<button
|
115 |
+
on:click|stopPropagation|preventDefault={() => addValue(suggestion)}
|
116 |
+
class="w-full cursor-pointer px-3 py-2 text-left hover:bg-blue-500 hover:text-white"
|
117 |
+
>
|
118 |
+
{suggestion.displayName}
|
119 |
+
</button>
|
120 |
+
{/each}
|
121 |
+
</div>
|
122 |
+
{/if}
|
123 |
+
</div>
|
124 |
+
{/if}
|
src/lib/components/ToolBadge.svelte
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import ToolLogo from "./ToolLogo.svelte";
|
3 |
+
import { base } from "$app/paths";
|
4 |
+
import { browser } from "$app/environment";
|
5 |
+
|
6 |
+
export let toolId: string;
|
7 |
+
</script>
|
8 |
+
|
9 |
+
<div
|
10 |
+
class="relative flex items-center justify-center space-x-2 rounded border border-gray-300 bg-gray-200 px-2 py-1"
|
11 |
+
>
|
12 |
+
{#if browser}
|
13 |
+
{#await fetch(`${base}/api/tools/${toolId}`).then((res) => res.json()) then value}
|
14 |
+
<ToolLogo color={value.color} icon={value.icon} size="sm" />
|
15 |
+
<div class="flex flex-col items-center justify-center py-1">
|
16 |
+
<a
|
17 |
+
href={`${base}/tools/${value._id}`}
|
18 |
+
target="_blank"
|
19 |
+
class="line-clamp-1 truncate font-semibold text-blue-600 hover:underline"
|
20 |
+
>{value.displayName}</a
|
21 |
+
>
|
22 |
+
{#if value.createdByName}
|
23 |
+
<p class="text-center text-xs text-gray-500">
|
24 |
+
Created by
|
25 |
+
<a class="underline" href="{base}/tools?user={value.createdByName}" target="_blank"
|
26 |
+
>{value.createdByName}</a
|
27 |
+
>
|
28 |
+
</p>
|
29 |
+
{:else}
|
30 |
+
<p class="text-center text-xs text-gray-500">Official HuggingChat tool</p>
|
31 |
+
{/if}
|
32 |
+
</div>
|
33 |
+
{/await}
|
34 |
+
{/if}
|
35 |
+
</div>
|
src/lib/components/ToolLogo.svelte
CHANGED
@@ -13,7 +13,7 @@
|
|
13 |
|
14 |
export let color: string;
|
15 |
export let icon: string;
|
16 |
-
export let size: "md" | "lg" = "md";
|
17 |
|
18 |
$: gradientColor = (() => {
|
19 |
switch (color) {
|
@@ -72,6 +72,8 @@
|
|
72 |
|
73 |
$: sizeClass = (() => {
|
74 |
switch (size) {
|
|
|
|
|
75 |
case "md":
|
76 |
return "size-14";
|
77 |
case "lg":
|
|
|
13 |
|
14 |
export let color: string;
|
15 |
export let icon: string;
|
16 |
+
export let size: "sm" | "md" | "lg" = "md";
|
17 |
|
18 |
$: gradientColor = (() => {
|
19 |
switch (color) {
|
|
|
72 |
|
73 |
$: sizeClass = (() => {
|
74 |
switch (size) {
|
75 |
+
case "sm":
|
76 |
+
return "size-8";
|
77 |
case "md":
|
78 |
return "size-14";
|
79 |
case "lg":
|
src/lib/components/chat/AssistantIntroduction.svelte
CHANGED
@@ -12,6 +12,7 @@
|
|
12 |
import CarbonCheckmark from "~icons/carbon/checkmark";
|
13 |
import CarbonRenew from "~icons/carbon/renew";
|
14 |
import CarbonUserMultiple from "~icons/carbon/user-multiple";
|
|
|
15 |
|
16 |
import { share } from "$lib/utils/share";
|
17 |
import { env as envPublic } from "$env/dynamic/public";
|
@@ -30,6 +31,7 @@
|
|
30 |
| "_id"
|
31 |
| "description"
|
32 |
| "userCount"
|
|
|
33 |
>;
|
34 |
|
35 |
const dispatch = createEventDispatcher<{ message: string }>();
|
@@ -83,6 +85,15 @@
|
|
83 |
</p>
|
84 |
{/if}
|
85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
{#if hasRag}
|
87 |
<div
|
88 |
class="flex h-5 w-fit items-center gap-1 rounded-full bg-blue-500/10 pl-1 pr-2 text-xs"
|
|
|
12 |
import CarbonCheckmark from "~icons/carbon/checkmark";
|
13 |
import CarbonRenew from "~icons/carbon/renew";
|
14 |
import CarbonUserMultiple from "~icons/carbon/user-multiple";
|
15 |
+
import CarbonTools from "~icons/carbon/tools";
|
16 |
|
17 |
import { share } from "$lib/utils/share";
|
18 |
import { env as envPublic } from "$env/dynamic/public";
|
|
|
31 |
| "_id"
|
32 |
| "description"
|
33 |
| "userCount"
|
34 |
+
| "tools"
|
35 |
>;
|
36 |
|
37 |
const dispatch = createEventDispatcher<{ message: string }>();
|
|
|
85 |
</p>
|
86 |
{/if}
|
87 |
|
88 |
+
{#if assistant?.tools?.length}
|
89 |
+
<div
|
90 |
+
class="flex h-5 w-fit items-center gap-1 rounded-full bg-purple-500/10 pl-1 pr-2 text-xs"
|
91 |
+
title="This assistant uses the websearch."
|
92 |
+
>
|
93 |
+
<CarbonTools class="text-sm text-purple-600" />
|
94 |
+
Has tools
|
95 |
+
</div>
|
96 |
+
{/if}
|
97 |
{#if hasRag}
|
98 |
<div
|
99 |
class="flex h-5 w-fit items-center gap-1 rounded-full bg-blue-500/10 pl-1 pr-2 text-xs"
|
src/lib/server/models.ts
CHANGED
@@ -110,7 +110,6 @@ async function getChatPromptRender(
|
|
110 |
];
|
111 |
}
|
112 |
|
113 |
-
logger.info({ formattedMessages });
|
114 |
if (toolResults?.length) {
|
115 |
// todo: should update the command r+ tokenizer to support system messages at any location
|
116 |
// or use the `rag` mode without the citations
|
|
|
110 |
];
|
111 |
}
|
112 |
|
|
|
113 |
if (toolResults?.length) {
|
114 |
// todo: should update the command r+ tokenizer to support system messages at any location
|
115 |
// or use the `rag` mode without the citations
|
src/lib/server/textGeneration/assistant.ts
CHANGED
@@ -31,9 +31,9 @@ export async function processPreprompt(preprompt: string) {
|
|
31 |
|
32 |
export async function getAssistantById(id?: ObjectId) {
|
33 |
return collections.assistants
|
34 |
-
.findOne<Pick<Assistant, "rag" | "dynamicPrompt" | "generateSettings">>(
|
35 |
{ _id: id },
|
36 |
-
{ projection: { rag: 1, dynamicPrompt: 1, generateSettings: 1 } }
|
37 |
)
|
38 |
.then((a) => a ?? undefined);
|
39 |
}
|
|
|
31 |
|
32 |
export async function getAssistantById(id?: ObjectId) {
|
33 |
return collections.assistants
|
34 |
+
.findOne<Pick<Assistant, "rag" | "dynamicPrompt" | "generateSettings" | "tools">>(
|
35 |
{ _id: id },
|
36 |
+
{ projection: { rag: 1, dynamicPrompt: 1, generateSettings: 1, tools: 1 } }
|
37 |
)
|
38 |
.then((a) => a ?? undefined);
|
39 |
}
|
src/lib/server/textGeneration/index.ts
CHANGED
@@ -8,7 +8,7 @@ import {
|
|
8 |
getAssistantById,
|
9 |
processPreprompt,
|
10 |
} from "./assistant";
|
11 |
-
import {
|
12 |
import type { WebSearch } from "$lib/types/WebSearch";
|
13 |
import {
|
14 |
type MessageUpdate,
|
@@ -77,8 +77,8 @@ async function* textGenerationWithoutTitle(
|
|
77 |
|
78 |
let toolResults: ToolResult[] = [];
|
79 |
|
80 |
-
if (model.tools
|
81 |
-
const tools = await
|
82 |
const toolCallsRequired = tools.some((tool) => !toolHasName("directly_answer", tool));
|
83 |
if (toolCallsRequired) toolResults = yield* runTools(ctx, tools, preprompt);
|
84 |
}
|
|
|
8 |
getAssistantById,
|
9 |
processPreprompt,
|
10 |
} from "./assistant";
|
11 |
+
import { getTools, runTools } from "./tools";
|
12 |
import type { WebSearch } from "$lib/types/WebSearch";
|
13 |
import {
|
14 |
type MessageUpdate,
|
|
|
77 |
|
78 |
let toolResults: ToolResult[] = [];
|
79 |
|
80 |
+
if (model.tools) {
|
81 |
+
const tools = await getTools(toolsPreference, ctx.assistant);
|
82 |
const toolCallsRequired = tools.some((tool) => !toolHasName("directly_answer", tool));
|
83 |
if (toolCallsRequired) toolResults = yield* runTools(ctx, tools, preprompt);
|
84 |
}
|
src/lib/server/textGeneration/tools.ts
CHANGED
@@ -20,27 +20,36 @@ import { stringifyError } from "$lib/utils/stringifyError";
|
|
20 |
import { collections } from "../database";
|
21 |
import { ObjectId } from "mongodb";
|
22 |
import type { Message } from "$lib/types/Message";
|
|
|
23 |
|
24 |
-
export async function
|
25 |
toolsPreference: Array<string>,
|
26 |
-
|
27 |
): Promise<Tool[]> {
|
28 |
-
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
// filter based on tool preferences, add the tools that are on by default
|
32 |
const activeConfigTools = toolFromConfigs.filter((el) => {
|
33 |
-
if (el.isLocked && el.isOnByDefault) return true;
|
34 |
-
return
|
35 |
});
|
36 |
|
37 |
-
// find tool where the id is in
|
38 |
const activeCommunityTools = await collections.tools
|
39 |
.find({
|
40 |
-
_id: { $in:
|
41 |
})
|
42 |
.toArray()
|
43 |
.then((el) => el.map((el) => ({ ...el, call: getCallMethod(el) })));
|
|
|
44 |
return [...activeConfigTools, ...activeCommunityTools];
|
45 |
}
|
46 |
|
|
|
20 |
import { collections } from "../database";
|
21 |
import { ObjectId } from "mongodb";
|
22 |
import type { Message } from "$lib/types/Message";
|
23 |
+
import type { Assistant } from "$lib/types/Assistant";
|
24 |
|
25 |
+
export async function getTools(
|
26 |
toolsPreference: Array<string>,
|
27 |
+
assistant: Pick<Assistant, "tools"> | undefined
|
28 |
): Promise<Tool[]> {
|
29 |
+
let preferences = toolsPreference;
|
30 |
+
|
31 |
+
if (assistant) {
|
32 |
+
if (assistant?.tools?.length) {
|
33 |
+
preferences = assistant.tools;
|
34 |
+
} else {
|
35 |
+
return [directlyAnswer, websearch];
|
36 |
+
}
|
37 |
+
}
|
38 |
|
39 |
// filter based on tool preferences, add the tools that are on by default
|
40 |
const activeConfigTools = toolFromConfigs.filter((el) => {
|
41 |
+
if (el.isLocked && el.isOnByDefault && !assistant) return true;
|
42 |
+
return preferences?.includes(el._id.toString()) ?? (el.isOnByDefault && !assistant);
|
43 |
});
|
44 |
|
45 |
+
// find tool where the id is in preferences
|
46 |
const activeCommunityTools = await collections.tools
|
47 |
.find({
|
48 |
+
_id: { $in: preferences.map((el) => new ObjectId(el)) },
|
49 |
})
|
50 |
.toArray()
|
51 |
.then((el) => el.map((el) => ({ ...el, call: getCallMethod(el) })));
|
52 |
+
|
53 |
return [...activeConfigTools, ...activeCommunityTools];
|
54 |
}
|
55 |
|
src/lib/server/textGeneration/types.ts
CHANGED
@@ -9,7 +9,7 @@ export interface TextGenerationContext {
|
|
9 |
endpoint: Endpoint;
|
10 |
conv: Conversation;
|
11 |
messages: Message[];
|
12 |
-
assistant?: Pick<Assistant, "rag" | "dynamicPrompt" | "generateSettings">;
|
13 |
isContinue: boolean;
|
14 |
webSearch: boolean;
|
15 |
toolsPreference: Array<string>;
|
|
|
9 |
endpoint: Endpoint;
|
10 |
conv: Conversation;
|
11 |
messages: Message[];
|
12 |
+
assistant?: Pick<Assistant, "rag" | "dynamicPrompt" | "generateSettings" | "tools">;
|
13 |
isContinue: boolean;
|
14 |
webSearch: boolean;
|
15 |
toolsPreference: Array<string>;
|
src/lib/types/Assistant.ts
CHANGED
@@ -28,6 +28,7 @@ export interface Assistant extends Timestamps {
|
|
28 |
dynamicPrompt?: boolean;
|
29 |
searchTokens: string[];
|
30 |
last24HoursCount: number;
|
|
|
31 |
}
|
32 |
|
33 |
// eslint-disable-next-line no-shadow
|
|
|
28 |
dynamicPrompt?: boolean;
|
29 |
searchTokens: string[];
|
30 |
last24HoursCount: number;
|
31 |
+
tools?: string[];
|
32 |
}
|
33 |
|
34 |
// eslint-disable-next-line no-shadow
|
src/routes/api/tools/[toolId]/+server.ts
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { collections } from "$lib/server/database.js";
|
2 |
+
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
3 |
+
import type { CommunityToolDB } from "$lib/types/Tool.js";
|
4 |
+
import { ObjectId } from "mongodb";
|
5 |
+
|
6 |
+
export async function GET({ params, locals }) {
|
7 |
+
// XXX: feature_flag_tools
|
8 |
+
if (!locals.user?.isEarlyAccess) {
|
9 |
+
return new Response("Not early access", { status: 403 });
|
10 |
+
}
|
11 |
+
|
12 |
+
const toolId = params.toolId;
|
13 |
+
|
14 |
+
try {
|
15 |
+
const configTool = toolFromConfigs.find((el) => el._id.toString() === toolId);
|
16 |
+
if (configTool) {
|
17 |
+
return Response.json({
|
18 |
+
_id: toolId,
|
19 |
+
displayName: configTool.displayName,
|
20 |
+
color: configTool.color,
|
21 |
+
icon: configTool.icon,
|
22 |
+
createdByName: undefined,
|
23 |
+
});
|
24 |
+
} else {
|
25 |
+
// try community tools
|
26 |
+
const tool = await collections.tools
|
27 |
+
.findOne<CommunityToolDB>({ _id: new ObjectId(toolId) })
|
28 |
+
.then((tool) =>
|
29 |
+
tool
|
30 |
+
? {
|
31 |
+
_id: tool._id.toString(),
|
32 |
+
displayName: tool.displayName,
|
33 |
+
color: tool.color,
|
34 |
+
icon: tool.icon,
|
35 |
+
createdByName: tool.createdByName,
|
36 |
+
}
|
37 |
+
: undefined
|
38 |
+
);
|
39 |
+
|
40 |
+
if (!tool) {
|
41 |
+
return new Response(`Tool "${toolId}" not found`, { status: 404 });
|
42 |
+
}
|
43 |
+
|
44 |
+
return Response.json(tool);
|
45 |
+
}
|
46 |
+
} catch (e) {
|
47 |
+
return new Response(`Tool "${toolId}" not found`, { status: 404 });
|
48 |
+
}
|
49 |
+
}
|
src/routes/api/tools/search/+server.ts
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { collections } from "$lib/server/database.js";
|
2 |
+
import { toolFromConfigs } from "$lib/server/tools/index.js";
|
3 |
+
import type { BaseTool, CommunityToolDB } from "$lib/types/Tool.js";
|
4 |
+
import { generateQueryTokens, generateSearchTokens } from "$lib/utils/searchTokens.js";
|
5 |
+
import type { Filter } from "mongodb";
|
6 |
+
|
7 |
+
export async function GET({ url, locals }) {
|
8 |
+
// XXX: feature_flag_tools
|
9 |
+
if (!locals.user?.isEarlyAccess) {
|
10 |
+
return new Response("Not early access", { status: 403 });
|
11 |
+
}
|
12 |
+
|
13 |
+
const query = url.searchParams.get("q")?.trim() ?? null;
|
14 |
+
const queryTokens = !!query && generateQueryTokens(query);
|
15 |
+
|
16 |
+
const filter: Filter<CommunityToolDB> = {
|
17 |
+
...(queryTokens && { searchTokens: { $all: queryTokens } }),
|
18 |
+
featured: true,
|
19 |
+
};
|
20 |
+
|
21 |
+
const matchingCommunityTools = await collections.tools
|
22 |
+
.find(filter)
|
23 |
+
.project<Pick<BaseTool, "_id" | "displayName" | "color" | "icon">>({
|
24 |
+
_id: 1,
|
25 |
+
displayName: 1,
|
26 |
+
color: 1,
|
27 |
+
icon: 1,
|
28 |
+
createdByName: 1,
|
29 |
+
})
|
30 |
+
.sort({ useCount: -1 })
|
31 |
+
.limit(5)
|
32 |
+
.toArray();
|
33 |
+
|
34 |
+
const matchingConfigTools = toolFromConfigs
|
35 |
+
.filter((tool) => !tool?.isHidden)
|
36 |
+
.filter((tool) => {
|
37 |
+
if (queryTokens) {
|
38 |
+
return generateSearchTokens(tool.displayName).some((token) =>
|
39 |
+
queryTokens.some((queryToken) => queryToken.test(token))
|
40 |
+
);
|
41 |
+
}
|
42 |
+
return true;
|
43 |
+
})
|
44 |
+
.map((tool) => ({
|
45 |
+
_id: tool._id,
|
46 |
+
displayName: tool.displayName,
|
47 |
+
color: tool.color,
|
48 |
+
icon: tool.icon,
|
49 |
+
createdByName: undefined,
|
50 |
+
}));
|
51 |
+
|
52 |
+
const tools = [...matchingConfigTools, ...matchingCommunityTools] satisfies Array<
|
53 |
+
Pick<BaseTool, "_id" | "displayName" | "color" | "icon"> & { createdByName?: string }
|
54 |
+
>;
|
55 |
+
|
56 |
+
return Response.json(tools.map((tool) => ({ ...tool, _id: tool._id.toString() })).slice(0, 5));
|
57 |
+
}
|
src/routes/assistants/+page.svelte
CHANGED
@@ -15,6 +15,8 @@
|
|
15 |
import CarbonEarthAmerica from "~icons/carbon/earth-americas-filled";
|
16 |
import CarbonUserMultiple from "~icons/carbon/user-multiple";
|
17 |
import CarbonSearch from "~icons/carbon/search";
|
|
|
|
|
18 |
import Pagination from "$lib/components/Pagination.svelte";
|
19 |
import { formatUserCount } from "$lib/utils/formatUserCount";
|
20 |
import { getHref } from "$lib/utils/getHref";
|
@@ -246,14 +248,24 @@
|
|
246 |
</div>
|
247 |
{/if}
|
248 |
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
257 |
|
258 |
{#if assistant.avatar}
|
259 |
<img
|
|
|
15 |
import CarbonEarthAmerica from "~icons/carbon/earth-americas-filled";
|
16 |
import CarbonUserMultiple from "~icons/carbon/user-multiple";
|
17 |
import CarbonSearch from "~icons/carbon/search";
|
18 |
+
import CarbonTools from "~icons/carbon/tools";
|
19 |
+
|
20 |
import Pagination from "$lib/components/Pagination.svelte";
|
21 |
import { formatUserCount } from "$lib/utils/formatUserCount";
|
22 |
import { getHref } from "$lib/utils/getHref";
|
|
|
248 |
</div>
|
249 |
{/if}
|
250 |
|
251 |
+
<div class="absolute left-3 top-3 flex items-center gap-1 text-xs text-gray-400">
|
252 |
+
{#if assistant.tools?.length}
|
253 |
+
<div
|
254 |
+
class="grid size-5 place-items-center rounded-full bg-purple-500/10"
|
255 |
+
title="This assistant uses the websearch."
|
256 |
+
>
|
257 |
+
<CarbonTools class="text-xs text-purple-600" />
|
258 |
+
</div>
|
259 |
+
{/if}
|
260 |
+
{#if hasRag}
|
261 |
+
<div
|
262 |
+
class="grid size-5 place-items-center rounded-full bg-blue-500/10"
|
263 |
+
title="This assistant uses the websearch."
|
264 |
+
>
|
265 |
+
<IconInternet classNames="text-sm text-blue-600" />
|
266 |
+
</div>
|
267 |
+
{/if}
|
268 |
+
</div>
|
269 |
|
270 |
{#if assistant.avatar}
|
271 |
<img
|
src/routes/conversation/[id]/+server.ts
CHANGED
@@ -422,6 +422,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
|
|
422 |
|
423 |
let hasError = false;
|
424 |
const initialMessageContent = messageToWriteTo.content;
|
|
|
425 |
try {
|
426 |
const ctx: TextGenerationContext = {
|
427 |
model,
|
|
|
422 |
|
423 |
let hasError = false;
|
424 |
const initialMessageContent = messageToWriteTo.content;
|
425 |
+
|
426 |
try {
|
427 |
const ctx: TextGenerationContext = {
|
428 |
model,
|
src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte
CHANGED
@@ -12,9 +12,11 @@
|
|
12 |
import CarbonFlag from "~icons/carbon/flag";
|
13 |
import CarbonLink from "~icons/carbon/link";
|
14 |
import CarbonStar from "~icons/carbon/star";
|
|
|
15 |
import CopyToClipBoardBtn from "$lib/components/CopyToClipBoardBtn.svelte";
|
16 |
import ReportModal from "./ReportModal.svelte";
|
17 |
import IconInternet from "$lib/components/icons/IconInternet.svelte";
|
|
|
18 |
|
19 |
export let data: PageData;
|
20 |
|
@@ -214,6 +216,27 @@
|
|
214 |
{/if}
|
215 |
</div>
|
216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
{#if hasRag}
|
218 |
<div class="mt-4">
|
219 |
<div class="mb-1 flex items-center gap-1">
|
|
|
12 |
import CarbonFlag from "~icons/carbon/flag";
|
13 |
import CarbonLink from "~icons/carbon/link";
|
14 |
import CarbonStar from "~icons/carbon/star";
|
15 |
+
import CarbonTools from "~icons/carbon/tools";
|
16 |
import CopyToClipBoardBtn from "$lib/components/CopyToClipBoardBtn.svelte";
|
17 |
import ReportModal from "./ReportModal.svelte";
|
18 |
import IconInternet from "$lib/components/icons/IconInternet.svelte";
|
19 |
+
import ToolBadge from "$lib/components/ToolBadge.svelte";
|
20 |
|
21 |
export let data: PageData;
|
22 |
|
|
|
216 |
{/if}
|
217 |
</div>
|
218 |
|
219 |
+
{#if assistant?.tools?.length}
|
220 |
+
<div class="mt-4">
|
221 |
+
<div class="mb-1 flex items-center gap-1">
|
222 |
+
<span
|
223 |
+
class="inline-grid size-5 place-items-center rounded-full bg-purple-500/10"
|
224 |
+
title="This assistant uses the websearch."
|
225 |
+
>
|
226 |
+
<CarbonTools class="text-xs text-purple-600" />
|
227 |
+
</span>
|
228 |
+
<h2 class="font-semibold">Tools</h2>
|
229 |
+
</div>
|
230 |
+
<p class="w-full text-sm text-gray-500">
|
231 |
+
This Assistant has access to the following tools:
|
232 |
+
</p>
|
233 |
+
<ul class="mr-2 mt-2 flex flex-wrap gap-2.5 text-sm text-gray-800">
|
234 |
+
{#each assistant.tools as tool}
|
235 |
+
<ToolBadge toolId={tool} />
|
236 |
+
{/each}
|
237 |
+
</ul>
|
238 |
+
</div>
|
239 |
+
{/if}
|
240 |
{#if hasRag}
|
241 |
<div class="mt-4">
|
242 |
<div class="mb-1 flex items-center gap-1">
|
src/routes/settings/(nav)/assistants/[assistantId]/edit/+page.server.ts
CHANGED
@@ -10,6 +10,7 @@ import { sha256 } from "$lib/utils/sha256";
|
|
10 |
import sharp from "sharp";
|
11 |
import { parseStringToList } from "$lib/utils/parseStringToList";
|
12 |
import { generateSearchTokens } from "$lib/utils/searchTokens";
|
|
|
13 |
|
14 |
const newAsssistantSchema = z.object({
|
15 |
name: z.string().min(1),
|
@@ -39,6 +40,21 @@ const newAsssistantSchema = z.object({
|
|
39 |
top_k: z
|
40 |
.union([z.literal(""), z.coerce.number().min(5).max(100)])
|
41 |
.transform((v) => (v === "" ? undefined : v)),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
});
|
43 |
|
44 |
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
@@ -74,7 +90,7 @@ export const actions: Actions = {
|
|
74 |
|
75 |
const formData = Object.fromEntries(await request.formData());
|
76 |
|
77 |
-
const parse = newAsssistantSchema.
|
78 |
|
79 |
if (!parse.success) {
|
80 |
// Loop through the errors array and create a custom errors array
|
@@ -155,6 +171,8 @@ export const actions: Actions = {
|
|
155 |
allowedDomains: parse.data.ragDomainList,
|
156 |
allowAllDomains: parse.data.ragAllowAll,
|
157 |
},
|
|
|
|
|
158 |
dynamicPrompt: parse.data.dynamicPrompt,
|
159 |
searchTokens: generateSearchTokens(parse.data.name),
|
160 |
generateSettings: {
|
|
|
10 |
import sharp from "sharp";
|
11 |
import { parseStringToList } from "$lib/utils/parseStringToList";
|
12 |
import { generateSearchTokens } from "$lib/utils/searchTokens";
|
13 |
+
import { toolFromConfigs } from "$lib/server/tools";
|
14 |
|
15 |
const newAsssistantSchema = z.object({
|
16 |
name: z.string().min(1),
|
|
|
40 |
top_k: z
|
41 |
.union([z.literal(""), z.coerce.number().min(5).max(100)])
|
42 |
.transform((v) => (v === "" ? undefined : v)),
|
43 |
+
tools: z
|
44 |
+
.string()
|
45 |
+
.optional()
|
46 |
+
.transform((v) => (v ? v.split(",") : []))
|
47 |
+
.transform(async (v) => [
|
48 |
+
...(await collections.tools
|
49 |
+
.find({ _id: { $in: v.map((toolId) => new ObjectId(toolId)) } })
|
50 |
+
.project({ _id: 1 })
|
51 |
+
.toArray()
|
52 |
+
.then((tools) => tools.map((tool) => tool._id.toString()))),
|
53 |
+
...toolFromConfigs
|
54 |
+
.filter((el) => (v ?? []).includes(el._id.toString()))
|
55 |
+
.map((el) => el._id.toString()),
|
56 |
+
])
|
57 |
+
.optional(),
|
58 |
});
|
59 |
|
60 |
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
|
|
90 |
|
91 |
const formData = Object.fromEntries(await request.formData());
|
92 |
|
93 |
+
const parse = await newAsssistantSchema.safeParseAsync(formData);
|
94 |
|
95 |
if (!parse.success) {
|
96 |
// Loop through the errors array and create a custom errors array
|
|
|
171 |
allowedDomains: parse.data.ragDomainList,
|
172 |
allowAllDomains: parse.data.ragAllowAll,
|
173 |
},
|
174 |
+
// XXX: feature_flag_tools
|
175 |
+
tools: locals.user?.isEarlyAccess ? parse.data.tools : undefined,
|
176 |
dynamicPrompt: parse.data.dynamicPrompt,
|
177 |
searchTokens: generateSearchTokens(parse.data.name),
|
178 |
generateSettings: {
|
src/routes/settings/(nav)/assistants/[assistantId]/edit/[email protected]
CHANGED
@@ -6,7 +6,7 @@
|
|
6 |
export let data: PageData;
|
7 |
export let form: ActionData;
|
8 |
|
9 |
-
|
10 |
</script>
|
11 |
|
12 |
<AssistantSettings bind:form {assistant} models={data.models} />
|
|
|
6 |
export let data: PageData;
|
7 |
export let form: ActionData;
|
8 |
|
9 |
+
let assistant = data.assistants.find((el) => el._id.toString() === $page.params.assistantId);
|
10 |
</script>
|
11 |
|
12 |
<AssistantSettings bind:form {assistant} models={data.models} />
|
src/routes/settings/(nav)/assistants/new/+page.server.ts
CHANGED
@@ -10,6 +10,7 @@ import sharp from "sharp";
|
|
10 |
import { parseStringToList } from "$lib/utils/parseStringToList";
|
11 |
import { usageLimits } from "$lib/server/usageLimits";
|
12 |
import { generateSearchTokens } from "$lib/utils/searchTokens";
|
|
|
13 |
|
14 |
const newAsssistantSchema = z.object({
|
15 |
name: z.string().min(1),
|
@@ -39,6 +40,21 @@ const newAsssistantSchema = z.object({
|
|
39 |
top_k: z
|
40 |
.union([z.literal(""), z.coerce.number().min(5).max(100)])
|
41 |
.transform((v) => (v === "" ? undefined : v)),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
});
|
43 |
|
44 |
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
@@ -62,7 +78,7 @@ export const actions: Actions = {
|
|
62 |
default: async ({ request, locals }) => {
|
63 |
const formData = Object.fromEntries(await request.formData());
|
64 |
|
65 |
-
const parse = newAsssistantSchema.
|
66 |
|
67 |
if (!parse.success) {
|
68 |
// Loop through the errors array and create a custom errors array
|
@@ -126,6 +142,8 @@ export const actions: Actions = {
|
|
126 |
createdById,
|
127 |
createdByName: locals.user?.username ?? locals.user?.name,
|
128 |
...parse.data,
|
|
|
|
|
129 |
exampleInputs,
|
130 |
avatar: hash,
|
131 |
createdAt: new Date(),
|
|
|
10 |
import { parseStringToList } from "$lib/utils/parseStringToList";
|
11 |
import { usageLimits } from "$lib/server/usageLimits";
|
12 |
import { generateSearchTokens } from "$lib/utils/searchTokens";
|
13 |
+
import { toolFromConfigs } from "$lib/server/tools";
|
14 |
|
15 |
const newAsssistantSchema = z.object({
|
16 |
name: z.string().min(1),
|
|
|
40 |
top_k: z
|
41 |
.union([z.literal(""), z.coerce.number().min(5).max(100)])
|
42 |
.transform((v) => (v === "" ? undefined : v)),
|
43 |
+
tools: z
|
44 |
+
.string()
|
45 |
+
.optional()
|
46 |
+
.transform((v) => (v ? v.split(",") : []))
|
47 |
+
.transform(async (v) => [
|
48 |
+
...(await collections.tools
|
49 |
+
.find({ _id: { $in: v.map((toolId) => new ObjectId(toolId)) } })
|
50 |
+
.project({ _id: 1 })
|
51 |
+
.toArray()
|
52 |
+
.then((tools) => tools.map((tool) => tool._id.toString()))),
|
53 |
+
...toolFromConfigs
|
54 |
+
.filter((el) => (v ?? []).includes(el._id.toString()))
|
55 |
+
.map((el) => el._id.toString()),
|
56 |
+
])
|
57 |
+
.optional(),
|
58 |
});
|
59 |
|
60 |
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
|
|
78 |
default: async ({ request, locals }) => {
|
79 |
const formData = Object.fromEntries(await request.formData());
|
80 |
|
81 |
+
const parse = await newAsssistantSchema.safeParseAsync(formData);
|
82 |
|
83 |
if (!parse.success) {
|
84 |
// Loop through the errors array and create a custom errors array
|
|
|
142 |
createdById,
|
143 |
createdByName: locals.user?.username ?? locals.user?.name,
|
144 |
...parse.data,
|
145 |
+
// XXX: feature_flag_tools
|
146 |
+
tools: locals.user?.isEarlyAccess ? parse.data.tools : undefined,
|
147 |
exampleInputs,
|
148 |
avatar: hash,
|
149 |
createdAt: new Date(),
|