This commit is contained in:
2026-04-26 16:50:53 +08:00
parent ea33413187
commit 705a2d3dd8
30 changed files with 1537 additions and 570 deletions

View File

@@ -412,6 +412,15 @@ function buildDefaultSceneActBackgroundPrompt(params: {
return `${sceneText}${phaseText}画面,${roleText}与玩家隔着可站立空间形成对峙,环境里保留“${params.eventDescription}”的冲突痕迹与清晰氛围。`;
}
function buildDefaultSceneTaskDescription(landmark: CustomWorldLandmark) {
const sceneName = landmark.name.trim() || '当前场景';
const sceneDescription = landmark.description.trim();
if (!sceneDescription) {
return `首次进入${sceneName}时,确认当前场景的核心异常、关键角色与下一步行动方向。`;
}
return `首次进入${sceneName}时,围绕${sceneDescription}确认核心任务、关键角色与下一步行动。`;
}
function buildDefaultSceneChapterBlueprint(params: {
landmark: CustomWorldLandmark;
fallbackImageSrc?: string | null;
@@ -432,7 +441,7 @@ function buildDefaultSceneChapterBlueprint(params: {
sceneId: params.landmark.id,
title: params.chapterTitle?.trim() || params.landmark.name.trim() || '场景章节',
summary: params.chapterSummary?.trim() || params.landmark.description.trim(),
sceneTaskDescription: params.landmark.description.trim(),
sceneTaskDescription: buildDefaultSceneTaskDescription(params.landmark),
linkedThreadIds: dedupeTextValues(params.linkedThreadIds ?? []),
linkedLandmarkIds: dedupeTextValues([
params.landmark.id,
@@ -502,11 +511,12 @@ function sanitizeSceneChapterBlueprint(params: {
availableSceneNpcIdSet.size > 0
? candidateNpcIds.filter((npcId) => availableSceneNpcIdSet.has(npcId))
: candidateNpcIds;
// 中文注释:已有幕只信任本幕保存的槽位;只有缺少整份幕蓝图的旧草稿才从场景角色里兜底,避免配置第一幕时把角色串到其他幕。
const resolvedEncounterNpcIds =
encounterNpcIds.length > 0
? encounterNpcIds
: availableSceneNpcIds.length > 0
? availableSceneNpcIds.slice(0, 1)
: currentAct
? []
: fallbackAct.encounterNpcIds;
const primaryNpcId = resolvedEncounterNpcIds[0] ?? '';
const oppositeNpcId = currentAct?.oppositeNpcId?.trim() || primaryNpcId;
@@ -554,6 +564,13 @@ function sanitizeSceneChapterBlueprint(params: {
id: params.chapter?.id?.trim() || fallbackChapter.id,
title: params.chapter?.title?.trim() || fallbackChapter.title,
summary: params.chapter?.summary?.trim() || fallbackChapter.summary,
sceneTaskDescription: (() => {
const currentTask = params.chapter?.sceneTaskDescription?.trim() ?? '';
const sceneDescription = params.landmark.description.trim();
return currentTask && currentTask !== sceneDescription
? currentTask
: fallbackChapter.sceneTaskDescription;
})(),
linkedThreadIds: dedupeTextValues(params.chapter?.linkedThreadIds ?? []),
linkedLandmarkIds: dedupeTextValues([
params.landmark.id,
@@ -1651,9 +1668,10 @@ function SceneActNpcSlotPickerModal({
<ModalShell
title={`配置角色:${actLabel} · ${slotLabel}`}
onClose={onClose}
panelClassName="sm:max-w-4xl"
panelClassName="flex max-h-[88vh] flex-col sm:max-w-4xl"
>
<div className="space-y-4">
<div className="flex min-h-0 flex-1 flex-col">
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto pr-1">
<div className="rounded-2xl border border-white/8 bg-black/20 px-4 py-4">
<div className="text-[11px] font-bold tracking-[0.16em] text-zinc-300">
@@ -1746,7 +1764,9 @@ function SceneActNpcSlotPickerModal({
</div>
</div>
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-end">
</div>
<div className="mt-4 flex shrink-0 flex-col gap-3 border-t border-white/8 bg-zinc-950/95 pt-4 sm:flex-row sm:justify-end">
{selectedNpc ? (
<ActionButton
label="移除角色"
@@ -1757,7 +1777,6 @@ function SceneActNpcSlotPickerModal({
tone="rose"
/>
) : null}
<ActionButton label="取消" onClick={onClose} />
<ActionButton
label="保存角色"
onClick={() => {
@@ -5752,7 +5771,7 @@ export function LandmarkEditor({
setIsCloseConfirmOpen(true);
};
const updateSceneActDraft = (
const updateSceneChapterDraft = (
updater: (chapter: SceneChapterBlueprint) => SceneChapterBlueprint,
) => {
setSceneChapterDraft((current) =>
@@ -5780,7 +5799,7 @@ export function LandmarkEditor({
index: number,
updater: (act: SceneActBlueprint) => SceneActBlueprint,
) => {
updateSceneActDraft((current) => ({
updateSceneChapterDraft((current) => ({
...current,
acts: current.acts.map((act, actIndex) =>
actIndex === index ? updater(act) : act,
@@ -5794,7 +5813,7 @@ export function LandmarkEditor({
return;
}
updateSceneActDraft((current) => {
updateSceneChapterDraft((current) => {
const nextActCount = current.acts.length + 1;
return {
...current,
@@ -5821,14 +5840,14 @@ export function LandmarkEditor({
return;
}
updateSceneActDraft((current) => ({
updateSceneChapterDraft((current) => ({
...current,
acts: current.acts.filter((_act, actIndex) => actIndex !== index),
}));
};
const moveSceneAct = (index: number, delta: number) => {
updateSceneActDraft((current) => ({
updateSceneChapterDraft((current) => ({
...current,
acts: moveArrayItem(current.acts, index, index + delta),
}));
@@ -5836,7 +5855,7 @@ export function LandmarkEditor({
const updateSceneActSharedBackground = (imageSrc?: string | null) => {
const resolvedImageSrc = imageSrc?.trim() || compatibilityImageSrc || '';
updateSceneActDraft((current) => ({
updateSceneChapterDraft((current) => ({
...current,
acts: current.acts.map((act) => ({
...act,
@@ -6004,6 +6023,18 @@ export function LandmarkEditor({
rows={5}
/>
</Field>
<Field label="场景任务">
<TextArea
value={renderedSceneChapterDraft.sceneTaskDescription}
onChange={(value) =>
updateSceneChapterDraft((current) => ({
...current,
sceneTaskDescription: value,
}))
}
rows={3}
/>
</Field>
<SectionPanel
title="多幕配置"
actions={