storytellurx-v1/app/pages/start.tsx
Elias Rhouzlane 605add02f4 fix: start
2026-02-10 11:00:39 +01:00

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>
);
};