storytellurx-v1/app/components/chat/status-bar.tsx
Elias Rhouzlane cfe3526f6b fix: localize
2025-07-14 14:08:57 +02:00

123 lines
4.4 KiB
TypeScript

"use client";
import { useMemo } from "react";
import { useNarrative } from "@/app/game-engine/narrative-provider.tsx";
import ChevronDownIcon from "@bureaudouble/icons/radix/ChevronDownIcon";
import { SearchParamController } from "@bureaudouble/react-search-parameter-controller";
import { cva } from "class-variance-authority";
import BreathingText from "../ui/breathing-text.tsx";
import { personaFontVariationsVariants } from "../../game-engine/persona-styles.ts";
import { useClientLocale } from "../../game-engine/locale-provider.tsx";
const statusBarVariants = cva(
"z-10 flex-none text-xs",
{
variants: {
variant: {
terminal:
"p-1 flex flex-wrap gap-x-3 items-center uppercase border-b border-border",
classical:
"px-2 pt-2 grid grid-cols-2 auto-rows-min gap-x-1 gap-y-0.5 transition-all duration-500 group-data-[scroll-direction=up]/html:opacity-100 group-data-[scroll-direction=up]/html:h-[7rem] group-data-[scroll-direction=down]/html:opacity-0 group-data-[scroll-direction=down]/html:h-0 *:even:text-right *:even:ml-auto **:data-value:uppercase **:data-short-title:line-clamp-2 **:data-short-title:text-xl **:data-short-title:text-center **:data-short-title:pt-2 **:data-short-title:px-4 **:data-short-title:font-light **:data-short-title:col-span-full",
},
},
},
);
export function StatusBar(
{ models, modelId }: {
models: { id: string; name: string }[];
modelId?: string | null | undefined;
},
) {
const { t } = useClientLocale();
const { gameState, uiVariant, id } = useNarrative();
const statusItems = useMemo(() =>
[
{
labelKey: "statusBarStep",
value: gameState.progressionStep,
variant: ["terminal"],
},
{
labelKey: uiVariant === "terminal"
? "statusBarCtxTerminal"
: "statusBarCtxClassical",
value: gameState.personaId || "N/A",
},
{
labelKey: "statusBarModality",
value: gameState.modality?.toUpperCase() || "N/A",
variant: ["terminal"],
},
{
labelKey: uiVariant === "terminal"
? "statusBarLustLevelTerminal"
: "statusBarLustLevelClassical",
value: gameState.lustExperienceLevel?.toUpperCase() ?? "beginner",
},
{
labelKey: "statusBarClimaxPeak",
value: gameState.arousalPeakPotential?.toFixed(2),
variant: ["terminal"],
},
{
labelKey: "statusBarConsent",
value: gameState.aiConsentLevel?.toFixed(2),
variant: ["terminal"],
},
{ labelKey: "statusBarSid", value: `${id.substring(0, 8)}...` },
]
.filter((v) => !v.variant || v.variant.includes(uiVariant)), [
gameState,
uiVariant,
id,
]);
const translatedTitle = gameState.shortTitleSummary === "defaultShortTitle"
? t("defaultShortTitle")
: gameState.shortTitleSummary;
return (
<div className={statusBarVariants({ variant: uiVariant })}>
{statusItems.map((item) => (
<span key={item.labelKey}>
<span data-label>{t(item.labelKey as any)}</span>
{uiVariant === "classical" ? <span className="mx-1" /> : "::"}
<span data-value>{item.value}</span>
</span>
))}
<label className="text-inherit flex flex-wrap items-center group">
<span data-label>
{t("statusBarModel")}
</span>
{uiVariant === "classical" ? <span className="mx-1" /> : "::"}
<SearchParamController
paramName="model-id"
currentValue={modelId}
key={modelId}
>
<select
data-value
className="max-w-30 truncate text-inherit group-hover:text-primary text-xs rounded-none focus:border-transparent focus:ring-0 focus:outline-none appearance-none cursor-pointer"
>
{models.map((model) => (
<option key={model.id} value={model.id}>
{model.name.toUpperCase()}
</option>
))}
</select>
</SearchParamController>
<ChevronDownIcon className="size-3" />
</label>
<BreathingText
staggerDuration={0.08}
fromFontVariationSettings={personaFontVariationsVariants(gameState)
.from}
toFontVariationSettings={personaFontVariationsVariants(gameState).to}
data-short-title
>
{translatedTitle}
</BreathingText>
</div>
);
}