222 lines
7.4 KiB
TypeScript
222 lines
7.4 KiB
TypeScript
import type { CustomWorldDraftCardDetail } from '../../../packages/shared/src/contracts/customWorldAgent';
|
||
import { CustomWorldDraftEditPanel } from './CustomWorldDraftEditPanel';
|
||
|
||
type CustomWorldAgentDraftDetailPanelProps = {
|
||
detail: CustomWorldDraftCardDetail | null;
|
||
loading: boolean;
|
||
busy?: boolean;
|
||
editMode?: boolean;
|
||
onClose: () => void;
|
||
onStartEdit?: () => void;
|
||
onCancelEdit?: () => void;
|
||
onSave?: (
|
||
sections: Array<{
|
||
sectionId: string;
|
||
value: string;
|
||
}>,
|
||
) => void;
|
||
onGenerateCharacter?: () => void;
|
||
onGenerateLandmark?: () => void;
|
||
onOpenRoleAssetStudio?: () => void;
|
||
};
|
||
|
||
function resolveKindLabel(kind: CustomWorldDraftCardDetail['kind']) {
|
||
if (kind === 'world') return '世界总卡';
|
||
if (kind === 'camp') return '营地';
|
||
if (kind === 'faction') return '势力';
|
||
if (kind === 'character') return '角色';
|
||
if (kind === 'landmark') return '地点';
|
||
if (kind === 'thread') return '线程';
|
||
if (kind === 'chapter') return '第一幕';
|
||
if (kind === 'scene_chapter') return '场景章节';
|
||
return '草稿卡';
|
||
}
|
||
|
||
function ActionButton(props: {
|
||
label: string;
|
||
onClick?: () => void;
|
||
disabled?: boolean;
|
||
tone?: 'default' | 'sky';
|
||
}) {
|
||
const { label, onClick, disabled = false, tone = 'default' } = props;
|
||
|
||
if (!onClick) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<button
|
||
type="button"
|
||
onClick={onClick}
|
||
disabled={disabled}
|
||
className={`rounded-full border px-3 py-1.5 text-[11px] transition ${
|
||
tone === 'sky'
|
||
? 'border-sky-300/20 bg-sky-500/10 text-sky-100 hover:text-white'
|
||
: 'border-white/10 bg-black/20 text-zinc-300 hover:text-white'
|
||
} disabled:cursor-not-allowed disabled:opacity-45`}
|
||
>
|
||
{label}
|
||
</button>
|
||
);
|
||
}
|
||
|
||
export function CustomWorldAgentDraftDetailPanel({
|
||
detail,
|
||
loading,
|
||
busy = false,
|
||
editMode = false,
|
||
onClose,
|
||
onStartEdit,
|
||
onCancelEdit,
|
||
onSave,
|
||
onGenerateCharacter,
|
||
onGenerateLandmark,
|
||
onOpenRoleAssetStudio,
|
||
}: CustomWorldAgentDraftDetailPanelProps) {
|
||
const shouldRenderImagePreview = (
|
||
detailKind: CustomWorldDraftCardDetail['kind'],
|
||
sectionId: string,
|
||
value: string,
|
||
) =>
|
||
detailKind === 'scene_chapter' &&
|
||
sectionId.endsWith(':backgroundImageSrc') &&
|
||
value !== '待继续精修';
|
||
|
||
return (
|
||
<section className="platform-remap-surface rounded-[1.5rem] border border-white/10 bg-black/18 px-4 py-4">
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div>
|
||
<div className="text-[11px] font-bold tracking-[0.2em] text-zinc-400">
|
||
卡片详情
|
||
</div>
|
||
<div className="mt-2 text-lg font-semibold text-white">
|
||
{loading ? '正在读取' : detail?.title || '选择一张草稿卡'}
|
||
</div>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="rounded-full border border-white/10 bg-black/20 px-3 py-1 text-[11px] text-zinc-300 transition hover:text-white"
|
||
>
|
||
关闭
|
||
</button>
|
||
</div>
|
||
|
||
{loading ? (
|
||
<div className="mt-4 rounded-[1.15rem] border border-white/8 bg-white/5 px-4 py-5 text-sm leading-7 text-zinc-300">
|
||
正在整理这张卡的内容。
|
||
</div>
|
||
) : detail ? (
|
||
<div className="mt-4 space-y-3">
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
<span className="rounded-full border border-sky-300/20 bg-sky-500/10 px-3 py-1 text-[11px] text-sky-100">
|
||
{resolveKindLabel(detail.kind)}
|
||
</span>
|
||
<span className="rounded-full border border-white/10 bg-black/24 px-3 py-1 text-[11px] text-zinc-300">
|
||
关联 {detail.linkedIds.length}
|
||
</span>
|
||
{detail.editable ? (
|
||
<span className="rounded-full border border-emerald-300/20 bg-emerald-500/10 px-3 py-1 text-[11px] text-emerald-100">
|
||
可编辑
|
||
</span>
|
||
) : null}
|
||
{detail.kind === 'character' && detail.assetStatusLabel ? (
|
||
<span className="rounded-full border border-amber-300/20 bg-amber-500/10 px-3 py-1 text-[11px] text-amber-100">
|
||
{detail.assetStatusLabel}
|
||
</span>
|
||
) : null}
|
||
</div>
|
||
|
||
<div className="flex flex-wrap gap-2">
|
||
{!editMode && detail.editable ? (
|
||
<ActionButton
|
||
label="编辑设定"
|
||
onClick={onStartEdit}
|
||
disabled={busy}
|
||
/>
|
||
) : null}
|
||
{!editMode && detail.kind === 'character' ? (
|
||
<ActionButton
|
||
label="角色资产"
|
||
onClick={onOpenRoleAssetStudio}
|
||
disabled={busy}
|
||
tone="sky"
|
||
/>
|
||
) : null}
|
||
{!editMode ? (
|
||
<>
|
||
<ActionButton
|
||
label="新增角色"
|
||
onClick={onGenerateCharacter}
|
||
disabled={busy}
|
||
tone="sky"
|
||
/>
|
||
<ActionButton
|
||
label="新增场景"
|
||
onClick={onGenerateLandmark}
|
||
disabled={busy}
|
||
tone="sky"
|
||
/>
|
||
</>
|
||
) : null}
|
||
</div>
|
||
|
||
{editMode && onSave && onCancelEdit ? (
|
||
<CustomWorldDraftEditPanel
|
||
detail={detail}
|
||
disabled={busy}
|
||
onSave={onSave}
|
||
onCancel={onCancelEdit}
|
||
/>
|
||
) : (
|
||
<div className="space-y-2">
|
||
{detail.sections.map((section) => (
|
||
<div
|
||
key={section.id}
|
||
className="rounded-[1.1rem] border border-white/8 bg-white/5 px-3 py-3"
|
||
>
|
||
<div className="text-[11px] tracking-[0.16em] text-zinc-400">
|
||
{section.label}
|
||
</div>
|
||
{shouldRenderImagePreview(detail.kind, section.id, section.value) ? (
|
||
<img
|
||
src={section.value}
|
||
alt={section.label}
|
||
className="mt-3 h-40 w-full rounded-[1rem] border border-white/10 object-cover"
|
||
/>
|
||
) : null}
|
||
<div className="mt-2 whitespace-pre-wrap text-sm leading-7 text-zinc-100">
|
||
{section.value}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{detail.warningMessages.length > 0 ? (
|
||
<div className="rounded-[1.15rem] border border-amber-300/20 bg-amber-500/10 px-4 py-4">
|
||
<div className="text-[11px] font-bold tracking-[0.18em] text-amber-100">
|
||
继续精修
|
||
</div>
|
||
<div className="mt-3 space-y-2">
|
||
{detail.warningMessages.map((message, index) => (
|
||
<div
|
||
key={`${detail.id}-warning-${index}`}
|
||
className="text-sm leading-7 text-amber-50"
|
||
>
|
||
{message}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
) : (
|
||
<div className="mt-4 rounded-[1.15rem] border border-dashed border-white/10 bg-black/22 px-4 py-5 text-sm leading-7 text-zinc-400">
|
||
从草稿抽屉里点开一张卡,就能在这里看世界底稿的具体内容。
|
||
</div>
|
||
)}
|
||
</section>
|
||
);
|
||
}
|