1
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-18 17:28:23 +08:00
parent b3066c7bc1
commit 54b3d3c490
21 changed files with 731 additions and 156 deletions

View File

@@ -62,6 +62,8 @@ interface AdventurePanelProps {
canRefreshOptions: boolean;
onRefreshOptions: () => void;
onChoice: (option: StoryOption) => void;
onSubmitNpcChatInput?: (input: string) => boolean;
onExitNpcChat?: () => boolean;
onOpenCharacter: () => void;
onOpenInventory: () => void;
playerCharacter: Character;
@@ -149,12 +151,22 @@ function getOptionActionTextClass(option: StoryOption) {
function getDialogueTurnAlignmentClass(
turn: NonNullable<StoryMoment['dialogue']>[number],
) {
if (turn.speaker === 'system') {
return 'justify-center';
}
return turn.speaker === 'player' ? 'justify-end' : 'justify-start';
}
function getDialogueTurnBubbleClass(
turn: NonNullable<StoryMoment['dialogue']>[number],
) {
if (turn.speaker === 'system') {
return turn.affinityDelta && turn.affinityDelta > 0
? 'border-rose-400/30 bg-rose-500/12 text-rose-50'
: 'border-white/12 bg-white/[0.06] text-zinc-100';
}
if (turn.speaker === 'player') {
return 'border-sky-400/20 bg-sky-500/10 text-sky-50';
}
@@ -169,6 +181,10 @@ function getDialogueTurnBubbleClass(
function getDialogueTurnBubbleShapeClass(
turn: NonNullable<StoryMoment['dialogue']>[number],
) {
if (turn.speaker === 'system') {
return 'rounded-full';
}
if (turn.speaker === 'player') {
return 'rounded-2xl rounded-br-none';
}
@@ -183,6 +199,10 @@ function getDialogueTurnBubbleShapeClass(
function getDialogueTurnLabel(
turn: NonNullable<StoryMoment['dialogue']>[number],
) {
if (turn.speaker === 'system') {
return turn.affinityDelta && turn.affinityDelta > 0 ? '关系变化' : '系统';
}
if (turn.speaker === 'player') {
return '\u4f60';
}
@@ -597,6 +617,8 @@ export function AdventurePanel({
canRefreshOptions,
onRefreshOptions,
onChoice,
onSubmitNpcChatInput,
onExitNpcChat,
onOpenCharacter,
onOpenInventory,
playerCharacter,
@@ -622,6 +644,8 @@ export function AdventurePanel({
}: AdventurePanelProps) {
const isDialogueStory = currentStory.displayMode === 'dialogue';
const dialogueTurns = currentStory.dialogue ?? [];
const npcChatState = currentStory.npcChatState ?? null;
const isNpcChatMode = Boolean(npcChatState);
const isStoryStreaming = Boolean(currentStory.streaming);
const shouldHideChoiceUi = hideOptions;
const storyScrollContainerRef = useRef<HTMLDivElement | null>(null);
@@ -656,6 +680,7 @@ export function AdventurePanel({
const [selectedBattleRewardItemId, setSelectedBattleRewardItemId] = useState<
string | null
>(null);
const [npcChatDraft, setNpcChatDraft] = useState('');
const lastAutoOpenedGoalRef = useRef<string | null>(null);
const lastAutoOpenedPulseRef = useRef<string | null>(null);
const battleReward = battleRewardUi.reward;
@@ -734,6 +759,10 @@ export function AdventurePanel({
setSelectedBattleRewardItemId(null);
}, [battleReward]);
useEffect(() => {
setNpcChatDraft('');
}, [npcChatState?.npcId, npcChatState?.turnCount]);
useEffect(() => {
if (!primaryQuestGoal) {
return;
@@ -887,6 +916,18 @@ export function AdventurePanel({
onDismissGoalPulse();
};
const submitNpcChatDraft = () => {
const nextInput = npcChatDraft.trim();
if (!nextInput || !onSubmitNpcChatInput) {
return;
}
const submitted = onSubmitNpcChatInput(nextInput);
if (submitted) {
setNpcChatDraft('');
}
};
return (
<div className="relative flex min-h-0 flex-1 flex-col">
<button