Spaces:
Build error
Build error
Direct model page (#858)
Browse files* Direct model page
* Remove auto redirect
* fix thumbnail
* css warning
* change url for models
* instantSet from settings
* Get rid of redirect
* thumbnail
* also link from /models
* use public app color
---------
Co-authored-by: Victor Mustar <[email protected]>
- src/routes/models/+page.svelte +1 -1
- src/routes/models/[...model]/+page.server.ts +39 -0
- src/routes/models/[...model]/+page.svelte +100 -0
- src/routes/models/[...model]/thumbnail.png/+server.ts +57 -0
- src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte +40 -0
- src/routes/settings/(nav)/[...model]/+page.svelte +1 -1
src/routes/models/+page.svelte
CHANGED
@@ -40,7 +40,7 @@
|
|
40 |
<dl class="mt-8 grid grid-cols-1 gap-3 sm:gap-5 xl:grid-cols-2">
|
41 |
{#each data.models.filter((el) => !el.unlisted) as model, index (model.id)}
|
42 |
<a
|
43 |
-
href="{base}
|
44 |
class="relative flex flex-col gap-2 overflow-hidden rounded-xl border bg-gray-50/50 px-6 py-5 shadow hover:bg-gray-50 hover:shadow-inner dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40"
|
45 |
>
|
46 |
<div class="flex items-center justify-between">
|
|
|
40 |
<dl class="mt-8 grid grid-cols-1 gap-3 sm:gap-5 xl:grid-cols-2">
|
41 |
{#each data.models.filter((el) => !el.unlisted) as model, index (model.id)}
|
42 |
<a
|
43 |
+
href="{base}/models/{model.id}"
|
44 |
class="relative flex flex-col gap-2 overflow-hidden rounded-xl border bg-gray-50/50 px-6 py-5 shadow hover:bg-gray-50 hover:shadow-inner dark:border-gray-800/70 dark:bg-gray-950/20 dark:hover:bg-gray-950/40"
|
45 |
>
|
46 |
<div class="flex items-center justify-between">
|
src/routes/models/[...model]/+page.server.ts
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { base } from "$app/paths";
|
2 |
+
import { authCondition } from "$lib/server/auth.js";
|
3 |
+
import { collections } from "$lib/server/database.js";
|
4 |
+
import { models } from "$lib/server/models";
|
5 |
+
import { redirect } from "@sveltejs/kit";
|
6 |
+
|
7 |
+
export async function load({ params, locals, parent }) {
|
8 |
+
const model = models.find(({ id }) => id === params.model);
|
9 |
+
const data = await parent();
|
10 |
+
|
11 |
+
if (!model || model.unlisted) {
|
12 |
+
throw redirect(302, `${base}/`);
|
13 |
+
}
|
14 |
+
|
15 |
+
if (locals.user?._id ?? locals.sessionId) {
|
16 |
+
await collections.settings.updateOne(
|
17 |
+
authCondition(locals),
|
18 |
+
{
|
19 |
+
$set: {
|
20 |
+
activeModel: model.id,
|
21 |
+
updatedAt: new Date(),
|
22 |
+
},
|
23 |
+
$setOnInsert: {
|
24 |
+
createdAt: new Date(),
|
25 |
+
},
|
26 |
+
},
|
27 |
+
{
|
28 |
+
upsert: true,
|
29 |
+
}
|
30 |
+
);
|
31 |
+
}
|
32 |
+
|
33 |
+
return {
|
34 |
+
settings: {
|
35 |
+
...data.settings,
|
36 |
+
activeModel: model.id,
|
37 |
+
},
|
38 |
+
};
|
39 |
+
}
|
src/routes/models/[...model]/+page.svelte
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { page } from "$app/stores";
|
3 |
+
import { base } from "$app/paths";
|
4 |
+
import { goto } from "$app/navigation";
|
5 |
+
import { onMount } from "svelte";
|
6 |
+
import { PUBLIC_APP_NAME, PUBLIC_ORIGIN } from "$env/static/public";
|
7 |
+
import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
|
8 |
+
import { findCurrentModel } from "$lib/utils/models";
|
9 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
10 |
+
import { ERROR_MESSAGES, error } from "$lib/stores/errors";
|
11 |
+
import { pendingMessage } from "$lib/stores/pendingMessage";
|
12 |
+
|
13 |
+
export let data;
|
14 |
+
|
15 |
+
let loading = false;
|
16 |
+
let files: File[] = [];
|
17 |
+
|
18 |
+
const settings = useSettingsStore();
|
19 |
+
const modelId = $page.params.model;
|
20 |
+
|
21 |
+
async function createConversation(message: string) {
|
22 |
+
try {
|
23 |
+
loading = true;
|
24 |
+
// check if $settings.activeModel is a valid model
|
25 |
+
// else check if it's an assistant, and use that model
|
26 |
+
// else use the first model
|
27 |
+
|
28 |
+
const validModels = data.models.map((model) => model.id);
|
29 |
+
|
30 |
+
let model;
|
31 |
+
if (validModels.includes($settings.activeModel)) {
|
32 |
+
model = $settings.activeModel;
|
33 |
+
} else {
|
34 |
+
if (validModels.includes(data.assistant?.modelId)) {
|
35 |
+
model = data.assistant?.modelId;
|
36 |
+
} else {
|
37 |
+
model = data.models[0].id;
|
38 |
+
}
|
39 |
+
}
|
40 |
+
const res = await fetch(`${base}/conversation`, {
|
41 |
+
method: "POST",
|
42 |
+
headers: {
|
43 |
+
"Content-Type": "application/json",
|
44 |
+
},
|
45 |
+
body: JSON.stringify({
|
46 |
+
model,
|
47 |
+
preprompt: $settings.customPrompts[$settings.activeModel],
|
48 |
+
}),
|
49 |
+
});
|
50 |
+
|
51 |
+
if (!res.ok) {
|
52 |
+
error.set("Error while creating conversation, try again.");
|
53 |
+
console.error("Error while creating conversation: " + (await res.text()));
|
54 |
+
return;
|
55 |
+
}
|
56 |
+
|
57 |
+
const { conversationId } = await res.json();
|
58 |
+
|
59 |
+
// Ugly hack to use a store as temp storage, feel free to improve ^^
|
60 |
+
pendingMessage.set({
|
61 |
+
content: message,
|
62 |
+
files,
|
63 |
+
});
|
64 |
+
|
65 |
+
// invalidateAll to update list of conversations
|
66 |
+
await goto(`${base}/conversation/${conversationId}`, { invalidateAll: true });
|
67 |
+
} catch (err) {
|
68 |
+
error.set(ERROR_MESSAGES.default);
|
69 |
+
console.error(err);
|
70 |
+
} finally {
|
71 |
+
loading = false;
|
72 |
+
}
|
73 |
+
}
|
74 |
+
|
75 |
+
onMount(async () => {
|
76 |
+
settings.instantSet({
|
77 |
+
activeModel: modelId,
|
78 |
+
});
|
79 |
+
});
|
80 |
+
</script>
|
81 |
+
|
82 |
+
<svelte:head>
|
83 |
+
<meta property="og:title" content={modelId + " - " + PUBLIC_APP_NAME} />
|
84 |
+
<meta property="og:type" content="link" />
|
85 |
+
<meta property="og:description" content={`Use ${modelId} inside of ${PUBLIC_APP_NAME}`} />
|
86 |
+
<meta
|
87 |
+
property="og:image"
|
88 |
+
content="{PUBLIC_ORIGIN || $page.url.origin}{base}/models/{modelId}/thumbnail.png"
|
89 |
+
/>
|
90 |
+
<meta property="og:url" content={$page.url.href} />
|
91 |
+
<meta name="twitter:card" content="summary_large_image" />
|
92 |
+
</svelte:head>
|
93 |
+
|
94 |
+
<ChatWindow
|
95 |
+
on:message={(ev) => createConversation(ev.detail)}
|
96 |
+
{loading}
|
97 |
+
currentModel={findCurrentModel([...data.models, ...data.oldModels], modelId)}
|
98 |
+
models={data.models}
|
99 |
+
bind:files
|
100 |
+
/>
|
src/routes/models/[...model]/thumbnail.png/+server.ts
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ModelThumbnail from "./ModelThumbnail.svelte";
|
2 |
+
import { redirect, type RequestHandler } from "@sveltejs/kit";
|
3 |
+
import type { SvelteComponent } from "svelte";
|
4 |
+
|
5 |
+
import { Resvg } from "@resvg/resvg-js";
|
6 |
+
import satori from "satori";
|
7 |
+
import { html } from "satori-html";
|
8 |
+
|
9 |
+
import InterRegular from "../../../../../static/fonts/Inter-Regular.ttf";
|
10 |
+
import InterBold from "../../../../../static/fonts/Inter-Bold.ttf";
|
11 |
+
import { base } from "$app/paths";
|
12 |
+
import { models } from "$lib/server/models";
|
13 |
+
|
14 |
+
export const GET: RequestHandler = (async ({ params }) => {
|
15 |
+
const model = models.find(({ id }) => id === params.model);
|
16 |
+
|
17 |
+
if (!model || model.unlisted) {
|
18 |
+
throw redirect(302, `${base}/`);
|
19 |
+
}
|
20 |
+
const renderedComponent = (ModelThumbnail as unknown as SvelteComponent).render({
|
21 |
+
name: model.name,
|
22 |
+
logoUrl: model.logoUrl,
|
23 |
+
});
|
24 |
+
|
25 |
+
const reactLike = html(
|
26 |
+
"<style>" + renderedComponent.css.code + "</style>" + renderedComponent.html
|
27 |
+
);
|
28 |
+
|
29 |
+
const svg = await satori(reactLike, {
|
30 |
+
width: 1200,
|
31 |
+
height: 648,
|
32 |
+
fonts: [
|
33 |
+
{
|
34 |
+
name: "Inter",
|
35 |
+
data: InterRegular as unknown as ArrayBuffer,
|
36 |
+
weight: 500,
|
37 |
+
},
|
38 |
+
{
|
39 |
+
name: "Inter",
|
40 |
+
data: InterBold as unknown as ArrayBuffer,
|
41 |
+
weight: 700,
|
42 |
+
},
|
43 |
+
],
|
44 |
+
});
|
45 |
+
|
46 |
+
const png = new Resvg(svg, {
|
47 |
+
fitTo: { mode: "original" },
|
48 |
+
})
|
49 |
+
.render()
|
50 |
+
.asPng();
|
51 |
+
|
52 |
+
return new Response(png, {
|
53 |
+
headers: {
|
54 |
+
"Content-Type": "image/png",
|
55 |
+
},
|
56 |
+
});
|
57 |
+
}) satisfies RequestHandler;
|
src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { PUBLIC_APP_COLOR } from "$env/static/public";
|
3 |
+
import { isHuggingChat } from "$lib/utils/isHuggingChat";
|
4 |
+
|
5 |
+
export let name: string;
|
6 |
+
export let logoUrl: string | undefined;
|
7 |
+
|
8 |
+
import logo from "../../../../../static/huggingchat/logo.svg?raw";
|
9 |
+
</script>
|
10 |
+
|
11 |
+
<div class=" flex h-[648px] w-full flex-col items-center bg-white">
|
12 |
+
<div class="flex flex-1 flex-col items-center justify-center gap-2">
|
13 |
+
{#if logoUrl}
|
14 |
+
<img class="h-48 w-48" src={logoUrl} alt="avatar" />
|
15 |
+
{/if}
|
16 |
+
<h1 class="m-0 text-5xl font-bold text-black">
|
17 |
+
{name}
|
18 |
+
</h1>
|
19 |
+
</div>
|
20 |
+
|
21 |
+
<div
|
22 |
+
class="flex h-[200px] w-full flex-col items-center justify-center rounded-b-none bg-{PUBLIC_APP_COLOR}-500/10 pb-10 pt-10 text-4xl text-gray-500"
|
23 |
+
style="border-radius: 100% 100% 0 0;"
|
24 |
+
>
|
25 |
+
Try it now
|
26 |
+
{#if isHuggingChat}
|
27 |
+
on
|
28 |
+
{/if}
|
29 |
+
|
30 |
+
{#if isHuggingChat}
|
31 |
+
<div class="flex flex-row pt-3 text-5xl font-bold text-black">
|
32 |
+
<div class="mr-5 flex items-center justify-center" id="logo">
|
33 |
+
<!-- eslint-disable-next-line -->
|
34 |
+
{@html logo}
|
35 |
+
</div>
|
36 |
+
<span>HuggingChat</span>
|
37 |
+
</div>
|
38 |
+
{/if}
|
39 |
+
</div>
|
40 |
+
</div>
|
src/routes/settings/(nav)/[...model]/+page.svelte
CHANGED
@@ -77,7 +77,7 @@
|
|
77 |
</a>
|
78 |
{/if}
|
79 |
<CopyToClipBoardBtn
|
80 |
-
value="{PUBLIC_ORIGIN || $page.url.origin}{base}
|
81 |
classNames="!border-none !shadow-none !py-0 !px-1 !rounded-md"
|
82 |
>
|
83 |
<div class="flex items-center gap-1.5 hover:underline">
|
|
|
77 |
</a>
|
78 |
{/if}
|
79 |
<CopyToClipBoardBtn
|
80 |
+
value="{PUBLIC_ORIGIN || $page.url.origin}{base}/models/{model.id}"
|
81 |
classNames="!border-none !shadow-none !py-0 !px-1 !rounded-md"
|
82 |
>
|
83 |
<div class="flex items-center gap-1.5 hover:underline">
|