nsarrazin HF staff victor HF staff commited on
Commit
6887755
1 Parent(s): 2e2f16c

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 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 those changes to all users.
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="My awesome model"
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="flex flex-col gap-2">
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
- {#if !!inputMessage1 || !!inputMessage2}
270
- <input
271
- name="exampleInput2"
272
- bind:value={inputMessage2}
273
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
274
- />
275
- {/if}
276
- {#if !!inputMessage2 || !!inputMessage3}
277
- <input
278
- name="exampleInput3"
279
- bind:value={inputMessage3}
280
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
281
- />
282
- {/if}
283
- {#if !!inputMessage3 || !!inputMessage4}
284
- <input
285
- name="exampleInput4"
286
- bind:value={inputMessage4}
287
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
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 <span
297
- class="ml-1 rounded bg-gray-100 px-1 py-0.5 text-xxs font-normal text-gray-600"
 
 
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}> Disabled </span>
320
  {#if !ragMode}
321
  <span class="block text-xs text-gray-500">
322
- Assistant won't look for information from Internet and will be faster to answer.
 
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
- <span class="mb-1 text-sm font-semibold"> Instructions (system prompt) </span>
400
- <div class="relative mb-20 flex min-h-[8lh] flex-1 grow flex-col">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-2 right-2"
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
- <div class="fixed bottom-6 right-6 ml-auto mt-6 flex w-fit justify-end gap-2 sm:absolute">
424
- <a
425
- href={assistant ? `${base}/settings/assistants/${assistant?._id}` : `${base}/settings`}
426
- class="flex items-center justify-center rounded-full bg-gray-200 px-5 py-2 font-semibold text-gray-600"
427
- >
428
- Cancel
429
- </a>
430
- <button
431
- type="submit"
432
- disabled={loading}
433
- aria-disabled={loading}
434
- class="flex items-center justify-center rounded-full bg-black px-8 py-2 font-semibold"
435
- class:bg-gray-200={loading}
436
- class:text-gray-600={loading}
437
- class:text-white={!loading}
438
- >
439
- {assistant ? "Save" : "Create"}
440
- </button>
 
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
- <p class="text-sm opacity-60 hover:opacity-80 {classNames}">
45
- {nTokens}{truncate ? `/${truncate}` : ""}
46
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
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 rag = (
340
- await collections.assistants.findOne<Pick<Assistant, "rag">>(
341
- { _id: conv.assistantId },
342
- { projection: { rag: 1 } }
343
- )
344
- )?.rag;
345
 
346
  const assistantHasRAG =
347
  ENABLE_ASSISTANTS_RAG === "true" &&
348
- rag &&
349
- (rag.allowedLinks.length > 0 || rag.allowedDomains.length > 0 || rag.allowAllDomains);
 
 
 
 
350
 
351
  // perform websearch if needed
352
- if (!isContinue && ((webSearch && !conv.assistantId) || assistantHasRAG)) {
353
- messageToWriteTo.webSearch = await runWebSearch(conv, messagesForPrompt, update, rag);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: conv.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
- <textarea
169
- disabled
170
- class="box-border h-full min-h-[8lh] w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2 disabled:cursor-not-allowed"
171
- >{assistant?.preprompt}</textarea
172
  >
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  {#if hasRag}
175
  <div class="mt-4">
176
- <h2 class=" font-semibold">Internet Access</h2>
 
 
 
 
 
 
 
 
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-[80dvh] xl:w-[1200px] 2xl:h-[70dvh]"
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}