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