Store preprompt in conversation (#422) (#470)
Browse files* Store preprompt in conversation (#422)
* display preprompt at the top
* system prompt modal
* tweaks
---------
Co-authored-by: Victor Mustar <victor.mustar@gmail.com>
- src/lib/components/SystemPromptModal.svelte +36 -0
- src/lib/components/chat/ChatMessages.svelte +5 -0
- src/lib/components/chat/ChatWindow.svelte +2 -0
- src/lib/types/Conversation.ts +2 -0
- src/lib/types/SharedConversation.ts +1 -0
- src/routes/+page.svelte +4 -1
- src/routes/conversation/+server.ts +7 -0
- src/routes/conversation/[id]/+page.server.ts +1 -0
- src/routes/conversation/[id]/+page.svelte +1 -0
- src/routes/conversation/[id]/+server.ts +1 -2
- src/routes/conversation/[id]/share/+server.ts +1 -0
- src/routes/r/[id]/+page.server.ts +1 -0
- src/routes/r/[id]/+page.svelte +1 -0
src/lib/components/SystemPromptModal.svelte
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import Modal from "./Modal.svelte";
|
3 |
+
import CarbonClose from "~icons/carbon/close";
|
4 |
+
import CarbonBlockchain from "~icons/carbon/blockchain";
|
5 |
+
|
6 |
+
export let preprompt: string;
|
7 |
+
|
8 |
+
let isOpen = false;
|
9 |
+
</script>
|
10 |
+
|
11 |
+
<button
|
12 |
+
type="button"
|
13 |
+
class="mx-auto flex items-center gap-1.5 rounded-full border border-gray-100 bg-gray-50 px-3 py-1 text-xs text-gray-500 hover:bg-gray-100 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
|
14 |
+
on:click={() => (isOpen = !isOpen)}
|
15 |
+
on:keypress={(e) => e.key === "Enter" && (isOpen = !isOpen)}
|
16 |
+
>
|
17 |
+
<CarbonBlockchain class="text-xxs" /> Using Custom System Prompt
|
18 |
+
</button>
|
19 |
+
|
20 |
+
{#if isOpen}
|
21 |
+
<Modal on:close={() => (isOpen = false)} width="w-full max-w-2xl">
|
22 |
+
<div class="flex w-full flex-col gap-5 p-6">
|
23 |
+
<div class="flex items-start justify-between text-xl font-semibold text-gray-800">
|
24 |
+
<h2>System Prompt</h2>
|
25 |
+
<button type="button" class="group" on:click={() => (isOpen = false)}>
|
26 |
+
<CarbonClose class="mt-auto text-gray-900 group-hover:text-gray-500" />
|
27 |
+
</button>
|
28 |
+
</div>
|
29 |
+
<textarea
|
30 |
+
disabled
|
31 |
+
value={preprompt}
|
32 |
+
class="min-h-[420px] w-full resize-none rounded-lg border bg-gray-50 p-2.5 text-gray-600 max-sm:text-sm"
|
33 |
+
/>
|
34 |
+
</div>
|
35 |
+
</Modal>
|
36 |
+
{/if}
|
src/lib/components/chat/ChatMessages.svelte
CHANGED
@@ -10,6 +10,7 @@
|
|
10 |
import ChatMessage from "./ChatMessage.svelte";
|
11 |
import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
|
12 |
import { browser } from "$app/environment";
|
|
|
13 |
|
14 |
export let messages: Message[];
|
15 |
export let loading: boolean;
|
@@ -18,6 +19,7 @@
|
|
18 |
export let currentModel: Model;
|
19 |
export let settings: LayoutData["settings"];
|
20 |
export let models: Model[];
|
|
|
21 |
export let readOnly: boolean;
|
22 |
|
23 |
let chatContainer: HTMLElement;
|
@@ -42,6 +44,9 @@
|
|
42 |
>
|
43 |
<div class="mx-auto flex h-full max-w-3xl flex-col gap-6 px-5 pt-6 sm:gap-8 xl:max-w-4xl">
|
44 |
{#each messages as message, i}
|
|
|
|
|
|
|
45 |
<ChatMessage
|
46 |
loading={loading && i === messages.length - 1}
|
47 |
{message}
|
|
|
10 |
import ChatMessage from "./ChatMessage.svelte";
|
11 |
import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
|
12 |
import { browser } from "$app/environment";
|
13 |
+
import SystemPromptModal from "../SystemPromptModal.svelte";
|
14 |
|
15 |
export let messages: Message[];
|
16 |
export let loading: boolean;
|
|
|
19 |
export let currentModel: Model;
|
20 |
export let settings: LayoutData["settings"];
|
21 |
export let models: Model[];
|
22 |
+
export let preprompt: string | undefined;
|
23 |
export let readOnly: boolean;
|
24 |
|
25 |
let chatContainer: HTMLElement;
|
|
|
44 |
>
|
45 |
<div class="mx-auto flex h-full max-w-3xl flex-col gap-6 px-5 pt-6 sm:gap-8 xl:max-w-4xl">
|
46 |
{#each messages as message, i}
|
47 |
+
{#if i === 0 && preprompt}
|
48 |
+
<SystemPromptModal {preprompt} />
|
49 |
+
{/if}
|
50 |
<ChatMessage
|
51 |
loading={loading && i === messages.length - 1}
|
52 |
{message}
|
src/lib/components/chat/ChatWindow.svelte
CHANGED
@@ -24,6 +24,7 @@
|
|
24 |
export let models: Model[];
|
25 |
export let settings: LayoutData["settings"];
|
26 |
export let webSearchMessages: WebSearchUpdate[] = [];
|
|
|
27 |
|
28 |
export let loginRequired = false;
|
29 |
$: isReadOnly = !models.some((model) => model.id === currentModel.id);
|
@@ -59,6 +60,7 @@
|
|
59 |
readOnly={isReadOnly}
|
60 |
isAuthor={!shared}
|
61 |
{webSearchMessages}
|
|
|
62 |
on:message
|
63 |
on:vote
|
64 |
on:retry={(ev) => {
|
|
|
24 |
export let models: Model[];
|
25 |
export let settings: LayoutData["settings"];
|
26 |
export let webSearchMessages: WebSearchUpdate[] = [];
|
27 |
+
export let preprompt: string | undefined = undefined;
|
28 |
|
29 |
export let loginRequired = false;
|
30 |
$: isReadOnly = !models.some((model) => model.id === currentModel.id);
|
|
|
60 |
readOnly={isReadOnly}
|
61 |
isAuthor={!shared}
|
62 |
{webSearchMessages}
|
63 |
+
{preprompt}
|
64 |
on:message
|
65 |
on:vote
|
66 |
on:retry={(ev) => {
|
src/lib/types/Conversation.ts
CHANGED
@@ -17,4 +17,6 @@ export interface Conversation extends Timestamps {
|
|
17 |
meta?: {
|
18 |
fromShareId?: string;
|
19 |
};
|
|
|
|
|
20 |
}
|
|
|
17 |
meta?: {
|
18 |
fromShareId?: string;
|
19 |
};
|
20 |
+
|
21 |
+
preprompt?: string;
|
22 |
}
|
src/lib/types/SharedConversation.ts
CHANGED
@@ -9,4 +9,5 @@ export interface SharedConversation extends Timestamps {
|
|
9 |
model: string;
|
10 |
title: string;
|
11 |
messages: Message[];
|
|
|
12 |
}
|
|
|
9 |
model: string;
|
10 |
title: string;
|
11 |
messages: Message[];
|
12 |
+
preprompt?: string;
|
13 |
}
|
src/routes/+page.svelte
CHANGED
@@ -17,7 +17,10 @@
|
|
17 |
headers: {
|
18 |
"Content-Type": "application/json",
|
19 |
},
|
20 |
-
body: JSON.stringify({
|
|
|
|
|
|
|
21 |
});
|
22 |
|
23 |
if (!res.ok) {
|
|
|
17 |
headers: {
|
18 |
"Content-Type": "application/json",
|
19 |
},
|
20 |
+
body: JSON.stringify({
|
21 |
+
model: data.settings.activeModel,
|
22 |
+
preprompt: data.settings.customPrompts[data.settings.activeModel],
|
23 |
+
}),
|
24 |
});
|
25 |
|
26 |
if (!res.ok) {
|
src/routes/conversation/+server.ts
CHANGED
@@ -18,9 +18,12 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|
18 |
.object({
|
19 |
fromShare: z.string().optional(),
|
20 |
model: validateModel(models),
|
|
|
21 |
})
|
22 |
.parse(JSON.parse(body));
|
23 |
|
|
|
|
|
24 |
if (values.fromShare) {
|
25 |
const conversation = await collections.sharedConversations.findOne({
|
26 |
_id: values.fromShare,
|
@@ -33,8 +36,11 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|
33 |
title = conversation.title;
|
34 |
messages = conversation.messages;
|
35 |
values.model = conversation.model;
|
|
|
36 |
}
|
37 |
|
|
|
|
|
38 |
const res = await collections.conversations.insertOne({
|
39 |
_id: new ObjectId(),
|
40 |
title:
|
@@ -42,6 +48,7 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|
42 |
"Untitled " + ((await collections.conversations.countDocuments(authCondition(locals))) + 1),
|
43 |
messages,
|
44 |
model: values.model,
|
|
|
45 |
createdAt: new Date(),
|
46 |
updatedAt: new Date(),
|
47 |
...(locals.user ? { userId: locals.user._id } : { sessionId: locals.sessionId }),
|
|
|
18 |
.object({
|
19 |
fromShare: z.string().optional(),
|
20 |
model: validateModel(models),
|
21 |
+
preprompt: z.string().optional(),
|
22 |
})
|
23 |
.parse(JSON.parse(body));
|
24 |
|
25 |
+
let preprompt = values.preprompt;
|
26 |
+
|
27 |
if (values.fromShare) {
|
28 |
const conversation = await collections.sharedConversations.findOne({
|
29 |
_id: values.fromShare,
|
|
|
36 |
title = conversation.title;
|
37 |
messages = conversation.messages;
|
38 |
values.model = conversation.model;
|
39 |
+
preprompt = conversation.preprompt;
|
40 |
}
|
41 |
|
42 |
+
const model = models.find((m) => m.name === values.model);
|
43 |
+
|
44 |
const res = await collections.conversations.insertOne({
|
45 |
_id: new ObjectId(),
|
46 |
title:
|
|
|
48 |
"Untitled " + ((await collections.conversations.countDocuments(authCondition(locals))) + 1),
|
49 |
messages,
|
50 |
model: values.model,
|
51 |
+
preprompt: preprompt === model?.preprompt ? undefined : preprompt,
|
52 |
createdAt: new Date(),
|
53 |
updatedAt: new Date(),
|
54 |
...(locals.user ? { userId: locals.user._id } : { sessionId: locals.sessionId }),
|
src/routes/conversation/[id]/+page.server.ts
CHANGED
@@ -33,5 +33,6 @@ export const load = async ({ params, depends, locals }) => {
|
|
33 |
messages: conversation.messages,
|
34 |
title: conversation.title,
|
35 |
model: conversation.model,
|
|
|
36 |
};
|
37 |
};
|
|
|
33 |
messages: conversation.messages,
|
34 |
title: conversation.title,
|
35 |
model: conversation.model,
|
36 |
+
preprompt: conversation.preprompt,
|
37 |
};
|
38 |
};
|
src/routes/conversation/[id]/+page.svelte
CHANGED
@@ -218,6 +218,7 @@
|
|
218 |
{loading}
|
219 |
{pending}
|
220 |
{messages}
|
|
|
221 |
bind:webSearchMessages
|
222 |
on:message={(event) => writeMessage(event.detail)}
|
223 |
on:retry={(event) => writeMessage(event.detail.content, event.detail.id)}
|
|
|
218 |
{loading}
|
219 |
{pending}
|
220 |
{messages}
|
221 |
+
preprompt={data.preprompt}
|
222 |
bind:webSearchMessages
|
223 |
on:message={(event) => writeMessage(event.detail)}
|
224 |
on:retry={(event) => writeMessage(event.detail.content, event.detail.id)}
|
src/routes/conversation/[id]/+server.ts
CHANGED
@@ -70,7 +70,6 @@ export async function POST({ request, fetch, locals, params, getClientAddress })
|
|
70 |
|
71 |
// fetch the model
|
72 |
const model = models.find((m) => m.id === conv.model);
|
73 |
-
const settings = await collections.settings.findOne(authCondition(locals));
|
74 |
|
75 |
if (!model) {
|
76 |
throw error(410, "Model not available anymore");
|
@@ -155,7 +154,7 @@ export async function POST({ request, fetch, locals, params, getClientAddress })
|
|
155 |
messages,
|
156 |
model,
|
157 |
webSearch: webSearchResults,
|
158 |
-
preprompt:
|
159 |
locals: locals,
|
160 |
});
|
161 |
|
|
|
70 |
|
71 |
// fetch the model
|
72 |
const model = models.find((m) => m.id === conv.model);
|
|
|
73 |
|
74 |
if (!model) {
|
75 |
throw error(410, "Model not available anymore");
|
|
|
154 |
messages,
|
155 |
model,
|
156 |
webSearch: webSearchResults,
|
157 |
+
preprompt: conv.preprompt ?? model.preprompt,
|
158 |
locals: locals,
|
159 |
});
|
160 |
|
src/routes/conversation/[id]/share/+server.ts
CHANGED
@@ -39,6 +39,7 @@ export async function POST({ params, url, locals }) {
|
|
39 |
updatedAt: new Date(),
|
40 |
title: conversation.title,
|
41 |
model: conversation.model,
|
|
|
42 |
};
|
43 |
|
44 |
await collections.sharedConversations.insertOne(shared);
|
|
|
39 |
updatedAt: new Date(),
|
40 |
title: conversation.title,
|
41 |
model: conversation.model,
|
42 |
+
preprompt: conversation.preprompt,
|
43 |
};
|
44 |
|
45 |
await collections.sharedConversations.insertOne(shared);
|
src/routes/r/[id]/+page.server.ts
CHANGED
@@ -12,6 +12,7 @@ export const load: PageServerLoad = async ({ params }) => {
|
|
12 |
}
|
13 |
|
14 |
return {
|
|
|
15 |
messages: conversation.messages,
|
16 |
title: conversation.title,
|
17 |
model: conversation.model,
|
|
|
12 |
}
|
13 |
|
14 |
return {
|
15 |
+
preprompt: conversation.preprompt,
|
16 |
messages: conversation.messages,
|
17 |
title: conversation.title,
|
18 |
model: conversation.model,
|
src/routes/r/[id]/+page.svelte
CHANGED
@@ -60,6 +60,7 @@
|
|
60 |
{loading}
|
61 |
shared={true}
|
62 |
messages={data.messages}
|
|
|
63 |
on:message={(ev) =>
|
64 |
createConversation()
|
65 |
.then((convId) => {
|
|
|
60 |
{loading}
|
61 |
shared={true}
|
62 |
messages={data.messages}
|
63 |
+
preprompt={data.preprompt}
|
64 |
on:message={(ev) =>
|
65 |
createConversation()
|
66 |
.then((convId) => {
|