This commit is contained in:
2026-04-19 20:33:18 +08:00
parent 692643136f
commit 67c584b4df
123 changed files with 11898 additions and 4082 deletions

View File

@@ -1,7 +1,3 @@
import type {
EightAnchorContent,
KeyRelationshipValue,
} from '../../packages/shared/src/contracts/customWorldAgent';
import {
type ReactNode,
useDeferredValue,
@@ -11,6 +7,10 @@ import {
useState,
} from 'react';
import type {
EightAnchorContent,
KeyRelationshipValue,
} from '../../packages/shared/src/contracts/customWorldAgent';
import { ROLE_TEMPLATE_CHARACTERS } from '../data/characterPresets';
import { getCustomWorldSceneRelativePositionLabel } from '../data/customWorldSceneGraph';
import {
@@ -20,7 +20,6 @@ import {
import { resolveCustomWorldCampScene } from '../services/customWorldCamp';
import { normalizeCustomWorldCreatorIntent } from '../services/customWorldCreatorIntent';
import { AnimationState, Character, CustomWorldProfile } from '../types';
import { getNineSliceStyle, UI_CHROME } from '../uiAssets';
import { CharacterAnimator } from './CharacterAnimator';
import type { CustomWorldEditorTarget } from './CustomWorldEntityEditorModal';
import { CustomWorldNpcPortrait } from './CustomWorldNpcVisualEditor';
@@ -75,10 +74,7 @@ function Section({
children: ReactNode;
}) {
return (
<div
className="pixel-nine-slice pixel-panel"
style={getNineSliceStyle(UI_CHROME.panel, { paddingX: 14, paddingY: 12 })}
>
<div className="platform-surface platform-surface--soft px-3.5 py-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="text-xs font-bold tracking-[0.16em] text-white">
@@ -113,17 +109,17 @@ function SmallButton({
}) {
const toneClassName =
tone === 'sky'
? 'border-sky-300/20 bg-sky-500/10 text-sky-100 hover:text-white'
? 'platform-button platform-button--primary'
: tone === 'rose'
? 'border-rose-300/20 bg-rose-500/10 text-rose-100 hover:text-white'
: 'border-white/10 bg-black/20 text-zinc-300 hover:text-white';
? 'platform-button platform-button--danger'
: 'platform-button platform-button--ghost';
return (
<button
type="button"
onClick={onClick}
disabled={disabled}
className={`rounded-full border px-3 py-1 text-[11px] transition-colors ${toneClassName} ${disabled ? 'cursor-not-allowed opacity-45' : ''}`}
className={`${toneClassName} min-h-0 rounded-full px-3 py-1 text-[11px] ${disabled ? 'cursor-not-allowed opacity-45' : ''}`}
>
{children}
</button>
@@ -140,12 +136,12 @@ function SearchBox({
placeholder: string;
}) {
return (
<div className="rounded-2xl border border-white/10 bg-black/20 px-3 py-2">
<div className="platform-subpanel rounded-2xl px-3 py-2">
<input
value={value}
onChange={(event) => onChange(event.target.value)}
placeholder={placeholder}
className="w-full bg-transparent text-sm text-zinc-100 outline-none placeholder:text-zinc-500"
className="w-full bg-transparent text-sm text-[var(--platform-text-strong)] outline-none placeholder:text-[var(--platform-text-soft)]"
/>
</div>
);
@@ -164,7 +160,7 @@ function ImageFrame({
}) {
return (
<div
className={`overflow-hidden rounded-2xl border border-white/10 bg-[radial-gradient(circle_at_top,rgba(56,189,248,0.16),transparent_48%),linear-gradient(180deg,rgba(19,24,39,0.96),rgba(8,10,17,0.92))] ${tone === 'landscape' ? 'aspect-[16/9]' : 'aspect-square'}`}
className={`overflow-hidden rounded-2xl border border-[var(--platform-subpanel-border)] bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.22),transparent_42%),linear-gradient(180deg,rgba(255,96,147,0.92),rgba(255,146,109,0.84))] ${tone === 'landscape' ? 'aspect-[16/9]' : 'aspect-square'}`}
>
{src ? (
<img src={src} alt={alt} className="h-full w-full object-cover" />
@@ -179,8 +175,8 @@ function ImageFrame({
function EmptyState({ title }: { title: string }) {
return (
<div className="rounded-2xl border border-dashed border-white/12 bg-black/20 px-5 py-6 text-center">
<div className="text-sm text-zinc-300">{title}</div>
<div className="platform-subpanel rounded-2xl border-dashed px-5 py-6 text-center">
<div className="text-sm text-[var(--platform-text-base)]">{title}</div>
</div>
);
}
@@ -195,7 +191,7 @@ function buildFallbackRenderKey(
function NewBadge() {
return (
<span className="rounded-full border border-amber-300/24 bg-amber-500/12 px-2.5 py-1 text-[10px] font-semibold text-amber-100">
<span className="platform-pill platform-pill--warm px-2.5 py-1 text-[10px] font-semibold">
</span>
);
@@ -211,21 +207,23 @@ function PendingEntityCard({
progress: number;
}) {
return (
<div className="rounded-[1.35rem] border border-sky-300/18 bg-sky-500/10 px-4 py-4">
<div className="platform-banner platform-banner--info rounded-[1.35rem] px-4 py-4">
<div className="flex items-start justify-between gap-3">
<div>
<div className="text-sm font-semibold text-white">{title}</div>
<div className="mt-1 text-xs leading-6 text-sky-50/90">
<div className="text-sm font-semibold text-[var(--platform-text-strong)]">
{title}
</div>
<div className="mt-1 text-xs leading-6">
{phaseLabel}
</div>
</div>
<div className="rounded-full border border-sky-300/20 bg-black/20 px-2.5 py-1 text-[10px] text-sky-100">
<div className="platform-pill platform-pill--cool px-2.5 py-1 text-[10px]">
{Math.round(progress)}%
</div>
</div>
<div className="mt-3 h-2.5 overflow-hidden rounded-full border border-white/10 bg-black/30">
<div className="platform-progress-track mt-3 h-2.5 overflow-hidden rounded-full">
<div
className="h-full bg-[linear-gradient(90deg,#38bdf8_0%,#67e8f9_100%)] transition-[width] duration-300"
className="h-full bg-[linear-gradient(90deg,#ff4f8b_0%,#ff8a73_100%)] transition-[width] duration-300"
style={{ width: `${Math.max(6, Math.min(100, progress))}%` }}
/>
</div>
@@ -261,7 +259,7 @@ function CatalogCard({
className={`shrink-0 rounded-full border px-2.5 py-1 text-[10px] ${
isSelected
? 'border-rose-300/25 bg-rose-500/14 text-rose-50'
: 'border-white/10 bg-black/20 text-zinc-400'
: 'platform-subpanel text-[var(--platform-text-soft)]'
}`}
>
{isSelected ? '已选' : '选择'}
@@ -277,14 +275,12 @@ function CatalogCard({
className={`w-full rounded-[1.3rem] border p-2.5 text-left transition-colors ${
isSelected
? 'border-rose-300/35 bg-rose-500/10'
: disabled
? 'border-white/10 bg-black/20'
: 'border-white/10 bg-black/20 hover:border-white/20 hover:bg-black/28'
: 'platform-subpanel'
}`}
>
<div className="flex items-start gap-3">
<div
className={`shrink-0 overflow-hidden rounded-[1rem] border border-white/8 bg-black/25 ${mediaClassName ?? 'h-[4.75rem] w-[4.75rem]'}`}
className={`platform-subpanel shrink-0 overflow-hidden rounded-[1rem] ${mediaClassName ?? 'h-[4.75rem] w-[4.75rem]'}`}
>
{media}
</div>
@@ -315,14 +311,12 @@ function CatalogCard({
className={`w-full rounded-[1.4rem] border p-3 text-left transition-colors ${
isSelected
? 'border-rose-300/35 bg-rose-500/10'
: disabled
? 'border-white/10 bg-black/20'
: 'border-white/10 bg-black/20 hover:border-white/20 hover:bg-black/28'
: 'platform-subpanel'
}`}
>
<div className="space-y-3">
<div
className={`overflow-hidden rounded-[1.1rem] border border-white/8 bg-black/25 ${mediaClassName ?? ''}`}
className={`platform-subpanel overflow-hidden rounded-[1.1rem] ${mediaClassName ?? ''}`}
>
{media}
</div>
@@ -1030,7 +1024,7 @@ export function CustomWorldEntityCatalog({
<div className="text-[11px] font-bold tracking-[0.28em] text-zinc-500">
</div>
<div className="mt-2 text-3xl font-black text-white sm:text-[2.2rem]">
<div className="mt-2 text-3xl font-black text-[var(--platform-text-strong)] sm:text-[2.2rem]">
{profile.name}
</div>
<div className="mt-2 text-sm tracking-[0.18em] text-zinc-400">
@@ -1038,17 +1032,17 @@ export function CustomWorldEntityCatalog({
</div>
</div>
<div className="sticky top-0 z-10 -mx-1 space-y-3 bg-[linear-gradient(180deg,rgba(8,10,17,0.98)_0%,rgba(8,10,17,0.95)_75%,rgba(8,10,17,0)_100%)] px-1 pb-3 pt-1">
<div className="sticky top-0 z-10 -mx-1 space-y-3 bg-[linear-gradient(180deg,rgba(255,252,253,0.98)_0%,rgba(255,244,248,0.94)_76%,rgba(255,244,248,0)_100%)] px-1 pb-3 pt-1 backdrop-blur-sm">
<div className="flex gap-2 overflow-x-auto pb-1 scrollbar-hide">
{RESULT_TABS.map((tab) => (
<div key={tab.id}>
<button
type="button"
onClick={() => onActiveTabChange(tab.id)}
className={`rounded-full border px-3 py-2 text-left text-sm transition-colors ${activeTab === tab.id ? 'border-sky-300/25 bg-sky-500/12 text-white' : 'border-white/10 bg-black/20 text-zinc-300 hover:text-white'}`}
className={`platform-tab px-3 py-2 text-left text-sm ${activeTab === tab.id ? 'platform-tab--active' : ''}`}
>
<div className="font-semibold">{tab.label}</div>
<div className="mt-1 text-[10px] tracking-[0.16em] text-white/55">
<div className="mt-1 text-[10px] tracking-[0.16em] text-[var(--platform-text-soft)]">
{counts[tab.id]}
</div>
</button>
@@ -1068,7 +1062,7 @@ export function CustomWorldEntityCatalog({
<div className="flex flex-wrap items-center justify-end gap-2">
{isBulkDeleteMode ? (
<>
<div className="rounded-full border border-white/10 bg-black/20 px-3 py-1 text-[11px] text-zinc-300">
<div className="platform-pill platform-pill--neutral px-3 py-1 text-[11px]">
{selectedBulkIds.length}
</div>
<SmallButton onClick={cancelBulkDelete}></SmallButton>
@@ -1109,19 +1103,19 @@ export function CustomWorldEntityCatalog({
<>
<Section title="档案规模">
<div className="grid grid-cols-3 gap-2 text-center text-[11px] text-zinc-300">
<div className="rounded-xl border border-white/8 bg-black/20 px-2 py-3">
<div className="platform-subpanel rounded-xl px-2 py-3">
<div className="text-xl font-black text-white">
{profile.playableNpcs.length}
</div>
<div></div>
</div>
<div className="rounded-xl border border-white/8 bg-black/20 px-2 py-3">
<div className="platform-subpanel rounded-xl px-2 py-3">
<div className="text-xl font-black text-white">
{profile.storyNpcs.length}
</div>
<div></div>
</div>
<div className="rounded-xl border border-white/8 bg-black/20 px-2 py-3">
<div className="platform-subpanel rounded-xl px-2 py-3">
<div className="text-xl font-black text-white">
{profile.landmarks.length + 1}
</div>
@@ -1150,15 +1144,15 @@ export function CustomWorldEntityCatalog({
)
}
>
<div className="space-y-3 text-sm leading-7 text-zinc-300">
<p>{profile.summary}</p>
<div className="rounded-2xl border border-amber-300/12 bg-amber-500/8 px-3 py-3 text-amber-100">
线{profile.playerGoal}
<div className="space-y-3 text-sm leading-7 text-zinc-300">
<p>{profile.summary}</p>
<div className="platform-banner platform-banner--warning rounded-2xl px-3 py-3">
线{profile.playerGoal}
</div>
<div className="platform-subpanel rounded-2xl px-3 py-3">
{profile.tone}
</div>
</div>
<div className="rounded-2xl border border-white/8 bg-black/20 px-3 py-3">
{profile.tone}
</div>
</div>
</Section>
<Section
@@ -1186,7 +1180,7 @@ export function CustomWorldEntityCatalog({
{structuredFoundationEntries.map((entry) => (
<div
key={entry.id}
className="rounded-2xl border border-white/8 bg-black/20 px-4 py-4"
className="platform-subpanel rounded-2xl px-4 py-4"
>
<div className="text-[11px] font-bold tracking-[0.18em] text-zinc-500">
{entry.label}
@@ -1261,30 +1255,30 @@ export function CustomWorldEntityCatalog({
className="h-full w-full object-cover object-top"
/>
) : (
<div className="flex h-full w-full items-center justify-center bg-black/30 px-3 text-center text-xs font-semibold tracking-[0.16em] text-zinc-400">
{role.name.slice(0, 4) || '角色'}
</div>
<div className="flex h-full w-full items-center justify-center bg-[rgba(255,255,255,0.64)] px-3 text-center text-xs font-semibold tracking-[0.16em] text-[var(--platform-text-soft)]">
{role.name.slice(0, 4) || '角色'}
</div>
)
}
/>
<div className="flex flex-wrap items-center gap-2 px-1">
{lockedCharacterNames.has(role.name.trim()) ? (
<span className="rounded-full border border-amber-300/18 bg-amber-500/10 px-2.5 py-1 text-[10px] text-amber-100">
<span className="platform-pill platform-pill--warm px-2.5 py-1 text-[10px]">
</span>
) : null}
<span className="rounded-full border border-white/10 bg-black/20 px-2.5 py-1 text-[10px] text-zinc-300">
<span className="platform-pill platform-pill--neutral px-2.5 py-1 text-[10px]">
{role.initialAffinity}
</span>
{role.generatedVisualAssetId ? (
<span className="rounded-full border border-emerald-400/20 bg-emerald-500/10 px-2.5 py-1 text-[10px] text-emerald-100">
<span className="platform-pill platform-pill--success px-2.5 py-1 text-[10px]">
</span>
) : null}
{role.tags.slice(0, 2).map((tag) => (
<span
key={`${role.id}-${tag}`}
className="rounded-full border border-white/10 bg-black/20 px-2.5 py-1 text-[10px] text-zinc-300"
className="platform-pill platform-pill--neutral px-2.5 py-1 text-[10px]"
>
{tag}
</span>