Spaces:
Running
Running
Prettier update (#54)
Browse files* Update .prettierrc
* prettify everthing
prettify everything
- .eslintrc.cjs +11 -11
- .prettierrc +1 -2
- src/app.html +2 -2
- src/hooks.server.ts +6 -6
- src/lib/actions/snapScrollToBottom.ts +6 -6
- src/lib/buildPrompt.ts +6 -6
- src/lib/components/CodeBlock.svelte +6 -6
- src/lib/components/CopyToClipBoardBtn.svelte +6 -6
- src/lib/components/ScrollToBottomBtn.svelte +7 -7
- src/lib/components/Tooltip.svelte +3 -3
- src/lib/components/chat/ChatInput.svelte +5 -5
- src/lib/components/chat/ChatIntroduction.svelte +10 -10
- src/lib/components/chat/ChatMessage.svelte +12 -12
- src/lib/components/chat/ChatMessages.svelte +6 -6
- src/lib/components/chat/ChatWindow.svelte +9 -9
- src/lib/components/icons/IconChevron.svelte +1 -1
- src/lib/components/icons/IconCopy.svelte +1 -1
- src/lib/components/icons/IconLoading.svelte +1 -1
- src/lib/components/icons/Logo.svelte +1 -1
- src/lib/server/database.ts +7 -7
- src/lib/stores/pendingMessage.ts +2 -2
- src/lib/types/Conversation.ts +2 -2
- src/lib/types/Message.ts +1 -1
- src/lib/types/SharedConversation.ts +1 -1
- src/lib/utils/sha256.ts +2 -2
- src/routes/+layout.server.ts +7 -7
- src/routes/+layout.svelte +26 -26
- src/routes/+page.svelte +8 -8
- src/routes/conversation/+server.ts +10 -10
- src/routes/conversation/[id]/+page.server.ts +8 -8
- src/routes/conversation/[id]/+page.svelte +15 -15
- src/routes/conversation/[id]/+server.ts +32 -32
- src/routes/conversation/[id]/share/+server.ts +15 -15
- src/routes/r/[id]/+page.server.ts +6 -6
- src/routes/r/[id]/+page.svelte +2 -2
- src/styles/highlight-js.css +1 -1
- src/styles/main.css +1 -1
- svelte.config.js +8 -8
- vite.config.ts +6 -6
.eslintrc.cjs
CHANGED
@@ -1,23 +1,23 @@
|
|
1 |
module.exports = {
|
2 |
root: true,
|
3 |
-
parser:
|
4 |
-
extends: [
|
5 |
-
plugins: [
|
6 |
-
ignorePatterns: [
|
7 |
-
overrides: [{ files: [
|
8 |
settings: {
|
9 |
-
|
10 |
},
|
11 |
parserOptions: {
|
12 |
-
sourceType:
|
13 |
-
ecmaVersion: 2020
|
14 |
},
|
15 |
rules: {
|
16 |
-
|
17 |
},
|
18 |
env: {
|
19 |
browser: true,
|
20 |
es2017: true,
|
21 |
-
node: true
|
22 |
-
}
|
23 |
};
|
|
|
1 |
module.exports = {
|
2 |
root: true,
|
3 |
+
parser: "@typescript-eslint/parser",
|
4 |
+
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
5 |
+
plugins: ["svelte3", "@typescript-eslint"],
|
6 |
+
ignorePatterns: ["*.cjs"],
|
7 |
+
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
|
8 |
settings: {
|
9 |
+
"svelte3/typescript": () => require("typescript"),
|
10 |
},
|
11 |
parserOptions: {
|
12 |
+
sourceType: "module",
|
13 |
+
ecmaVersion: 2020,
|
14 |
},
|
15 |
rules: {
|
16 |
+
"no-shadow": ["error"],
|
17 |
},
|
18 |
env: {
|
19 |
browser: true,
|
20 |
es2017: true,
|
21 |
+
node: true,
|
22 |
+
},
|
23 |
};
|
.prettierrc
CHANGED
@@ -1,7 +1,6 @@
|
|
1 |
{
|
2 |
"useTabs": true,
|
3 |
-
"
|
4 |
-
"trailingComma": "none",
|
5 |
"printWidth": 100,
|
6 |
"plugins": ["prettier-plugin-svelte"],
|
7 |
"pluginSearchDirs": ["."],
|
|
|
1 |
{
|
2 |
"useTabs": true,
|
3 |
+
"trailingComma": "es5",
|
|
|
4 |
"printWidth": 100,
|
5 |
"plugins": ["prettier-plugin-svelte"],
|
6 |
"pluginSearchDirs": ["."],
|
src/app.html
CHANGED
@@ -10,8 +10,8 @@
|
|
10 |
<div style="display: contents">%sveltekit.body%</div>
|
11 |
</body>
|
12 |
<script>
|
13 |
-
if (localStorage.theme ===
|
14 |
-
document.documentElement.classList.add(
|
15 |
}
|
16 |
</script>
|
17 |
</html>
|
|
|
10 |
<div style="display: contents">%sveltekit.body%</div>
|
11 |
</body>
|
12 |
<script>
|
13 |
+
if (localStorage.theme === "dark") {
|
14 |
+
document.documentElement.classList.add("dark");
|
15 |
}
|
16 |
</script>
|
17 |
</html>
|
src/hooks.server.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
-
import { COOKIE_NAME } from
|
2 |
-
import type { Handle } from
|
3 |
-
import { addYears } from
|
4 |
|
5 |
export const handle: Handle = async ({ event, resolve }) => {
|
6 |
const token = event.cookies.get(COOKIE_NAME);
|
@@ -9,11 +9,11 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|
9 |
|
10 |
// Refresh cookie expiration date
|
11 |
event.cookies.set(COOKIE_NAME, event.locals.sessionId, {
|
12 |
-
path:
|
13 |
-
sameSite:
|
14 |
secure: true,
|
15 |
httpOnly: true,
|
16 |
-
expires: addYears(new Date(), 1)
|
17 |
});
|
18 |
|
19 |
const response = await resolve(event);
|
|
|
1 |
+
import { COOKIE_NAME } from "$env/static/private";
|
2 |
+
import type { Handle } from "@sveltejs/kit";
|
3 |
+
import { addYears } from "date-fns";
|
4 |
|
5 |
export const handle: Handle = async ({ event, resolve }) => {
|
6 |
const token = event.cookies.get(COOKIE_NAME);
|
|
|
9 |
|
10 |
// Refresh cookie expiration date
|
11 |
event.cookies.set(COOKIE_NAME, event.locals.sessionId, {
|
12 |
+
path: "/",
|
13 |
+
sameSite: "lax",
|
14 |
secure: true,
|
15 |
httpOnly: true,
|
16 |
+
expires: addYears(new Date(), 1),
|
17 |
});
|
18 |
|
19 |
const response = await resolve(event);
|
src/lib/actions/snapScrollToBottom.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
-
import { navigating } from
|
2 |
-
import { get } from
|
3 |
|
4 |
/**
|
5 |
* @param node element to snap scroll to bottom
|
@@ -31,18 +31,18 @@ export const snapScrollToBottom = (node: HTMLElement, dependency: any) => {
|
|
31 |
if (!force && isDetached && !get(navigating)) return;
|
32 |
|
33 |
node.scroll({
|
34 |
-
top: node.scrollHeight
|
35 |
});
|
36 |
};
|
37 |
|
38 |
-
node.addEventListener(
|
39 |
|
40 |
updateScroll({ force: true });
|
41 |
|
42 |
return {
|
43 |
update: updateScroll,
|
44 |
destroy: () => {
|
45 |
-
node.removeEventListener(
|
46 |
-
}
|
47 |
};
|
48 |
};
|
|
|
1 |
+
import { navigating } from "$app/stores";
|
2 |
+
import { get } from "svelte/store";
|
3 |
|
4 |
/**
|
5 |
* @param node element to snap scroll to bottom
|
|
|
31 |
if (!force && isDetached && !get(navigating)) return;
|
32 |
|
33 |
node.scroll({
|
34 |
+
top: node.scrollHeight,
|
35 |
});
|
36 |
};
|
37 |
|
38 |
+
node.addEventListener("scroll", handleScroll);
|
39 |
|
40 |
updateScroll({ force: true });
|
41 |
|
42 |
return {
|
43 |
update: updateScroll,
|
44 |
destroy: () => {
|
45 |
+
node.removeEventListener("scroll", handleScroll);
|
46 |
+
},
|
47 |
};
|
48 |
};
|
src/lib/buildPrompt.ts
CHANGED
@@ -1,9 +1,9 @@
|
|
1 |
import {
|
2 |
PUBLIC_ASSISTANT_MESSAGE_TOKEN,
|
3 |
PUBLIC_SEP_TOKEN,
|
4 |
-
PUBLIC_USER_MESSAGE_TOKEN
|
5 |
-
} from
|
6 |
-
import type { Message } from
|
7 |
|
8 |
/**
|
9 |
* Convert [{user: "assistant", content: "hi"}, {user: "user", content: "hello"}] to:
|
@@ -15,11 +15,11 @@ export function buildPrompt(messages: Message[]): string {
|
|
15 |
messages
|
16 |
.map(
|
17 |
(m) =>
|
18 |
-
(m.from ===
|
19 |
? PUBLIC_USER_MESSAGE_TOKEN + m.content
|
20 |
: PUBLIC_ASSISTANT_MESSAGE_TOKEN + m.content) +
|
21 |
-
(m.content.endsWith(PUBLIC_SEP_TOKEN) ?
|
22 |
)
|
23 |
-
.join(
|
24 |
);
|
25 |
}
|
|
|
1 |
import {
|
2 |
PUBLIC_ASSISTANT_MESSAGE_TOKEN,
|
3 |
PUBLIC_SEP_TOKEN,
|
4 |
+
PUBLIC_USER_MESSAGE_TOKEN,
|
5 |
+
} from "$env/static/public";
|
6 |
+
import type { Message } from "./types/Message";
|
7 |
|
8 |
/**
|
9 |
* Convert [{user: "assistant", content: "hi"}, {user: "user", content: "hello"}] to:
|
|
|
15 |
messages
|
16 |
.map(
|
17 |
(m) =>
|
18 |
+
(m.from === "user"
|
19 |
? PUBLIC_USER_MESSAGE_TOKEN + m.content
|
20 |
: PUBLIC_ASSISTANT_MESSAGE_TOKEN + m.content) +
|
21 |
+
(m.content.endsWith(PUBLIC_SEP_TOKEN) ? "" : PUBLIC_SEP_TOKEN)
|
22 |
)
|
23 |
+
.join("") + PUBLIC_ASSISTANT_MESSAGE_TOKEN
|
24 |
);
|
25 |
}
|
src/lib/components/CodeBlock.svelte
CHANGED
@@ -1,14 +1,14 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { afterUpdate } from
|
3 |
-
import CopyToClipBoardBtn from
|
4 |
|
5 |
-
export let code =
|
6 |
-
export let lang =
|
7 |
|
8 |
-
$: highlightedCode =
|
9 |
|
10 |
afterUpdate(async () => {
|
11 |
-
const { default: hljs } = await import(
|
12 |
const language = hljs.getLanguage(lang);
|
13 |
|
14 |
highlightedCode = hljs.highlightAuto(code, language?.aliases).value;
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { afterUpdate } from "svelte";
|
3 |
+
import CopyToClipBoardBtn from "./CopyToClipBoardBtn.svelte";
|
4 |
|
5 |
+
export let code = "";
|
6 |
+
export let lang = "";
|
7 |
|
8 |
+
$: highlightedCode = "";
|
9 |
|
10 |
afterUpdate(async () => {
|
11 |
+
const { default: hljs } = await import("highlight.js");
|
12 |
const language = hljs.getLanguage(lang);
|
13 |
|
14 |
highlightedCode = hljs.highlightAuto(code, language?.aliases).value;
|
src/lib/components/CopyToClipBoardBtn.svelte
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { onDestroy } from
|
3 |
|
4 |
-
import IconCopy from
|
5 |
-
import Tooltip from
|
6 |
|
7 |
-
export let classNames =
|
8 |
export let value: string;
|
9 |
|
10 |
let isSuccess = false;
|
@@ -39,12 +39,12 @@
|
|
39 |
{!isSuccess && 'text-gray-200 dark:text-gray-200'}
|
40 |
{isSuccess && 'text-green-500'}
|
41 |
"
|
42 |
-
title={
|
43 |
type="button"
|
44 |
on:click={handleClick}
|
45 |
>
|
46 |
<span class="relative">
|
47 |
<IconCopy />
|
48 |
-
<Tooltip classNames={isSuccess ?
|
49 |
</span>
|
50 |
</button>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { onDestroy } from "svelte";
|
3 |
|
4 |
+
import IconCopy from "./icons/IconCopy.svelte";
|
5 |
+
import Tooltip from "./Tooltip.svelte";
|
6 |
|
7 |
+
export let classNames = "";
|
8 |
export let value: string;
|
9 |
|
10 |
let isSuccess = false;
|
|
|
39 |
{!isSuccess && 'text-gray-200 dark:text-gray-200'}
|
40 |
{isSuccess && 'text-green-500'}
|
41 |
"
|
42 |
+
title={"Copy to clipboard"}
|
43 |
type="button"
|
44 |
on:click={handleClick}
|
45 |
>
|
46 |
<span class="relative">
|
47 |
<IconCopy />
|
48 |
+
<Tooltip classNames={isSuccess ? "opacity-100" : "opacity-0"} />
|
49 |
</span>
|
50 |
</button>
|
src/lib/components/ScrollToBottomBtn.svelte
CHANGED
@@ -1,16 +1,16 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { fade } from
|
3 |
-
import IconChevron from
|
4 |
-
import { onDestroy } from
|
5 |
|
6 |
export let scrollNode: HTMLElement;
|
7 |
export { className as class };
|
8 |
|
9 |
let visible: boolean = false;
|
10 |
-
let className =
|
11 |
|
12 |
$: if (scrollNode) {
|
13 |
-
scrollNode.addEventListener(
|
14 |
}
|
15 |
|
16 |
function onScroll() {
|
@@ -20,14 +20,14 @@
|
|
20 |
|
21 |
onDestroy(() => {
|
22 |
if (!scrollNode) return;
|
23 |
-
scrollNode.removeEventListener(
|
24 |
});
|
25 |
</script>
|
26 |
|
27 |
{#if visible}
|
28 |
<button
|
29 |
transition:fade={{ duration: 150 }}
|
30 |
-
on:click={() => scrollNode.scrollTo({ top: scrollNode.scrollHeight, behavior:
|
31 |
class="absolute flex rounded-full border w-10 h-10 items-center justify-center shadow bg-white dark:bg-gray-700 dark:border-gray-600 {className}"
|
32 |
><IconChevron /></button
|
33 |
>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { fade } from "svelte/transition";
|
3 |
+
import IconChevron from "./icons/IconChevron.svelte";
|
4 |
+
import { onDestroy } from "svelte";
|
5 |
|
6 |
export let scrollNode: HTMLElement;
|
7 |
export { className as class };
|
8 |
|
9 |
let visible: boolean = false;
|
10 |
+
let className = "";
|
11 |
|
12 |
$: if (scrollNode) {
|
13 |
+
scrollNode.addEventListener("scroll", onScroll);
|
14 |
}
|
15 |
|
16 |
function onScroll() {
|
|
|
20 |
|
21 |
onDestroy(() => {
|
22 |
if (!scrollNode) return;
|
23 |
+
scrollNode.removeEventListener("scroll", onScroll);
|
24 |
});
|
25 |
</script>
|
26 |
|
27 |
{#if visible}
|
28 |
<button
|
29 |
transition:fade={{ duration: 150 }}
|
30 |
+
on:click={() => scrollNode.scrollTo({ top: scrollNode.scrollHeight, behavior: "smooth" })}
|
31 |
class="absolute flex rounded-full border w-10 h-10 items-center justify-center shadow bg-white dark:bg-gray-700 dark:border-gray-600 {className}"
|
32 |
><IconChevron /></button
|
33 |
>
|
src/lib/components/Tooltip.svelte
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
<script lang="ts">
|
2 |
-
export let classNames =
|
3 |
-
export let label =
|
4 |
-
export let position =
|
5 |
</script>
|
6 |
|
7 |
<div
|
|
|
1 |
<script lang="ts">
|
2 |
+
export let classNames = "";
|
3 |
+
export let label = "Copied";
|
4 |
+
export let position = "left-1/2 top-full transform -translate-x-1/2 translate-y-2";
|
5 |
</script>
|
6 |
|
7 |
<div
|
src/lib/components/chat/ChatInput.svelte
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
<script lang="ts">
|
2 |
-
export let value =
|
3 |
export let minRows = 1;
|
4 |
export let maxRows: null | number = null;
|
5 |
-
export let placeholder =
|
6 |
export let disabled = false;
|
7 |
export let autofocus = false;
|
8 |
|
@@ -11,10 +11,10 @@
|
|
11 |
|
12 |
function handleKeydown(event: KeyboardEvent) {
|
13 |
// submit on enter
|
14 |
-
if (event.key ===
|
15 |
event.preventDefault();
|
16 |
|
17 |
-
textareaElement.closest(
|
18 |
}
|
19 |
}
|
20 |
|
@@ -25,7 +25,7 @@
|
|
25 |
<pre
|
26 |
class="invisible py-3"
|
27 |
aria-hidden="true"
|
28 |
-
style="min-height: {minHeight}; max-height: {maxHeight}">{value +
|
29 |
|
30 |
<textarea
|
31 |
tabindex="0"
|
|
|
1 |
<script lang="ts">
|
2 |
+
export let value = "";
|
3 |
export let minRows = 1;
|
4 |
export let maxRows: null | number = null;
|
5 |
+
export let placeholder = "";
|
6 |
export let disabled = false;
|
7 |
export let autofocus = false;
|
8 |
|
|
|
11 |
|
12 |
function handleKeydown(event: KeyboardEvent) {
|
13 |
// submit on enter
|
14 |
+
if (event.key === "Enter" && !event.shiftKey) {
|
15 |
event.preventDefault();
|
16 |
|
17 |
+
textareaElement.closest("form")?.requestSubmit();
|
18 |
}
|
19 |
}
|
20 |
|
|
|
25 |
<pre
|
26 |
class="invisible py-3"
|
27 |
aria-hidden="true"
|
28 |
+
style="min-height: {minHeight}; max-height: {maxHeight}">{value + " \n"}</pre>
|
29 |
|
30 |
<textarea
|
31 |
tabindex="0"
|
src/lib/components/chat/ChatIntroduction.svelte
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { PUBLIC_DISABLE_INTRO_TILES, PUBLIC_MODEL_NAME } from
|
3 |
|
4 |
-
import Logo from
|
5 |
-
import CarbonArrowUpRight from
|
6 |
-
import CarbonEarth from
|
7 |
-
import { createEventDispatcher } from
|
8 |
|
9 |
const dispatch = createEventDispatcher<{ message: string }>();
|
10 |
</script>
|
@@ -62,7 +62,7 @@
|
|
62 |
</div>
|
63 |
</div>
|
64 |
</div>
|
65 |
-
{#if PUBLIC_DISABLE_INTRO_TILES !==
|
66 |
<div class="lg:col-span-3 lg:mt-12">
|
67 |
<p class="mb-3 text-gray-600 dark:text-gray-300">Examples</p>
|
68 |
<div class="grid lg:grid-cols-3 gap-3 lg:gap-5">
|
@@ -71,8 +71,8 @@
|
|
71 |
class="text-gray-600 dark:text-gray-300 p-4 bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 hover:bg-gray-100 border dark:border-gray-800 rounded-xl"
|
72 |
on:click={() =>
|
73 |
dispatch(
|
74 |
-
|
75 |
-
|
76 |
)}
|
77 |
>
|
78 |
"Write an email from bullet list"
|
@@ -81,14 +81,14 @@
|
|
81 |
type="button"
|
82 |
class="text-gray-600 dark:text-gray-300 p-4 bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 hover:bg-gray-100 border dark:border-gray-800 rounded-xl"
|
83 |
on:click={() =>
|
84 |
-
dispatch(
|
85 |
>
|
86 |
"Code a snake game"
|
87 |
</button>
|
88 |
<button
|
89 |
type="button"
|
90 |
class="text-gray-600 dark:text-gray-300 p-4 bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 hover:bg-gray-100 border dark:border-gray-800 rounded-xl"
|
91 |
-
on:click={() => dispatch(
|
92 |
>
|
93 |
"Assist in a task"
|
94 |
</button>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { PUBLIC_DISABLE_INTRO_TILES, PUBLIC_MODEL_NAME } from "$env/static/public";
|
3 |
|
4 |
+
import Logo from "$lib/components/icons/Logo.svelte";
|
5 |
+
import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
|
6 |
+
import CarbonEarth from "~icons/carbon/earth";
|
7 |
+
import { createEventDispatcher } from "svelte";
|
8 |
|
9 |
const dispatch = createEventDispatcher<{ message: string }>();
|
10 |
</script>
|
|
|
62 |
</div>
|
63 |
</div>
|
64 |
</div>
|
65 |
+
{#if PUBLIC_DISABLE_INTRO_TILES !== "true"}
|
66 |
<div class="lg:col-span-3 lg:mt-12">
|
67 |
<p class="mb-3 text-gray-600 dark:text-gray-300">Examples</p>
|
68 |
<div class="grid lg:grid-cols-3 gap-3 lg:gap-5">
|
|
|
71 |
class="text-gray-600 dark:text-gray-300 p-4 bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 hover:bg-gray-100 border dark:border-gray-800 rounded-xl"
|
72 |
on:click={() =>
|
73 |
dispatch(
|
74 |
+
"message",
|
75 |
+
"Write an email from bullet list: \n\n- Buy milk\n- Buy eggs\n- Buy bread"
|
76 |
)}
|
77 |
>
|
78 |
"Write an email from bullet list"
|
|
|
81 |
type="button"
|
82 |
class="text-gray-600 dark:text-gray-300 p-4 bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 hover:bg-gray-100 border dark:border-gray-800 rounded-xl"
|
83 |
on:click={() =>
|
84 |
+
dispatch("message", "Code a snake game in python, the snake should be red")}
|
85 |
>
|
86 |
"Code a snake game"
|
87 |
</button>
|
88 |
<button
|
89 |
type="button"
|
90 |
class="text-gray-600 dark:text-gray-300 p-4 bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 hover:bg-gray-100 border dark:border-gray-800 rounded-xl"
|
91 |
+
on:click={() => dispatch("message", "How do I make a lemon cheesecake?")}
|
92 |
>
|
93 |
"Assist in a task"
|
94 |
</button>
|
src/lib/components/chat/ChatMessage.svelte
CHANGED
@@ -1,14 +1,14 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { marked } from
|
3 |
-
import type { Message } from
|
4 |
-
import { afterUpdate } from
|
5 |
-
import { deepestChild } from
|
6 |
|
7 |
-
import CodeBlock from
|
8 |
-
import IconLoading from
|
9 |
|
10 |
function sanitizeMd(md: string) {
|
11 |
-
return md.replaceAll(
|
12 |
}
|
13 |
|
14 |
export let message: Message;
|
@@ -20,7 +20,7 @@
|
|
20 |
|
21 |
const options: marked.MarkedOptions = {
|
22 |
...marked.getDefaults(),
|
23 |
-
gfm: true
|
24 |
};
|
25 |
|
26 |
$: tokens = marked.lexer(sanitizeMd(message.content));
|
@@ -35,7 +35,7 @@
|
|
35 |
if (contentEl) {
|
36 |
loadingEl = new IconLoading({
|
37 |
target: deepestChild(contentEl),
|
38 |
-
props: { classNames:
|
39 |
});
|
40 |
}
|
41 |
}, 600);
|
@@ -43,7 +43,7 @@
|
|
43 |
});
|
44 |
</script>
|
45 |
|
46 |
-
{#if message.from ===
|
47 |
<div class="flex items-start justify-start gap-4 leading-relaxed">
|
48 |
<img
|
49 |
alt=""
|
@@ -61,7 +61,7 @@
|
|
61 |
bind:this={contentEl}
|
62 |
>
|
63 |
{#each tokens as token}
|
64 |
-
{#if token.type ===
|
65 |
<CodeBlock lang={token.lang} code={token.text} />
|
66 |
{:else}
|
67 |
{@html marked.parser([token], options)}
|
@@ -71,7 +71,7 @@
|
|
71 |
</div>
|
72 |
</div>
|
73 |
{/if}
|
74 |
-
{#if message.from ===
|
75 |
<div class="flex items-start justify-start gap-4">
|
76 |
<div class="mt-5 w-3 h-3 flex-none rounded-full" />
|
77 |
<div class="rounded-2xl px-5 py-3.5 text-gray-500 dark:text-gray-400 whitespace-break-spaces">
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { marked } from "marked";
|
3 |
+
import type { Message } from "$lib/types/Message";
|
4 |
+
import { afterUpdate } from "svelte";
|
5 |
+
import { deepestChild } from "$lib/utils/dom";
|
6 |
|
7 |
+
import CodeBlock from "../CodeBlock.svelte";
|
8 |
+
import IconLoading from "../icons/IconLoading.svelte";
|
9 |
|
10 |
function sanitizeMd(md: string) {
|
11 |
+
return md.replaceAll("<", "<");
|
12 |
}
|
13 |
|
14 |
export let message: Message;
|
|
|
20 |
|
21 |
const options: marked.MarkedOptions = {
|
22 |
...marked.getDefaults(),
|
23 |
+
gfm: true,
|
24 |
};
|
25 |
|
26 |
$: tokens = marked.lexer(sanitizeMd(message.content));
|
|
|
35 |
if (contentEl) {
|
36 |
loadingEl = new IconLoading({
|
37 |
target: deepestChild(contentEl),
|
38 |
+
props: { classNames: "loading inline ml-2" },
|
39 |
});
|
40 |
}
|
41 |
}, 600);
|
|
|
43 |
});
|
44 |
</script>
|
45 |
|
46 |
+
{#if message.from === "assistant"}
|
47 |
<div class="flex items-start justify-start gap-4 leading-relaxed">
|
48 |
<img
|
49 |
alt=""
|
|
|
61 |
bind:this={contentEl}
|
62 |
>
|
63 |
{#each tokens as token}
|
64 |
+
{#if token.type === "code"}
|
65 |
<CodeBlock lang={token.lang} code={token.text} />
|
66 |
{:else}
|
67 |
{@html marked.parser([token], options)}
|
|
|
71 |
</div>
|
72 |
</div>
|
73 |
{/if}
|
74 |
+
{#if message.from === "user"}
|
75 |
<div class="flex items-start justify-start gap-4">
|
76 |
<div class="mt-5 w-3 h-3 flex-none rounded-full" />
|
77 |
<div class="rounded-2xl px-5 py-3.5 text-gray-500 dark:text-gray-400 whitespace-break-spaces">
|
src/lib/components/chat/ChatMessages.svelte
CHANGED
@@ -1,9 +1,9 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import type { Message } from
|
3 |
-
import { snapScrollToBottom } from
|
4 |
-
import ScrollToBottomBtn from
|
5 |
-
import ChatIntroduction from
|
6 |
-
import ChatMessage from
|
7 |
|
8 |
export let messages: Message[];
|
9 |
export let loading: boolean;
|
@@ -20,7 +20,7 @@
|
|
20 |
<ChatIntroduction on:message />
|
21 |
{/each}
|
22 |
{#if pending}
|
23 |
-
<ChatMessage message={{ from:
|
24 |
{/if}
|
25 |
<div class="h-32 flex-none" />
|
26 |
</div>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import type { Message } from "$lib/types/Message";
|
3 |
+
import { snapScrollToBottom } from "$lib/actions/snapScrollToBottom";
|
4 |
+
import ScrollToBottomBtn from "$lib/components/ScrollToBottomBtn.svelte";
|
5 |
+
import ChatIntroduction from "./ChatIntroduction.svelte";
|
6 |
+
import ChatMessage from "./ChatMessage.svelte";
|
7 |
|
8 |
export let messages: Message[];
|
9 |
export let loading: boolean;
|
|
|
20 |
<ChatIntroduction on:message />
|
21 |
{/each}
|
22 |
{#if pending}
|
23 |
+
<ChatMessage message={{ from: "assistant", content: "" }} />
|
24 |
{/if}
|
25 |
<div class="h-32 flex-none" />
|
26 |
</div>
|
src/lib/components/chat/ChatWindow.svelte
CHANGED
@@ -1,13 +1,13 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import type { Message } from
|
3 |
-
import { createEventDispatcher } from
|
4 |
|
5 |
-
import CarbonAdd from
|
6 |
-
import CarbonSendAltFilled from
|
7 |
-
import CarbonTextAlignJustify from
|
8 |
|
9 |
-
import ChatMessages from
|
10 |
-
import ChatInput from
|
11 |
|
12 |
export let messages: Message[] = [];
|
13 |
export let disabled: boolean = false;
|
@@ -32,8 +32,8 @@
|
|
32 |
<form
|
33 |
on:submit|preventDefault={() => {
|
34 |
if (loading) return;
|
35 |
-
dispatch(
|
36 |
-
message =
|
37 |
}}
|
38 |
class="w-full relative flex items-center rounded-xl flex-1 max-w-4xl border bg-gray-100 focus-within:border-gray-300 dark:bg-gray-700 dark:border-gray-600 dark:focus-within:border-gray-500 "
|
39 |
>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import type { Message } from "$lib/types/Message";
|
3 |
+
import { createEventDispatcher } from "svelte";
|
4 |
|
5 |
+
import CarbonAdd from "~icons/carbon/add";
|
6 |
+
import CarbonSendAltFilled from "~icons/carbon/send-alt-filled";
|
7 |
+
import CarbonTextAlignJustify from "~icons/carbon/text-align-justify";
|
8 |
|
9 |
+
import ChatMessages from "./ChatMessages.svelte";
|
10 |
+
import ChatInput from "./ChatInput.svelte";
|
11 |
|
12 |
export let messages: Message[] = [];
|
13 |
export let disabled: boolean = false;
|
|
|
32 |
<form
|
33 |
on:submit|preventDefault={() => {
|
34 |
if (loading) return;
|
35 |
+
dispatch("message", message);
|
36 |
+
message = "";
|
37 |
}}
|
38 |
class="w-full relative flex items-center rounded-xl flex-1 max-w-4xl border bg-gray-100 focus-within:border-gray-300 dark:bg-gray-700 dark:border-gray-600 dark:focus-within:border-gray-500 "
|
39 |
>
|
src/lib/components/icons/IconChevron.svelte
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
<script lang="ts">
|
2 |
-
export let classNames: string =
|
3 |
</script>
|
4 |
|
5 |
<svg
|
|
|
1 |
<script lang="ts">
|
2 |
+
export let classNames: string = "";
|
3 |
</script>
|
4 |
|
5 |
<svg
|
src/lib/components/icons/IconCopy.svelte
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
<script lang="ts">
|
2 |
-
export let classNames =
|
3 |
</script>
|
4 |
|
5 |
<svg
|
|
|
1 |
<script lang="ts">
|
2 |
+
export let classNames = "";
|
3 |
</script>
|
4 |
|
5 |
<svg
|
src/lib/components/icons/IconLoading.svelte
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
<script lang="ts">
|
2 |
-
export let classNames: string =
|
3 |
</script>
|
4 |
|
5 |
<svg
|
|
|
1 |
<script lang="ts">
|
2 |
+
export let classNames: string = "";
|
3 |
</script>
|
4 |
|
5 |
<svg
|
src/lib/components/icons/Logo.svelte
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
<script lang="ts">
|
2 |
-
export let classNames: string =
|
3 |
</script>
|
4 |
|
5 |
<svg
|
|
|
1 |
<script lang="ts">
|
2 |
+
export let classNames: string = "";
|
3 |
</script>
|
4 |
|
5 |
<svg
|
src/lib/server/database.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
-
import { MONGODB_URL, MONGODB_DB_NAME } from
|
2 |
-
import { MongoClient } from
|
3 |
-
import type { Conversation } from
|
4 |
-
import type { SharedConversation } from
|
5 |
|
6 |
const client = new MongoClient(MONGODB_URL, {
|
7 |
// directConnection: true
|
@@ -11,13 +11,13 @@ export const connectPromise = client.connect().catch(console.error);
|
|
11 |
|
12 |
const db = client.db(MONGODB_DB_NAME);
|
13 |
|
14 |
-
const conversations = db.collection<Conversation>(
|
15 |
-
const sharedConversations = db.collection<SharedConversation>(
|
16 |
|
17 |
export { client, db };
|
18 |
export const collections = { conversations, sharedConversations };
|
19 |
|
20 |
-
client.on(
|
21 |
conversations.createIndex({ sessionId: 1, updatedAt: -1 });
|
22 |
sharedConversations.createIndex({ hash: 1 }, { unique: true });
|
23 |
});
|
|
|
1 |
+
import { MONGODB_URL, MONGODB_DB_NAME } from "$env/static/private";
|
2 |
+
import { MongoClient } from "mongodb";
|
3 |
+
import type { Conversation } from "$lib/types/Conversation";
|
4 |
+
import type { SharedConversation } from "$lib/types/SharedConversation";
|
5 |
|
6 |
const client = new MongoClient(MONGODB_URL, {
|
7 |
// directConnection: true
|
|
|
11 |
|
12 |
const db = client.db(MONGODB_DB_NAME);
|
13 |
|
14 |
+
const conversations = db.collection<Conversation>("conversations");
|
15 |
+
const sharedConversations = db.collection<SharedConversation>("sharedConversations");
|
16 |
|
17 |
export { client, db };
|
18 |
export const collections = { conversations, sharedConversations };
|
19 |
|
20 |
+
client.on("open", () => {
|
21 |
conversations.createIndex({ sessionId: 1, updatedAt: -1 });
|
22 |
sharedConversations.createIndex({ hash: 1 }, { unique: true });
|
23 |
});
|
src/lib/stores/pendingMessage.ts
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
-
import { writable } from
|
2 |
|
3 |
-
export const pendingMessage = writable<string>(
|
|
|
1 |
+
import { writable } from "svelte/store";
|
2 |
|
3 |
+
export const pendingMessage = writable<string>("");
|
src/lib/types/Conversation.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
-
import type { ObjectId } from
|
2 |
-
import type { Message } from
|
3 |
|
4 |
export interface Conversation {
|
5 |
_id: ObjectId;
|
|
|
1 |
+
import type { ObjectId } from "mongodb";
|
2 |
+
import type { Message } from "./Message";
|
3 |
|
4 |
export interface Conversation {
|
5 |
_id: ObjectId;
|
src/lib/types/Message.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
export interface Message {
|
2 |
-
from:
|
3 |
content: string;
|
4 |
}
|
|
|
1 |
export interface Message {
|
2 |
+
from: "user" | "assistant";
|
3 |
content: string;
|
4 |
}
|
src/lib/types/SharedConversation.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import type { Message } from
|
2 |
|
3 |
export interface SharedConversation {
|
4 |
_id: string;
|
|
|
1 |
+
import type { Message } from "./Message";
|
2 |
|
3 |
export interface SharedConversation {
|
4 |
_id: string;
|
src/lib/utils/sha256.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
export async function sha256(input: string): Promise<string> {
|
2 |
const utf8 = new TextEncoder().encode(input);
|
3 |
-
const hashBuffer = await crypto.subtle.digest(
|
4 |
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
5 |
-
const hashHex = hashArray.map((bytes) => bytes.toString(16).padStart(2,
|
6 |
return hashHex;
|
7 |
}
|
|
|
1 |
export async function sha256(input: string): Promise<string> {
|
2 |
const utf8 = new TextEncoder().encode(input);
|
3 |
+
const hashBuffer = await crypto.subtle.digest("SHA-256", utf8);
|
4 |
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
5 |
+
const hashHex = hashArray.map((bytes) => bytes.toString(16).padStart(2, "0")).join("");
|
6 |
return hashHex;
|
7 |
}
|
src/routes/+layout.server.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
-
import type { LayoutServerLoad } from
|
2 |
-
import { collections } from
|
3 |
-
import type { Conversation } from
|
4 |
|
5 |
export const load: LayoutServerLoad = async (event) => {
|
6 |
const { conversations } = collections;
|
@@ -8,16 +8,16 @@ export const load: LayoutServerLoad = async (event) => {
|
|
8 |
return {
|
9 |
conversations: await conversations
|
10 |
.find({
|
11 |
-
sessionId: event.locals.sessionId
|
12 |
})
|
13 |
.sort({ updatedAt: -1 })
|
14 |
-
.project<Pick<Conversation,
|
15 |
title: 1,
|
16 |
_id: 1,
|
17 |
updatedAt: 1,
|
18 |
-
createdAt: 1
|
19 |
})
|
20 |
.map((conv) => ({ id: conv._id.toString(), title: conv.title }))
|
21 |
-
.toArray()
|
22 |
};
|
23 |
};
|
|
|
1 |
+
import type { LayoutServerLoad } from "./$types";
|
2 |
+
import { collections } from "$lib/server/database";
|
3 |
+
import type { Conversation } from "$lib/types/Conversation";
|
4 |
|
5 |
export const load: LayoutServerLoad = async (event) => {
|
6 |
const { conversations } = collections;
|
|
|
8 |
return {
|
9 |
conversations: await conversations
|
10 |
.find({
|
11 |
+
sessionId: event.locals.sessionId,
|
12 |
})
|
13 |
.sort({ updatedAt: -1 })
|
14 |
+
.project<Pick<Conversation, "title" | "_id" | "updatedAt" | "createdAt">>({
|
15 |
title: 1,
|
16 |
_id: 1,
|
17 |
updatedAt: 1,
|
18 |
+
createdAt: 1,
|
19 |
})
|
20 |
.map((conv) => ({ id: conv._id.toString(), title: conv.title }))
|
21 |
+
.toArray(),
|
22 |
};
|
23 |
};
|
src/routes/+layout.svelte
CHANGED
@@ -1,37 +1,37 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { goto, invalidateAll } from
|
3 |
-
import { page } from
|
4 |
-
import
|
5 |
-
import type { LayoutData } from
|
6 |
|
7 |
-
import CarbonTrashCan from
|
8 |
-
import CarbonExport from
|
9 |
-
import { base } from
|
10 |
|
11 |
export let data: LayoutData;
|
12 |
|
13 |
function switchTheme() {
|
14 |
-
const { classList } = document.querySelector(
|
15 |
-
if (classList.contains(
|
16 |
-
classList.remove(
|
17 |
-
localStorage.theme =
|
18 |
} else {
|
19 |
-
classList.add(
|
20 |
-
localStorage.theme =
|
21 |
}
|
22 |
}
|
23 |
|
24 |
async function shareConversation(id: string, title: string) {
|
25 |
try {
|
26 |
const res = await fetch(`${base}/conversation/${id}/share`, {
|
27 |
-
method:
|
28 |
headers: {
|
29 |
-
|
30 |
-
}
|
31 |
});
|
32 |
|
33 |
if (!res.ok) {
|
34 |
-
alert(
|
35 |
return;
|
36 |
}
|
37 |
|
@@ -40,29 +40,29 @@
|
|
40 |
if (navigator.share) {
|
41 |
navigator.share({
|
42 |
title,
|
43 |
-
text:
|
44 |
-
url
|
45 |
});
|
46 |
} else {
|
47 |
-
prompt(
|
48 |
}
|
49 |
} catch (err) {
|
50 |
console.error(err);
|
51 |
-
alert(
|
52 |
}
|
53 |
}
|
54 |
|
55 |
async function deleteConversation(id: string) {
|
56 |
try {
|
57 |
const res = await fetch(`${base}/conversation/${id}`, {
|
58 |
-
method:
|
59 |
headers: {
|
60 |
-
|
61 |
-
}
|
62 |
});
|
63 |
|
64 |
if (!res.ok) {
|
65 |
-
alert(
|
66 |
return;
|
67 |
}
|
68 |
|
@@ -73,7 +73,7 @@
|
|
73 |
}
|
74 |
} catch (err) {
|
75 |
console.error(err);
|
76 |
-
alert(
|
77 |
}
|
78 |
}
|
79 |
</script>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { goto, invalidateAll } from "$app/navigation";
|
3 |
+
import { page } from "$app/stores";
|
4 |
+
import "../styles/main.css";
|
5 |
+
import type { LayoutData } from "./$types";
|
6 |
|
7 |
+
import CarbonTrashCan from "~icons/carbon/trash-can";
|
8 |
+
import CarbonExport from "~icons/carbon/export";
|
9 |
+
import { base } from "$app/paths";
|
10 |
|
11 |
export let data: LayoutData;
|
12 |
|
13 |
function switchTheme() {
|
14 |
+
const { classList } = document.querySelector("html") as HTMLElement;
|
15 |
+
if (classList.contains("dark")) {
|
16 |
+
classList.remove("dark");
|
17 |
+
localStorage.theme = "light";
|
18 |
} else {
|
19 |
+
classList.add("dark");
|
20 |
+
localStorage.theme = "dark";
|
21 |
}
|
22 |
}
|
23 |
|
24 |
async function shareConversation(id: string, title: string) {
|
25 |
try {
|
26 |
const res = await fetch(`${base}/conversation/${id}/share`, {
|
27 |
+
method: "POST",
|
28 |
headers: {
|
29 |
+
"Content-Type": "application/json",
|
30 |
+
},
|
31 |
});
|
32 |
|
33 |
if (!res.ok) {
|
34 |
+
alert("Error while sharing conversation: " + (await res.text()));
|
35 |
return;
|
36 |
}
|
37 |
|
|
|
40 |
if (navigator.share) {
|
41 |
navigator.share({
|
42 |
title,
|
43 |
+
text: "Share this chat with others",
|
44 |
+
url,
|
45 |
});
|
46 |
} else {
|
47 |
+
prompt("Share this link with your friends:", url);
|
48 |
}
|
49 |
} catch (err) {
|
50 |
console.error(err);
|
51 |
+
alert("Error while sharing conversation: " + err);
|
52 |
}
|
53 |
}
|
54 |
|
55 |
async function deleteConversation(id: string) {
|
56 |
try {
|
57 |
const res = await fetch(`${base}/conversation/${id}`, {
|
58 |
+
method: "DELETE",
|
59 |
headers: {
|
60 |
+
"Content-Type": "application/json",
|
61 |
+
},
|
62 |
});
|
63 |
|
64 |
if (!res.ok) {
|
65 |
+
alert("Error while deleting conversation: " + (await res.text()));
|
66 |
return;
|
67 |
}
|
68 |
|
|
|
73 |
}
|
74 |
} catch (err) {
|
75 |
console.error(err);
|
76 |
+
alert("Error while deleting conversation: " + err);
|
77 |
}
|
78 |
}
|
79 |
</script>
|
src/routes/+page.svelte
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { goto } from
|
3 |
-
import { base } from
|
4 |
-
import ChatWindow from
|
5 |
-
import { pendingMessage } from
|
6 |
|
7 |
let loading = false;
|
8 |
|
@@ -10,14 +10,14 @@
|
|
10 |
try {
|
11 |
loading = true;
|
12 |
const res = await fetch(`${base}/conversation`, {
|
13 |
-
method:
|
14 |
headers: {
|
15 |
-
|
16 |
-
}
|
17 |
});
|
18 |
|
19 |
if (!res.ok) {
|
20 |
-
alert(
|
21 |
return;
|
22 |
}
|
23 |
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { goto } from "$app/navigation";
|
3 |
+
import { base } from "$app/paths";
|
4 |
+
import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
|
5 |
+
import { pendingMessage } from "$lib/stores/pendingMessage";
|
6 |
|
7 |
let loading = false;
|
8 |
|
|
|
10 |
try {
|
11 |
loading = true;
|
12 |
const res = await fetch(`${base}/conversation`, {
|
13 |
+
method: "POST",
|
14 |
headers: {
|
15 |
+
"Content-Type": "application/json",
|
16 |
+
},
|
17 |
});
|
18 |
|
19 |
if (!res.ok) {
|
20 |
+
alert("Error while creating conversation: " + (await res.text()));
|
21 |
return;
|
22 |
}
|
23 |
|
src/routes/conversation/+server.ts
CHANGED
@@ -1,29 +1,29 @@
|
|
1 |
-
import type { RequestHandler } from
|
2 |
-
import { collections } from
|
3 |
-
import { ObjectId } from
|
4 |
-
import { redirect } from
|
5 |
-
import { base } from
|
6 |
|
7 |
export const POST: RequestHandler = async (input) => {
|
8 |
const res = await collections.conversations.insertOne({
|
9 |
_id: new ObjectId(),
|
10 |
title:
|
11 |
-
|
12 |
((await collections.conversations.countDocuments({ sessionId: input.locals.sessionId })) + 1),
|
13 |
messages: [],
|
14 |
createdAt: new Date(),
|
15 |
updatedAt: new Date(),
|
16 |
-
sessionId: input.locals.sessionId
|
17 |
});
|
18 |
|
19 |
return new Response(
|
20 |
JSON.stringify({
|
21 |
-
conversationId: res.insertedId.toString()
|
22 |
}),
|
23 |
-
{ headers: {
|
24 |
);
|
25 |
};
|
26 |
|
27 |
export const GET: RequestHandler = async () => {
|
28 |
-
throw redirect(301, base ||
|
29 |
};
|
|
|
1 |
+
import type { RequestHandler } from "./$types";
|
2 |
+
import { collections } from "$lib/server/database";
|
3 |
+
import { ObjectId } from "mongodb";
|
4 |
+
import { redirect } from "@sveltejs/kit";
|
5 |
+
import { base } from "$app/paths";
|
6 |
|
7 |
export const POST: RequestHandler = async (input) => {
|
8 |
const res = await collections.conversations.insertOne({
|
9 |
_id: new ObjectId(),
|
10 |
title:
|
11 |
+
"Untitled " +
|
12 |
((await collections.conversations.countDocuments({ sessionId: input.locals.sessionId })) + 1),
|
13 |
messages: [],
|
14 |
createdAt: new Date(),
|
15 |
updatedAt: new Date(),
|
16 |
+
sessionId: input.locals.sessionId,
|
17 |
});
|
18 |
|
19 |
return new Response(
|
20 |
JSON.stringify({
|
21 |
+
conversationId: res.insertedId.toString(),
|
22 |
}),
|
23 |
+
{ headers: { "Content-Type": "application/json" } }
|
24 |
);
|
25 |
};
|
26 |
|
27 |
export const GET: RequestHandler = async () => {
|
28 |
+
throw redirect(301, base || "/");
|
29 |
};
|
src/routes/conversation/[id]/+page.server.ts
CHANGED
@@ -1,21 +1,21 @@
|
|
1 |
-
import type { PageServerLoad } from
|
2 |
-
import { collections } from
|
3 |
-
import type { Conversation } from
|
4 |
-
import { ObjectId } from
|
5 |
-
import { error } from
|
6 |
|
7 |
export const load: PageServerLoad = async (event) => {
|
8 |
// todo: add validation on params.id
|
9 |
const conversation = await collections.conversations.findOne({
|
10 |
_id: new ObjectId(event.params.id),
|
11 |
-
sessionId: event.locals.sessionId
|
12 |
});
|
13 |
|
14 |
if (!conversation) {
|
15 |
-
throw error(404,
|
16 |
}
|
17 |
|
18 |
return {
|
19 |
-
messages: conversation.messages
|
20 |
};
|
21 |
};
|
|
|
1 |
+
import type { PageServerLoad } from "./$types";
|
2 |
+
import { collections } from "$lib/server/database";
|
3 |
+
import type { Conversation } from "$lib/types/Conversation";
|
4 |
+
import { ObjectId } from "mongodb";
|
5 |
+
import { error } from "@sveltejs/kit";
|
6 |
|
7 |
export const load: PageServerLoad = async (event) => {
|
8 |
// todo: add validation on params.id
|
9 |
const conversation = await collections.conversations.findOne({
|
10 |
_id: new ObjectId(event.params.id),
|
11 |
+
sessionId: event.locals.sessionId,
|
12 |
});
|
13 |
|
14 |
if (!conversation) {
|
15 |
+
throw error(404, "Conversation not found");
|
16 |
}
|
17 |
|
18 |
return {
|
19 |
+
messages: conversation.messages,
|
20 |
};
|
21 |
};
|
src/routes/conversation/[id]/+page.svelte
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import ChatWindow from
|
3 |
-
import { pendingMessage } from
|
4 |
-
import { onMount } from
|
5 |
-
import type { PageData } from
|
6 |
-
import { page } from
|
7 |
-
import { HfInference } from
|
8 |
-
import { invalidate } from
|
9 |
|
10 |
export let data: PageData;
|
11 |
|
@@ -30,12 +30,12 @@
|
|
30 |
truncate: 1024,
|
31 |
watermark: false,
|
32 |
max_new_tokens: 1024,
|
33 |
-
stop: [
|
34 |
-
return_full_text: false
|
35 |
-
}
|
36 |
},
|
37 |
{
|
38 |
-
use_cache: false
|
39 |
}
|
40 |
);
|
41 |
|
@@ -47,9 +47,9 @@
|
|
47 |
if (!data.token.special) {
|
48 |
const lastMessage = messages.at(-1);
|
49 |
|
50 |
-
if (lastMessage?.from !==
|
51 |
// First token has a space at the beginning, trim it
|
52 |
-
messages = [...messages, { from:
|
53 |
} else {
|
54 |
lastMessage.content += data.token.text;
|
55 |
messages = [...messages];
|
@@ -65,7 +65,7 @@
|
|
65 |
loading = true;
|
66 |
pending = true;
|
67 |
|
68 |
-
messages = [...messages, { from:
|
69 |
|
70 |
await getTextGenerationStream(message);
|
71 |
|
@@ -79,7 +79,7 @@
|
|
79 |
onMount(async () => {
|
80 |
if ($pendingMessage) {
|
81 |
const val = $pendingMessage;
|
82 |
-
$pendingMessage =
|
83 |
|
84 |
if (messages.length === 0) {
|
85 |
writeMessage(val);
|
|
|
1 |
<script lang="ts">
|
2 |
+
import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
|
3 |
+
import { pendingMessage } from "$lib/stores/pendingMessage";
|
4 |
+
import { onMount } from "svelte";
|
5 |
+
import type { PageData } from "./$types";
|
6 |
+
import { page } from "$app/stores";
|
7 |
+
import { HfInference } from "@huggingface/inference";
|
8 |
+
import { invalidate } from "$app/navigation";
|
9 |
|
10 |
export let data: PageData;
|
11 |
|
|
|
30 |
truncate: 1024,
|
31 |
watermark: false,
|
32 |
max_new_tokens: 1024,
|
33 |
+
stop: ["<|endoftext|>"],
|
34 |
+
return_full_text: false,
|
35 |
+
},
|
36 |
},
|
37 |
{
|
38 |
+
use_cache: false,
|
39 |
}
|
40 |
);
|
41 |
|
|
|
47 |
if (!data.token.special) {
|
48 |
const lastMessage = messages.at(-1);
|
49 |
|
50 |
+
if (lastMessage?.from !== "assistant") {
|
51 |
// First token has a space at the beginning, trim it
|
52 |
+
messages = [...messages, { from: "assistant", content: data.token.text.trimStart() }];
|
53 |
} else {
|
54 |
lastMessage.content += data.token.text;
|
55 |
messages = [...messages];
|
|
|
65 |
loading = true;
|
66 |
pending = true;
|
67 |
|
68 |
+
messages = [...messages, { from: "user", content: message }];
|
69 |
|
70 |
await getTextGenerationStream(message);
|
71 |
|
|
|
79 |
onMount(async () => {
|
80 |
if ($pendingMessage) {
|
81 |
const val = $pendingMessage;
|
82 |
+
$pendingMessage = "";
|
83 |
|
84 |
if (messages.length === 0) {
|
85 |
writeMessage(val);
|
src/routes/conversation/[id]/+server.ts
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
-
import { HF_TOKEN } from
|
2 |
-
import { PUBLIC_MODEL_ENDPOINT } from
|
3 |
-
import { buildPrompt } from
|
4 |
-
import { collections } from
|
5 |
-
import type { Message } from
|
6 |
-
import { streamToAsyncIterable } from
|
7 |
-
import { sum } from
|
8 |
-
import { error } from
|
9 |
-
import { ObjectId } from
|
10 |
|
11 |
export async function POST({ request, fetch, locals, params }) {
|
12 |
// todo: add validation on params.id
|
@@ -14,29 +14,29 @@ export async function POST({ request, fetch, locals, params }) {
|
|
14 |
|
15 |
const conv = await collections.conversations.findOne({
|
16 |
_id: convId,
|
17 |
-
sessionId: locals.sessionId
|
18 |
});
|
19 |
|
20 |
if (!conv) {
|
21 |
-
throw error(404,
|
22 |
}
|
23 |
|
24 |
// Todo: validate prompt with zod? or aktype
|
25 |
const json = await request.json();
|
26 |
|
27 |
-
const messages = [...conv.messages, { from:
|
28 |
const prompt = buildPrompt(messages);
|
29 |
|
30 |
const resp = await fetch(PUBLIC_MODEL_ENDPOINT, {
|
31 |
headers: {
|
32 |
-
|
33 |
-
Authorization: `Basic ${HF_TOKEN}
|
34 |
},
|
35 |
-
method:
|
36 |
body: JSON.stringify({
|
37 |
...json,
|
38 |
-
inputs: prompt
|
39 |
-
})
|
40 |
});
|
41 |
|
42 |
const [stream1, stream2] = resp.body!.tee();
|
@@ -49,17 +49,17 @@ export async function POST({ request, fetch, locals, params }) {
|
|
49 |
generated_text = generated_text.slice(prompt.length);
|
50 |
}
|
51 |
|
52 |
-
messages.push({ from:
|
53 |
|
54 |
await collections.conversations.updateOne(
|
55 |
{
|
56 |
-
_id: convId
|
57 |
},
|
58 |
{
|
59 |
$set: {
|
60 |
messages,
|
61 |
-
updatedAt: new Date()
|
62 |
-
}
|
63 |
}
|
64 |
);
|
65 |
}
|
@@ -70,7 +70,7 @@ export async function POST({ request, fetch, locals, params }) {
|
|
70 |
return new Response(stream1, {
|
71 |
headers: Object.fromEntries(resp.headers.entries()),
|
72 |
status: resp.status,
|
73 |
-
statusText: resp.statusText
|
74 |
});
|
75 |
}
|
76 |
|
@@ -79,11 +79,11 @@ export async function DELETE({ locals, params }) {
|
|
79 |
|
80 |
const conv = await collections.conversations.findOne({
|
81 |
_id: convId,
|
82 |
-
sessionId: locals.sessionId
|
83 |
});
|
84 |
|
85 |
if (!conv) {
|
86 |
-
throw error(404,
|
87 |
}
|
88 |
|
89 |
if (conv.shares?.length) {
|
@@ -113,24 +113,24 @@ async function parseGeneratedText(stream: ReadableStream): Promise<string> {
|
|
113 |
// Get last line starting with "data:" and parse it as JSON to get the generated text
|
114 |
const message = new TextDecoder().decode(completeInput);
|
115 |
|
116 |
-
let lastIndex = message.lastIndexOf(
|
117 |
if (lastIndex === -1) {
|
118 |
-
lastIndex = message.indexOf(
|
119 |
}
|
120 |
|
121 |
if (lastIndex === -1) {
|
122 |
-
console.error(
|
123 |
}
|
124 |
|
125 |
-
let lastMessage = message.slice(lastIndex).trim().slice(
|
126 |
-
if (lastMessage.includes(
|
127 |
-
lastMessage = lastMessage.slice(0, lastMessage.indexOf(
|
128 |
}
|
129 |
|
130 |
const res = JSON.parse(lastMessage).generated_text;
|
131 |
|
132 |
-
if (typeof res !==
|
133 |
-
throw new Error(
|
134 |
}
|
135 |
|
136 |
return res;
|
|
|
1 |
+
import { HF_TOKEN } from "$env/static/private";
|
2 |
+
import { PUBLIC_MODEL_ENDPOINT } from "$env/static/public";
|
3 |
+
import { buildPrompt } from "$lib/buildPrompt.js";
|
4 |
+
import { collections } from "$lib/server/database.js";
|
5 |
+
import type { Message } from "$lib/types/Message.js";
|
6 |
+
import { streamToAsyncIterable } from "$lib/utils/streamToAsyncIterable";
|
7 |
+
import { sum } from "$lib/utils/sum";
|
8 |
+
import { error } from "@sveltejs/kit";
|
9 |
+
import { ObjectId } from "mongodb";
|
10 |
|
11 |
export async function POST({ request, fetch, locals, params }) {
|
12 |
// todo: add validation on params.id
|
|
|
14 |
|
15 |
const conv = await collections.conversations.findOne({
|
16 |
_id: convId,
|
17 |
+
sessionId: locals.sessionId,
|
18 |
});
|
19 |
|
20 |
if (!conv) {
|
21 |
+
throw error(404, "Conversation not found");
|
22 |
}
|
23 |
|
24 |
// Todo: validate prompt with zod? or aktype
|
25 |
const json = await request.json();
|
26 |
|
27 |
+
const messages = [...conv.messages, { from: "user", content: json.inputs }] satisfies Message[];
|
28 |
const prompt = buildPrompt(messages);
|
29 |
|
30 |
const resp = await fetch(PUBLIC_MODEL_ENDPOINT, {
|
31 |
headers: {
|
32 |
+
"Content-Type": request.headers.get("Content-Type") ?? "application/json",
|
33 |
+
Authorization: `Basic ${HF_TOKEN}`,
|
34 |
},
|
35 |
+
method: "POST",
|
36 |
body: JSON.stringify({
|
37 |
...json,
|
38 |
+
inputs: prompt,
|
39 |
+
}),
|
40 |
});
|
41 |
|
42 |
const [stream1, stream2] = resp.body!.tee();
|
|
|
49 |
generated_text = generated_text.slice(prompt.length);
|
50 |
}
|
51 |
|
52 |
+
messages.push({ from: "assistant", content: generated_text });
|
53 |
|
54 |
await collections.conversations.updateOne(
|
55 |
{
|
56 |
+
_id: convId,
|
57 |
},
|
58 |
{
|
59 |
$set: {
|
60 |
messages,
|
61 |
+
updatedAt: new Date(),
|
62 |
+
},
|
63 |
}
|
64 |
);
|
65 |
}
|
|
|
70 |
return new Response(stream1, {
|
71 |
headers: Object.fromEntries(resp.headers.entries()),
|
72 |
status: resp.status,
|
73 |
+
statusText: resp.statusText,
|
74 |
});
|
75 |
}
|
76 |
|
|
|
79 |
|
80 |
const conv = await collections.conversations.findOne({
|
81 |
_id: convId,
|
82 |
+
sessionId: locals.sessionId,
|
83 |
});
|
84 |
|
85 |
if (!conv) {
|
86 |
+
throw error(404, "Conversation not found");
|
87 |
}
|
88 |
|
89 |
if (conv.shares?.length) {
|
|
|
113 |
// Get last line starting with "data:" and parse it as JSON to get the generated text
|
114 |
const message = new TextDecoder().decode(completeInput);
|
115 |
|
116 |
+
let lastIndex = message.lastIndexOf("\ndata:");
|
117 |
if (lastIndex === -1) {
|
118 |
+
lastIndex = message.indexOf("data");
|
119 |
}
|
120 |
|
121 |
if (lastIndex === -1) {
|
122 |
+
console.error("Could not parse in last message");
|
123 |
}
|
124 |
|
125 |
+
let lastMessage = message.slice(lastIndex).trim().slice("data:".length);
|
126 |
+
if (lastMessage.includes("\n")) {
|
127 |
+
lastMessage = lastMessage.slice(0, lastMessage.indexOf("\n"));
|
128 |
}
|
129 |
|
130 |
const res = JSON.parse(lastMessage).generated_text;
|
131 |
|
132 |
+
if (typeof res !== "string") {
|
133 |
+
throw new Error("Could not parse generated text");
|
134 |
}
|
135 |
|
136 |
return res;
|
src/routes/conversation/[id]/share/+server.ts
CHANGED
@@ -1,20 +1,20 @@
|
|
1 |
-
import { base } from
|
2 |
-
import { SHARE_BASE_URL } from
|
3 |
-
import { collections } from
|
4 |
-
import type { SharedConversation } from
|
5 |
-
import { sha256 } from
|
6 |
-
import { error } from
|
7 |
-
import { ObjectId } from
|
8 |
-
import { nanoid } from
|
9 |
|
10 |
export async function POST({ params, url, locals }) {
|
11 |
const conversation = await collections.conversations.findOne({
|
12 |
_id: new ObjectId(params.id),
|
13 |
-
sessionId: locals.sessionId
|
14 |
});
|
15 |
|
16 |
if (!conversation) {
|
17 |
-
throw error(404,
|
18 |
}
|
19 |
|
20 |
const hash = await sha256(JSON.stringify(conversation.messages));
|
@@ -24,9 +24,9 @@ export async function POST({ params, url, locals }) {
|
|
24 |
if (existingShare) {
|
25 |
return new Response(
|
26 |
JSON.stringify({
|
27 |
-
url: url.origin + `${base}/r/${existingShare._id}
|
28 |
}),
|
29 |
-
{ headers: {
|
30 |
);
|
31 |
}
|
32 |
|
@@ -36,15 +36,15 @@ export async function POST({ params, url, locals }) {
|
|
36 |
messages: conversation.messages,
|
37 |
hash,
|
38 |
updatedAt: new Date(),
|
39 |
-
title: conversation.title
|
40 |
};
|
41 |
|
42 |
await collections.sharedConversations.insertOne(shared);
|
43 |
|
44 |
return new Response(
|
45 |
JSON.stringify({
|
46 |
-
url: (SHARE_BASE_URL || `${url.origin}${base}`) + `/r/${shared._id}
|
47 |
}),
|
48 |
-
{ headers: {
|
49 |
);
|
50 |
}
|
|
|
1 |
+
import { base } from "$app/paths";
|
2 |
+
import { SHARE_BASE_URL } from "$env/static/private";
|
3 |
+
import { collections } from "$lib/server/database.js";
|
4 |
+
import type { SharedConversation } from "$lib/types/SharedConversation.js";
|
5 |
+
import { sha256 } from "$lib/utils/sha256.js";
|
6 |
+
import { error } from "@sveltejs/kit";
|
7 |
+
import { ObjectId } from "mongodb";
|
8 |
+
import { nanoid } from "nanoid";
|
9 |
|
10 |
export async function POST({ params, url, locals }) {
|
11 |
const conversation = await collections.conversations.findOne({
|
12 |
_id: new ObjectId(params.id),
|
13 |
+
sessionId: locals.sessionId,
|
14 |
});
|
15 |
|
16 |
if (!conversation) {
|
17 |
+
throw error(404, "Conversation not found");
|
18 |
}
|
19 |
|
20 |
const hash = await sha256(JSON.stringify(conversation.messages));
|
|
|
24 |
if (existingShare) {
|
25 |
return new Response(
|
26 |
JSON.stringify({
|
27 |
+
url: url.origin + `${base}/r/${existingShare._id}`,
|
28 |
}),
|
29 |
+
{ headers: { "Content-Type": "application/json" } }
|
30 |
);
|
31 |
}
|
32 |
|
|
|
36 |
messages: conversation.messages,
|
37 |
hash,
|
38 |
updatedAt: new Date(),
|
39 |
+
title: conversation.title,
|
40 |
};
|
41 |
|
42 |
await collections.sharedConversations.insertOne(shared);
|
43 |
|
44 |
return new Response(
|
45 |
JSON.stringify({
|
46 |
+
url: (SHARE_BASE_URL || `${url.origin}${base}`) + `/r/${shared._id}`,
|
47 |
}),
|
48 |
+
{ headers: { "Content-Type": "application/json" } }
|
49 |
);
|
50 |
}
|
src/routes/r/[id]/+page.server.ts
CHANGED
@@ -1,17 +1,17 @@
|
|
1 |
-
import type { PageServerLoad } from
|
2 |
-
import { collections } from
|
3 |
-
import { error } from
|
4 |
|
5 |
export const load: PageServerLoad = async ({ params }) => {
|
6 |
const conversation = await collections.sharedConversations.findOne({
|
7 |
-
_id: params.id
|
8 |
});
|
9 |
|
10 |
if (!conversation) {
|
11 |
-
throw error(404,
|
12 |
}
|
13 |
|
14 |
return {
|
15 |
-
messages: conversation.messages
|
16 |
};
|
17 |
};
|
|
|
1 |
+
import type { PageServerLoad } from "./$types";
|
2 |
+
import { collections } from "$lib/server/database";
|
3 |
+
import { error } from "@sveltejs/kit";
|
4 |
|
5 |
export const load: PageServerLoad = async ({ params }) => {
|
6 |
const conversation = await collections.sharedConversations.findOne({
|
7 |
+
_id: params.id,
|
8 |
});
|
9 |
|
10 |
if (!conversation) {
|
11 |
+
throw error(404, "Conversation not found");
|
12 |
}
|
13 |
|
14 |
return {
|
15 |
+
messages: conversation.messages,
|
16 |
};
|
17 |
};
|
src/routes/r/[id]/+page.svelte
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import ChatWindow from
|
3 |
-
import type { PageData } from
|
4 |
|
5 |
export let data: PageData;
|
6 |
</script>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
|
3 |
+
import type { PageData } from "./$types";
|
4 |
|
5 |
export let data: PageData;
|
6 |
</script>
|
src/styles/highlight-js.css
CHANGED
@@ -1 +1 @@
|
|
1 |
-
@import
|
|
|
1 |
+
@import "highlight.js/styles/atom-one-dark";
|
src/styles/main.css
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
@import
|
2 |
|
3 |
@tailwind base;
|
4 |
@tailwind components;
|
|
|
1 |
+
@import "./highlight-js.css";
|
2 |
|
3 |
@tailwind base;
|
4 |
@tailwind components;
|
svelte.config.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1 |
-
import adapter from
|
2 |
-
import { vitePreprocess } from
|
3 |
-
import dotenv from
|
4 |
|
5 |
-
dotenv.config({ path:
|
6 |
-
dotenv.config({ path:
|
7 |
|
8 |
/** @type {import('@sveltejs/kit').Config} */
|
9 |
const config = {
|
@@ -15,9 +15,9 @@ const config = {
|
|
15 |
adapter: adapter(),
|
16 |
|
17 |
paths: {
|
18 |
-
base: process.env.APP_BASE ||
|
19 |
-
}
|
20 |
-
}
|
21 |
};
|
22 |
|
23 |
export default config;
|
|
|
1 |
+
import adapter from "@sveltejs/adapter-node";
|
2 |
+
import { vitePreprocess } from "@sveltejs/kit/vite";
|
3 |
+
import dotenv from "dotenv";
|
4 |
|
5 |
+
dotenv.config({ path: "./.env.local" });
|
6 |
+
dotenv.config({ path: "./.env" });
|
7 |
|
8 |
/** @type {import('@sveltejs/kit').Config} */
|
9 |
const config = {
|
|
|
15 |
adapter: adapter(),
|
16 |
|
17 |
paths: {
|
18 |
+
base: process.env.APP_BASE || "",
|
19 |
+
},
|
20 |
+
},
|
21 |
};
|
22 |
|
23 |
export default config;
|
vite.config.ts
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
-
import { sveltekit } from
|
2 |
-
import { defineConfig } from
|
3 |
-
import Icons from
|
4 |
|
5 |
export default defineConfig({
|
6 |
plugins: [
|
7 |
sveltekit(),
|
8 |
Icons({
|
9 |
-
compiler:
|
10 |
-
})
|
11 |
-
]
|
12 |
});
|
|
|
1 |
+
import { sveltekit } from "@sveltejs/kit/vite";
|
2 |
+
import { defineConfig } from "vite";
|
3 |
+
import Icons from "unplugin-icons/vite";
|
4 |
|
5 |
export default defineConfig({
|
6 |
plugins: [
|
7 |
sveltekit(),
|
8 |
Icons({
|
9 |
+
compiler: "svelte",
|
10 |
+
}),
|
11 |
+
],
|
12 |
});
|