Spaces:
Build error
Build error
Dynamic system instructions (#949)
Browse files* Dynamic system instructions
* disable tag rendering if the feature is disabled
* make time & date tags bold
* fix bug enabling dynamic prompt on assistant creation
* Move fetching at prompt building time
* get rid of date & time tags
* use `url=` for tags
* add env flag check
* Add more detailed errors in prompt
* wording
* token counter
* modal update
* wording
* hide disabled if dynamic prompt is enabled
* add template variables parsing
* regex update
* same regex
* regex again
* sys prompt max height
* rm unused
* Always use absolute URL in links
* trying something
* Revert "trying something"
This reverts commit e30ab33f801fad31cbcaa7b89c47c79d4aca86a6.
* wording
* remove debug log
* last wording tweak
---------
Co-authored-by: Victor Mustar <[email protected]>
- src/lib/components/AssistantSettings.svelte +97 -53
- src/lib/components/TokensCounter.svelte +15 -3
- src/lib/components/chat/AssistantIntroduction.svelte +3 -1
- src/lib/types/Assistant.ts +1 -0
- src/routes/assistants/+page.svelte +2 -1
- src/routes/conversation/+server.ts +12 -12
- src/routes/conversation/[id]/+server.ts +51 -11
- src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte +41 -7
- src/routes/settings/(nav)/assistants/[assistantId]/edit/+page.server.ts +2 -0
- src/routes/settings/(nav)/assistants/new/+page.server.ts +2 -0
- src/routes/settings/+layout.svelte +1 -1
src/lib/components/AssistantSettings.svelte
CHANGED
@@ -12,6 +12,7 @@
|
|
12 |
|
13 |
import { useSettingsStore } from "$lib/stores/settings";
|
14 |
import { isHuggingChat } from "$lib/utils/isHuggingChat";
|
|
|
15 |
import TokensCounter from "./TokensCounter.svelte";
|
16 |
|
17 |
type ActionData = {
|
@@ -33,6 +34,7 @@
|
|
33 |
let modelId =
|
34 |
assistant?.modelId ?? models.find((_model) => _model.id === $settings.activeModel)?.name;
|
35 |
let systemPrompt = assistant?.preprompt ?? "";
|
|
|
36 |
|
37 |
let compress: typeof readAndCompressImage | null = null;
|
38 |
|
@@ -84,6 +86,9 @@
|
|
84 |
: (assistant?.rag?.allowedDomains?.length ?? 0) > 0
|
85 |
? "domains"
|
86 |
: false;
|
|
|
|
|
|
|
87 |
</script>
|
88 |
|
89 |
<form
|
@@ -142,10 +147,10 @@
|
|
142 |
>
|
143 |
{#if assistant}
|
144 |
<h2 class="text-xl font-semibold">
|
145 |
-
Edit {assistant?.name ?? "assistant"}
|
146 |
</h2>
|
147 |
<p class="mb-6 text-sm text-gray-500">
|
148 |
-
Modifying an existing assistant will propagate
|
149 |
</p>
|
150 |
{:else}
|
151 |
<h2 class="text-xl font-semibold">Create new assistant</h2>
|
@@ -222,7 +227,7 @@
|
|
222 |
<input
|
223 |
name="name"
|
224 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
225 |
-
placeholder="
|
226 |
value={assistant?.name ?? ""}
|
227 |
/>
|
228 |
<p class="text-xs text-red-500">{getError("name", form)}</p>
|
@@ -260,41 +265,42 @@
|
|
260 |
|
261 |
<label>
|
262 |
<div class="mb-1 font-semibold">User start messages</div>
|
263 |
-
<div class="
|
264 |
<input
|
265 |
name="exampleInput1"
|
|
|
266 |
bind:value={inputMessage1}
|
267 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
268 |
/>
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
/>
|
289 |
-
{/if}
|
290 |
</div>
|
291 |
<p class="text-xs text-red-500">{getError("inputMessage1", form)}</p>
|
292 |
</label>
|
293 |
{#if $page.data.enableAssistantsRAG}
|
294 |
<div class="mb-4 flex flex-col flex-nowrap">
|
295 |
<span class="mt-2 text-smd font-semibold"
|
296 |
-
>Internet access
|
297 |
-
|
|
|
|
|
298 |
>Experimental</span
|
299 |
>
|
300 |
|
@@ -316,10 +322,11 @@
|
|
316 |
name="ragMode"
|
317 |
value={false}
|
318 |
/>
|
319 |
-
<span class="my-2 text-sm" class:font-semibold={!ragMode}>
|
320 |
{#if !ragMode}
|
321 |
<span class="block text-xs text-gray-500">
|
322 |
-
Assistant
|
|
|
323 |
</span>
|
324 |
{/if}
|
325 |
</label>
|
@@ -391,16 +398,52 @@
|
|
391 |
/>
|
392 |
<p class="text-xs text-red-500">{getError("ragLinkList", form)}</p>
|
393 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
394 |
</div>
|
395 |
{/if}
|
396 |
</div>
|
397 |
|
398 |
<div class="col-span-1 flex h-full flex-col">
|
399 |
-
<
|
400 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
401 |
<textarea
|
402 |
name="preprompt"
|
403 |
-
class="flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
|
404 |
placeholder="You'll act as..."
|
405 |
bind:value={systemPrompt}
|
406 |
/>
|
@@ -408,35 +451,36 @@
|
|
408 |
{@const model = models.find((_model) => _model.id === modelId)}
|
409 |
{#if model?.tokenizer && systemPrompt}
|
410 |
<TokensCounter
|
411 |
-
classNames="absolute bottom-
|
412 |
prompt={systemPrompt}
|
413 |
modelTokenizer={model.tokenizer}
|
414 |
truncate={model?.parameters?.truncate}
|
415 |
/>
|
416 |
{/if}
|
417 |
{/if}
|
|
|
|
|
418 |
</div>
|
419 |
-
<p class="text-xs text-red-500">{getError("preprompt", form)}</p>
|
420 |
</div>
|
421 |
-
</div>
|
422 |
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
|
|
441 |
</div>
|
442 |
</form>
|
|
|
12 |
|
13 |
import { useSettingsStore } from "$lib/stores/settings";
|
14 |
import { isHuggingChat } from "$lib/utils/isHuggingChat";
|
15 |
+
import IconInternet from "./icons/IconInternet.svelte";
|
16 |
import TokensCounter from "./TokensCounter.svelte";
|
17 |
|
18 |
type ActionData = {
|
|
|
34 |
let modelId =
|
35 |
assistant?.modelId ?? models.find((_model) => _model.id === $settings.activeModel)?.name;
|
36 |
let systemPrompt = assistant?.preprompt ?? "";
|
37 |
+
let dynamicPrompt = assistant?.dynamicPrompt ?? false;
|
38 |
|
39 |
let compress: typeof readAndCompressImage | null = null;
|
40 |
|
|
|
86 |
: (assistant?.rag?.allowedDomains?.length ?? 0) > 0
|
87 |
? "domains"
|
88 |
: false;
|
89 |
+
|
90 |
+
const regex = /{{\s?url=(.+?)\s?}}/g;
|
91 |
+
$: templateVariables = [...systemPrompt.matchAll(regex)].map((match) => match[1]);
|
92 |
</script>
|
93 |
|
94 |
<form
|
|
|
147 |
>
|
148 |
{#if assistant}
|
149 |
<h2 class="text-xl font-semibold">
|
150 |
+
Edit Assistant: {assistant?.name ?? "assistant"}
|
151 |
</h2>
|
152 |
<p class="mb-6 text-sm text-gray-500">
|
153 |
+
Modifying an existing assistant will propagate the changes to all users.
|
154 |
</p>
|
155 |
{:else}
|
156 |
<h2 class="text-xl font-semibold">Create new assistant</h2>
|
|
|
227 |
<input
|
228 |
name="name"
|
229 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
230 |
+
placeholder="Assistant Name"
|
231 |
value={assistant?.name ?? ""}
|
232 |
/>
|
233 |
<p class="text-xs text-red-500">{getError("name", form)}</p>
|
|
|
265 |
|
266 |
<label>
|
267 |
<div class="mb-1 font-semibold">User start messages</div>
|
268 |
+
<div class="grid gap-1.5 text-sm md:grid-cols-2">
|
269 |
<input
|
270 |
name="exampleInput1"
|
271 |
+
placeholder="Start Message 1"
|
272 |
bind:value={inputMessage1}
|
273 |
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
274 |
/>
|
275 |
+
<input
|
276 |
+
name="exampleInput2"
|
277 |
+
placeholder="Start Message 2"
|
278 |
+
bind:value={inputMessage2}
|
279 |
+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
280 |
+
/>
|
281 |
+
|
282 |
+
<input
|
283 |
+
name="exampleInput3"
|
284 |
+
placeholder="Start Message 3"
|
285 |
+
bind:value={inputMessage3}
|
286 |
+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
287 |
+
/>
|
288 |
+
<input
|
289 |
+
name="exampleInput4"
|
290 |
+
placeholder="Start Message 4"
|
291 |
+
bind:value={inputMessage4}
|
292 |
+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
293 |
+
/>
|
|
|
|
|
294 |
</div>
|
295 |
<p class="text-xs text-red-500">{getError("inputMessage1", form)}</p>
|
296 |
</label>
|
297 |
{#if $page.data.enableAssistantsRAG}
|
298 |
<div class="mb-4 flex flex-col flex-nowrap">
|
299 |
<span class="mt-2 text-smd font-semibold"
|
300 |
+
>Internet access
|
301 |
+
<IconInternet classNames="inline text-sm text-blue-600" />
|
302 |
+
|
303 |
+
<span class="ml-1 rounded bg-gray-100 px-1 py-0.5 text-xxs font-normal text-gray-600"
|
304 |
>Experimental</span
|
305 |
>
|
306 |
|
|
|
322 |
name="ragMode"
|
323 |
value={false}
|
324 |
/>
|
325 |
+
<span class="my-2 text-sm" class:font-semibold={!ragMode}> Default </span>
|
326 |
{#if !ragMode}
|
327 |
<span class="block text-xs text-gray-500">
|
328 |
+
Assistant will not use internet to do information retrieval and will respond faster.
|
329 |
+
Recommended for most Assistants.
|
330 |
</span>
|
331 |
{/if}
|
332 |
</label>
|
|
|
398 |
/>
|
399 |
<p class="text-xs text-red-500">{getError("ragLinkList", form)}</p>
|
400 |
{/if}
|
401 |
+
|
402 |
+
<!-- divider -->
|
403 |
+
<div class="my-3 ml-0 mr-6 w-full border border-gray-200" />
|
404 |
+
|
405 |
+
<label class="text-sm has-[:checked]:font-semibold">
|
406 |
+
<input type="checkbox" name="dynamicPrompt" bind:checked={dynamicPrompt} />
|
407 |
+
Dynamic Prompt
|
408 |
+
<p class="mb-2 text-xs font-normal text-gray-500">
|
409 |
+
Allow the use of template variables {"{{url=https://example.com/path}}"}
|
410 |
+
to insert dynamic content into your prompt by making GET requests to specified URLs on
|
411 |
+
each inference.
|
412 |
+
</p>
|
413 |
+
</label>
|
414 |
</div>
|
415 |
{/if}
|
416 |
</div>
|
417 |
|
418 |
<div class="col-span-1 flex h-full flex-col">
|
419 |
+
<div class="mb-1 flex justify-between text-sm">
|
420 |
+
<span class="font-semibold"> Instructions (System Prompt) </span>
|
421 |
+
{#if dynamicPrompt && templateVariables.length}
|
422 |
+
<div class="relative">
|
423 |
+
<button
|
424 |
+
type="button"
|
425 |
+
class="peer rounded bg-blue-500/20 px-1 text-xs text-blue-600 focus:bg-blue-500/30 focus:text-blue-800 sm:text-sm"
|
426 |
+
>
|
427 |
+
{templateVariables.length} template variable{templateVariables.length > 1 ? "s" : ""}
|
428 |
+
</button>
|
429 |
+
<div
|
430 |
+
class="invisible absolute right-0 top-6 z-10 rounded-lg border bg-white p-2 text-xs shadow-lg peer-focus:visible hover:visible sm:w-96"
|
431 |
+
>
|
432 |
+
Will performs a GET request and injects the response into the prompt. Works better
|
433 |
+
with plain text, csv or json content.
|
434 |
+
{#each templateVariables as match}
|
435 |
+
<a href={match} target="_blank" class="text-gray-500 underline decoration-gray-300"
|
436 |
+
>{match}</a
|
437 |
+
>
|
438 |
+
{/each}
|
439 |
+
</div>
|
440 |
+
</div>
|
441 |
+
{/if}
|
442 |
+
</div>
|
443 |
+
<div class="relative mb-20 flex h-full flex-col gap-2">
|
444 |
<textarea
|
445 |
name="preprompt"
|
446 |
+
class="min-h-[8lh] flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
|
447 |
placeholder="You'll act as..."
|
448 |
bind:value={systemPrompt}
|
449 |
/>
|
|
|
451 |
{@const model = models.find((_model) => _model.id === modelId)}
|
452 |
{#if model?.tokenizer && systemPrompt}
|
453 |
<TokensCounter
|
454 |
+
classNames="absolute bottom-4 right-4"
|
455 |
prompt={systemPrompt}
|
456 |
modelTokenizer={model.tokenizer}
|
457 |
truncate={model?.parameters?.truncate}
|
458 |
/>
|
459 |
{/if}
|
460 |
{/if}
|
461 |
+
|
462 |
+
<p class="text-xs text-red-500">{getError("preprompt", form)}</p>
|
463 |
</div>
|
|
|
464 |
</div>
|
|
|
465 |
|
466 |
+
<div class="fixed bottom-6 right-6 ml-auto mt-6 flex w-fit justify-end gap-2 sm:absolute">
|
467 |
+
<a
|
468 |
+
href={assistant ? `${base}/settings/assistants/${assistant?._id}` : `${base}/settings`}
|
469 |
+
class="flex items-center justify-center rounded-full bg-gray-200 px-5 py-2 font-semibold text-gray-600"
|
470 |
+
>
|
471 |
+
Cancel
|
472 |
+
</a>
|
473 |
+
<button
|
474 |
+
type="submit"
|
475 |
+
disabled={loading}
|
476 |
+
aria-disabled={loading}
|
477 |
+
class="flex items-center justify-center rounded-full bg-black px-8 py-2 font-semibold"
|
478 |
+
class:bg-gray-200={loading}
|
479 |
+
class:text-gray-600={loading}
|
480 |
+
class:text-white={!loading}
|
481 |
+
>
|
482 |
+
{assistant ? "Save" : "Create"}
|
483 |
+
</button>
|
484 |
+
</div>
|
485 |
</div>
|
486 |
</form>
|
src/lib/components/TokensCounter.svelte
CHANGED
@@ -41,8 +41,20 @@
|
|
41 |
|
42 |
{#if tokenizer}
|
43 |
{#await tokenizeText(prompt) then nTokens}
|
44 |
-
|
45 |
-
|
46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
{/await}
|
48 |
{/if}
|
|
|
41 |
|
42 |
{#if tokenizer}
|
43 |
{#await tokenizeText(prompt) then nTokens}
|
44 |
+
{@const exceedLimit = nTokens > (truncate || Infinity)}
|
45 |
+
<div class={classNames}>
|
46 |
+
<p
|
47 |
+
class="peer text-sm {exceedLimit
|
48 |
+
? 'text-red-500 opacity-100'
|
49 |
+
: 'opacity-60 hover:opacity-90'}"
|
50 |
+
>
|
51 |
+
{nTokens}{truncate ? `/${truncate}` : ""}
|
52 |
+
</p>
|
53 |
+
<div
|
54 |
+
class="invisible absolute -top-6 right-0 whitespace-nowrap rounded bg-black px-1 text-sm text-white peer-hover:visible"
|
55 |
+
>
|
56 |
+
Tokens usage
|
57 |
+
</div>
|
58 |
+
</div>
|
59 |
{/await}
|
60 |
{/if}
|
src/lib/components/chat/AssistantIntroduction.svelte
CHANGED
@@ -10,6 +10,7 @@
|
|
10 |
| "avatar"
|
11 |
| "name"
|
12 |
| "rag"
|
|
|
13 |
| "modelId"
|
14 |
| "createdByName"
|
15 |
| "exampleInputs"
|
@@ -22,7 +23,8 @@
|
|
22 |
$: hasRag =
|
23 |
assistant?.rag?.allowAllDomains ||
|
24 |
(assistant?.rag?.allowedDomains?.length ?? 0) > 0 ||
|
25 |
-
(assistant?.rag?.allowedLinks?.length ?? 0) > 0
|
|
|
26 |
</script>
|
27 |
|
28 |
<div class="flex h-full w-full flex-col content-center items-center justify-center pb-52">
|
|
|
10 |
| "avatar"
|
11 |
| "name"
|
12 |
| "rag"
|
13 |
+
| "dynamicPrompt"
|
14 |
| "modelId"
|
15 |
| "createdByName"
|
16 |
| "exampleInputs"
|
|
|
23 |
$: hasRag =
|
24 |
assistant?.rag?.allowAllDomains ||
|
25 |
(assistant?.rag?.allowedDomains?.length ?? 0) > 0 ||
|
26 |
+
(assistant?.rag?.allowedLinks?.length ?? 0) > 0 ||
|
27 |
+
assistant?.dynamicPrompt;
|
28 |
</script>
|
29 |
|
30 |
<div class="flex h-full w-full flex-col content-center items-center justify-center pb-52">
|
src/lib/types/Assistant.ts
CHANGED
@@ -19,5 +19,6 @@ export interface Assistant extends Timestamps {
|
|
19 |
allowedDomains: string[];
|
20 |
allowedLinks: string[];
|
21 |
};
|
|
|
22 |
searchTokens: string[];
|
23 |
}
|
|
|
19 |
allowedDomains: string[];
|
20 |
allowedLinks: string[];
|
21 |
};
|
22 |
+
dynamicPrompt?: boolean;
|
23 |
searchTokens: string[];
|
24 |
}
|
src/routes/assistants/+page.svelte
CHANGED
@@ -205,7 +205,8 @@
|
|
205 |
{@const hasRag =
|
206 |
assistant?.rag?.allowAllDomains ||
|
207 |
!!assistant?.rag?.allowedDomains?.length ||
|
208 |
-
!!assistant?.rag?.allowedLinks?.length
|
|
|
209 |
|
210 |
<button
|
211 |
class="relative flex flex-col items-center justify-center overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 py-6 text-center shadow hover:bg-gray-50 hover:shadow-inner max-sm:px-4 sm:h-64 sm:pb-4 xl:pt-8 dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40"
|
|
|
205 |
{@const hasRag =
|
206 |
assistant?.rag?.allowAllDomains ||
|
207 |
!!assistant?.rag?.allowedDomains?.length ||
|
208 |
+
!!assistant?.rag?.allowedLinks?.length ||
|
209 |
+
!!assistant?.dynamicPrompt}
|
210 |
|
211 |
<button
|
212 |
class="relative flex flex-col items-center justify-center overflow-hidden text-balance rounded-xl border bg-gray-50/50 px-4 py-6 text-center shadow hover:bg-gray-50 hover:shadow-inner max-sm:px-4 sm:h-64 sm:pb-4 xl:pt-8 dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40"
|
src/routes/conversation/+server.ts
CHANGED
@@ -45,22 +45,11 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|
45 |
throw error(400, "Invalid model");
|
46 |
}
|
47 |
|
48 |
-
// get preprompt from assistant if it exists
|
49 |
-
const assistant = await collections.assistants.findOne({
|
50 |
-
_id: new ObjectId(values.assistantId),
|
51 |
-
});
|
52 |
-
|
53 |
-
if (assistant) {
|
54 |
-
values.preprompt = assistant.preprompt;
|
55 |
-
} else {
|
56 |
-
values.preprompt ??= model?.preprompt ?? "";
|
57 |
-
}
|
58 |
-
|
59 |
let messages: Message[] = [
|
60 |
{
|
61 |
id: v4(),
|
62 |
from: "system",
|
63 |
-
content: values.preprompt,
|
64 |
createdAt: new Date(),
|
65 |
updatedAt: new Date(),
|
66 |
children: [],
|
@@ -95,6 +84,17 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|
95 |
throw error(400, "Can't start a conversation with an unlisted model");
|
96 |
}
|
97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
const res = await collections.conversations.insertOne({
|
99 |
_id: new ObjectId(),
|
100 |
title: title || "New Chat",
|
|
|
45 |
throw error(400, "Invalid model");
|
46 |
}
|
47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
let messages: Message[] = [
|
49 |
{
|
50 |
id: v4(),
|
51 |
from: "system",
|
52 |
+
content: values.preprompt ?? "",
|
53 |
createdAt: new Date(),
|
54 |
updatedAt: new Date(),
|
55 |
children: [],
|
|
|
84 |
throw error(400, "Can't start a conversation with an unlisted model");
|
85 |
}
|
86 |
|
87 |
+
// get preprompt from assistant if it exists
|
88 |
+
const assistant = await collections.assistants.findOne({
|
89 |
+
_id: new ObjectId(values.assistantId),
|
90 |
+
});
|
91 |
+
|
92 |
+
if (assistant) {
|
93 |
+
values.preprompt = assistant.preprompt;
|
94 |
+
} else {
|
95 |
+
values.preprompt ??= model?.preprompt ?? "";
|
96 |
+
}
|
97 |
+
|
98 |
const res = await collections.conversations.insertOne({
|
99 |
_id: new ObjectId(),
|
100 |
title: title || "New Chat",
|
src/routes/conversation/[id]/+server.ts
CHANGED
@@ -21,6 +21,7 @@ import { addChildren } from "$lib/utils/tree/addChildren.js";
|
|
21 |
import { addSibling } from "$lib/utils/tree/addSibling.js";
|
22 |
import { preprocessMessages } from "$lib/server/preprocessMessages.js";
|
23 |
import { usageLimits } from "$lib/server/usageLimits";
|
|
|
24 |
|
25 |
export async function POST({ request, locals, params, getClientAddress }) {
|
26 |
const id = z.string().parse(params.id);
|
@@ -336,21 +337,60 @@ export async function POST({ request, locals, params, getClientAddress }) {
|
|
336 |
);
|
337 |
|
338 |
// check if assistant has a rag
|
339 |
-
const
|
340 |
-
|
341 |
-
|
342 |
-
{ projection: { rag: 1 } }
|
343 |
-
)
|
344 |
-
)?.rag;
|
345 |
|
346 |
const assistantHasRAG =
|
347 |
ENABLE_ASSISTANTS_RAG === "true" &&
|
348 |
-
|
349 |
-
(
|
|
|
|
|
|
|
|
|
350 |
|
351 |
// perform websearch if needed
|
352 |
-
if (
|
353 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
354 |
}
|
355 |
|
356 |
// inject websearch result & optionally images into the messages
|
@@ -367,7 +407,7 @@ export async function POST({ request, locals, params, getClientAddress }) {
|
|
367 |
const endpoint = await model.getEndpoint();
|
368 |
for await (const output of await endpoint({
|
369 |
messages: processedMessages,
|
370 |
-
preprompt
|
371 |
continueMessage: isContinue,
|
372 |
})) {
|
373 |
// if not generated_text is here it means the generation is not done
|
|
|
21 |
import { addSibling } from "$lib/utils/tree/addSibling.js";
|
22 |
import { preprocessMessages } from "$lib/server/preprocessMessages.js";
|
23 |
import { usageLimits } from "$lib/server/usageLimits";
|
24 |
+
import { isURLLocal } from "$lib/server/isURLLocal.js";
|
25 |
|
26 |
export async function POST({ request, locals, params, getClientAddress }) {
|
27 |
const id = z.string().parse(params.id);
|
|
|
337 |
);
|
338 |
|
339 |
// check if assistant has a rag
|
340 |
+
const assistant = await collections.assistants.findOne<
|
341 |
+
Pick<Assistant, "rag" | "dynamicPrompt">
|
342 |
+
>({ _id: conv.assistantId }, { projection: { rag: 1, dynamicPrompt: 1 } });
|
|
|
|
|
|
|
343 |
|
344 |
const assistantHasRAG =
|
345 |
ENABLE_ASSISTANTS_RAG === "true" &&
|
346 |
+
assistant &&
|
347 |
+
((assistant.rag &&
|
348 |
+
(assistant.rag.allowedLinks.length > 0 ||
|
349 |
+
assistant.rag.allowedDomains.length > 0 ||
|
350 |
+
assistant.rag.allowAllDomains)) ||
|
351 |
+
assistant.dynamicPrompt);
|
352 |
|
353 |
// perform websearch if needed
|
354 |
+
if (
|
355 |
+
!isContinue &&
|
356 |
+
((webSearch && !conv.assistantId) || (assistantHasRAG && !assistant.dynamicPrompt))
|
357 |
+
) {
|
358 |
+
messageToWriteTo.webSearch = await runWebSearch(
|
359 |
+
conv,
|
360 |
+
messagesForPrompt,
|
361 |
+
update,
|
362 |
+
assistant?.rag
|
363 |
+
);
|
364 |
+
}
|
365 |
+
|
366 |
+
let preprompt = conv.preprompt;
|
367 |
+
|
368 |
+
if (assistant?.dynamicPrompt && preprompt && ENABLE_ASSISTANTS_RAG === "true") {
|
369 |
+
// process the preprompt
|
370 |
+
const urlRegex = /{{\s?url=(.*?)\s?}}/g;
|
371 |
+
let match;
|
372 |
+
while ((match = urlRegex.exec(preprompt)) !== null) {
|
373 |
+
try {
|
374 |
+
const url = new URL(match[1]);
|
375 |
+
if (await isURLLocal(url)) {
|
376 |
+
throw new Error("URL couldn't be fetched, it resolved to a local address.");
|
377 |
+
}
|
378 |
+
|
379 |
+
const res = await fetch(url.href);
|
380 |
+
|
381 |
+
if (!res.ok) {
|
382 |
+
throw new Error("URL couldn't be fetched, error " + res.status);
|
383 |
+
}
|
384 |
+
const text = await res.text();
|
385 |
+
preprompt = preprompt.replaceAll(match[0], text);
|
386 |
+
} catch (e) {
|
387 |
+
preprompt = preprompt.replaceAll(match[0], (e as Error).message);
|
388 |
+
}
|
389 |
+
}
|
390 |
+
|
391 |
+
if (messagesForPrompt[0].from === "system") {
|
392 |
+
messagesForPrompt[0].content = preprompt;
|
393 |
+
}
|
394 |
}
|
395 |
|
396 |
// inject websearch result & optionally images into the messages
|
|
|
407 |
const endpoint = await model.getEndpoint();
|
408 |
for await (const output of await endpoint({
|
409 |
messages: processedMessages,
|
410 |
+
preprompt,
|
411 |
continueMessage: isContinue,
|
412 |
})) {
|
413 |
// if not generated_text is here it means the generation is not done
|
src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte
CHANGED
@@ -32,7 +32,10 @@
|
|
32 |
$: hasRag =
|
33 |
assistant?.rag?.allowAllDomains ||
|
34 |
!!assistant?.rag?.allowedDomains?.length ||
|
35 |
-
!!assistant?.rag?.allowedLinks?.length
|
|
|
|
|
|
|
36 |
</script>
|
37 |
|
38 |
{#if displayReportModal}
|
@@ -164,16 +167,42 @@
|
|
164 |
|
165 |
<!-- two columns for big screen, single column for small screen -->
|
166 |
<div class="mb-12 mt-3">
|
167 |
-
<h2 class="mb-2 font-semibold">System Instructions</h2>
|
168 |
-
<
|
169 |
-
|
170 |
-
class="box-border h-
|
171 |
-
>{assistant?.preprompt}</textarea
|
172 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
|
174 |
{#if hasRag}
|
175 |
<div class="mt-4">
|
176 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
{#if assistant?.rag?.allowAllDomains}
|
178 |
<p class="text-sm text-gray-500">
|
179 |
This Assistant uses Web Search to find information on Internet.
|
@@ -203,6 +232,11 @@
|
|
203 |
{/each}
|
204 |
</ul>
|
205 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
206 |
</div>
|
207 |
{/if}
|
208 |
</div>
|
|
|
32 |
$: hasRag =
|
33 |
assistant?.rag?.allowAllDomains ||
|
34 |
!!assistant?.rag?.allowedDomains?.length ||
|
35 |
+
!!assistant?.rag?.allowedLinks?.length ||
|
36 |
+
!!assistant?.dynamicPrompt;
|
37 |
+
|
38 |
+
$: prepromptTags = assistant?.preprompt?.split(/(\{\{[^{}]*\}\})/) ?? [];
|
39 |
</script>
|
40 |
|
41 |
{#if displayReportModal}
|
|
|
167 |
|
168 |
<!-- two columns for big screen, single column for small screen -->
|
169 |
<div class="mb-12 mt-3">
|
170 |
+
<h2 class="mb-2 inline font-semibold">System Instructions</h2>
|
171 |
+
<div
|
172 |
+
id="System Instructions"
|
173 |
+
class="overlow-y-auto mt-2 box-border h-fit max-h-[240px] w-full overflow-y-auto whitespace-pre-line rounded-lg border-2 border-gray-200 bg-gray-100 p-2 disabled:cursor-not-allowed 2xl:max-h-[310px]"
|
|
|
174 |
>
|
175 |
+
{#if assistant?.dynamicPrompt}
|
176 |
+
{#each prepromptTags as tag}
|
177 |
+
{#if tag.startsWith("{{") && tag.endsWith("}}") && tag.includes("url=")}
|
178 |
+
{@const url = tag.split("url=")[1].split("}}")[0]}
|
179 |
+
<a
|
180 |
+
target="_blank"
|
181 |
+
href={url.startsWith("http") ? url : `//${url}`}
|
182 |
+
class="break-words rounded-lg bg-blue-100 px-1 py-0.5 text-blue-800 hover:underline"
|
183 |
+
>
|
184 |
+
{tag}</a
|
185 |
+
>
|
186 |
+
{:else}
|
187 |
+
{tag}
|
188 |
+
{/if}
|
189 |
+
{/each}
|
190 |
+
{:else}
|
191 |
+
{assistant?.preprompt}
|
192 |
+
{/if}
|
193 |
+
</div>
|
194 |
|
195 |
{#if hasRag}
|
196 |
<div class="mt-4">
|
197 |
+
<div class="mb-1 flex items-center gap-1">
|
198 |
+
<span
|
199 |
+
class="inline-grid size-5 place-items-center rounded-full bg-blue-500/10"
|
200 |
+
title="This assistant uses the websearch."
|
201 |
+
>
|
202 |
+
<IconInternet classNames="text-sm text-blue-600" />
|
203 |
+
</span>
|
204 |
+
<h2 class=" font-semibold">Internet Access</h2>
|
205 |
+
</div>
|
206 |
{#if assistant?.rag?.allowAllDomains}
|
207 |
<p class="text-sm text-gray-500">
|
208 |
This Assistant uses Web Search to find information on Internet.
|
|
|
232 |
{/each}
|
233 |
</ul>
|
234 |
{/if}
|
235 |
+
{#if assistant?.dynamicPrompt}
|
236 |
+
<p class="text-sm text-gray-500">
|
237 |
+
This Assistant has dynamic prompts enabled and can make requests to external services.
|
238 |
+
</p>
|
239 |
+
{/if}
|
240 |
</div>
|
241 |
{/if}
|
242 |
</div>
|
src/routes/settings/(nav)/assistants/[assistantId]/edit/+page.server.ts
CHANGED
@@ -24,6 +24,7 @@ const newAsssistantSchema = z.object({
|
|
24 |
ragLinkList: z.preprocess(parseStringToList, z.string().url().array().max(10)),
|
25 |
ragDomainList: z.preprocess(parseStringToList, z.string().array()),
|
26 |
ragAllowAll: z.preprocess((v) => v === "true", z.boolean()),
|
|
|
27 |
});
|
28 |
|
29 |
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
@@ -140,6 +141,7 @@ export const actions: Actions = {
|
|
140 |
allowedDomains: parse.data.ragDomainList,
|
141 |
allowAllDomains: parse.data.ragAllowAll,
|
142 |
},
|
|
|
143 |
searchTokens: generateSearchTokens(parse.data.name),
|
144 |
},
|
145 |
}
|
|
|
24 |
ragLinkList: z.preprocess(parseStringToList, z.string().url().array().max(10)),
|
25 |
ragDomainList: z.preprocess(parseStringToList, z.string().array()),
|
26 |
ragAllowAll: z.preprocess((v) => v === "true", z.boolean()),
|
27 |
+
dynamicPrompt: z.preprocess((v) => v === "on", z.boolean()),
|
28 |
});
|
29 |
|
30 |
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
|
|
141 |
allowedDomains: parse.data.ragDomainList,
|
142 |
allowAllDomains: parse.data.ragAllowAll,
|
143 |
},
|
144 |
+
dynamicPrompt: parse.data.dynamicPrompt,
|
145 |
searchTokens: generateSearchTokens(parse.data.name),
|
146 |
},
|
147 |
}
|
src/routes/settings/(nav)/assistants/new/+page.server.ts
CHANGED
@@ -24,6 +24,7 @@ const newAsssistantSchema = z.object({
|
|
24 |
ragLinkList: z.preprocess(parseStringToList, z.string().url().array().max(10)),
|
25 |
ragDomainList: z.preprocess(parseStringToList, z.string().array()),
|
26 |
ragAllowAll: z.preprocess((v) => v === "true", z.boolean()),
|
|
|
27 |
});
|
28 |
|
29 |
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
@@ -122,6 +123,7 @@ export const actions: Actions = {
|
|
122 |
allowedDomains: parse.data.ragDomainList,
|
123 |
allowAllDomains: parse.data.ragAllowAll,
|
124 |
},
|
|
|
125 |
searchTokens: generateSearchTokens(parse.data.name),
|
126 |
});
|
127 |
|
|
|
24 |
ragLinkList: z.preprocess(parseStringToList, z.string().url().array().max(10)),
|
25 |
ragDomainList: z.preprocess(parseStringToList, z.string().array()),
|
26 |
ragAllowAll: z.preprocess((v) => v === "true", z.boolean()),
|
27 |
+
dynamicPrompt: z.preprocess((v) => v === "on", z.boolean()),
|
28 |
});
|
29 |
|
30 |
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
|
|
123 |
allowedDomains: parse.data.ragDomainList,
|
124 |
allowAllDomains: parse.data.ragAllowAll,
|
125 |
},
|
126 |
+
dynamicPrompt: parse.data.dynamicPrompt,
|
127 |
searchTokens: generateSearchTokens(parse.data.name),
|
128 |
});
|
129 |
|
src/routes/settings/+layout.svelte
CHANGED
@@ -31,7 +31,7 @@
|
|
31 |
}
|
32 |
goto(previousPage);
|
33 |
}}
|
34 |
-
class="h-[95dvh] w-[90dvw] overflow-hidden rounded-2xl bg-white shadow-2xl outline-none sm:h-[
|
35 |
>
|
36 |
<slot />
|
37 |
{#if $settings.recentlySaved}
|
|
|
31 |
}
|
32 |
goto(previousPage);
|
33 |
}}
|
34 |
+
class="h-[95dvh] w-[90dvw] overflow-hidden rounded-2xl bg-white shadow-2xl outline-none sm:h-[85dvh] xl:w-[1200px] 2xl:h-[75dvh]"
|
35 |
>
|
36 |
<slot />
|
37 |
{#if $settings.recentlySaved}
|