129 lines
4.8 KiB
TypeScript
129 lines
4.8 KiB
TypeScript
import { context } from "@bureaudouble/rsc/server/context";
|
|
import urlcat from "@bureaudouble/outils/urlcat.ts";
|
|
import { AI_PERSONAS } from "@/app/game-engine/personas.ts";
|
|
import { AVAILABLE_MODELS } from "@/app/game-engine/models-server.ts";
|
|
import * as motion from "@/app/utils/motion.ts";
|
|
import Gravity, { MatterBody } from "@/app/components/utils/gravity.tsx";
|
|
import Layout from "./layout.tsx";
|
|
import cn from "@bureaudouble/outils/cn.ts";
|
|
import { useServerI18n } from "../actions/locale.ts";
|
|
|
|
export default async () => {
|
|
const { t } = await useServerI18n();
|
|
|
|
const personas = AI_PERSONAS.filter((p) => p.type === "persona");
|
|
const models = AVAILABLE_MODELS
|
|
.filter(({ startPage }) => !!startPage)
|
|
.map(({ id, name, type, startPage }) => ({
|
|
id,
|
|
name,
|
|
type,
|
|
label: t(startPage!.label as any),
|
|
}));
|
|
|
|
const ctx = context();
|
|
const personaId = new URL(ctx.request.url).searchParams.get("persona-id");
|
|
|
|
const fadeInVariants = {
|
|
hidden: { opacity: 0 },
|
|
visible: {
|
|
opacity: 1,
|
|
transition: { duration: 0.5, staggerChildren: 0.1 },
|
|
},
|
|
};
|
|
const wordVariants = {
|
|
hidden: { opacity: 0 },
|
|
visible: { opacity: 1 },
|
|
};
|
|
|
|
return (
|
|
<Layout variant="classical">
|
|
{personaId
|
|
? (
|
|
<div className="p-4 flex flex-col h-full gap-y-4 after:absolute after:inset-0 after:-translate-x-px after:mix-blend-color-dodge after:pointer-events-none after:[background:var(--bg-grid)] h-full">
|
|
<a href="/start" className="hover:underline text-sm">
|
|
{t("backToPersonas")}
|
|
</a>
|
|
<div className="leading-5">
|
|
{t("modelChoiceHeader")}
|
|
</div>
|
|
<div className="flex-grow overflow-y-auto flex flex-col gap-4">
|
|
{models.map((model) => (
|
|
<a
|
|
href={urlcat("/chat", {
|
|
"persona-id": personaId,
|
|
"model-id": model.id,
|
|
})}
|
|
key={model.id}
|
|
className="block py-1.5 px-2 border border-foreground border-dashed hover:bg-accent transition-colors leading-5"
|
|
>
|
|
<div>{model.label}</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
{model.name} |{" "}
|
|
{model.type === "client" ? t("onDevice") : t("server")}
|
|
</p>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
: (
|
|
<div className="p-4 flex flex-col gap-4 after:absolute after:inset-0 after:-translate-x-px after:[background:var(--bg-grid)] after:pointer-events-none after:mix-blend-color-dodge h-full">
|
|
<motion.h2
|
|
className="leading-5"
|
|
initial="hidden"
|
|
animate="visible"
|
|
variants={fadeInVariants}
|
|
>
|
|
{t("startPageHeader")
|
|
.split(" ")
|
|
.map((word, index) => (
|
|
<motion.span
|
|
key={index}
|
|
variants={wordVariants}
|
|
className="inline-block mr-1"
|
|
>
|
|
{word}
|
|
</motion.span>
|
|
))}
|
|
</motion.h2>
|
|
<Gravity
|
|
gravity={{ x: 0, y: 1 }}
|
|
className="w-full h-full overflow-hidden mix-blend-hard-light saturate-[1.2]"
|
|
>
|
|
{personas.map((persona, i, arr) => (
|
|
<MatterBody
|
|
isDraggable={false}
|
|
bodyType="circle"
|
|
matterBodyOptions={{ friction: 0.5, restitution: 0.2 }}
|
|
x={`${Math.min(100, (i % 3) * 33 + Math.random() * 10)}%`}
|
|
y={`${20 + 80 * i / arr.length}%`}
|
|
>
|
|
<a
|
|
href={urlcat("/start", { "persona-id": persona.id })}
|
|
key={persona.id}
|
|
className="hover:blur-xs flex items-center justify-center opacity-100 transition-opacity duration-2300 starting:opacity-0 relative"
|
|
>
|
|
<div className="absolute flex items-center justify-center inset-0 mix-blend-overlay text-xs translate-y-5">
|
|
<span>{persona.name}</span>
|
|
</div>
|
|
<img
|
|
src={urlcat("/public/assets/images/:filename", {
|
|
filename: `${persona.id}.png`,
|
|
})}
|
|
className={cn(
|
|
"size-36 overflow-visible",
|
|
persona.id === "polaire"
|
|
? "object-contain"
|
|
: "object-cover",
|
|
)}
|
|
/>
|
|
</a>
|
|
</MatterBody>
|
|
))}
|
|
</Gravity>
|
|
</div>
|
|
)}
|
|
</Layout>
|
|
);
|
|
};
|