Spaces:
Running
on
Inf2
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 <victor.mustar@gmail.com>
- 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
|
@@ -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>
|
|
@@ -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}
|
|
@@ -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">
|
|
@@ -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 |
}
|
|
@@ -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"
|
|
@@ -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",
|
|
@@ -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
|
|
@@ -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>
|
|
@@ -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 |
}
|
|
@@ -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 |
|
|
@@ -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}
|