This commit is contained in:
2026-04-29 11:51:04 +08:00
parent e191619ab3
commit 412279ae11
89 changed files with 3966 additions and 491 deletions

View File

@@ -1,11 +1,11 @@
import {type Dispatch, type SetStateAction,useState} from 'react';
import { type Dispatch, type SetStateAction, useState } from 'react';
import {
generateCharacterPanelChatSuggestions,
generateCharacterPanelChatSummary,
streamCharacterPanelChatReply,
} from '../../services/aiService';
import type {StoryGenerationContext} from '../../services/aiTypes';
import type { StoryGenerationContext } from '../../services/aiTypes';
import type {
Character,
CharacterChatRecord,
@@ -47,12 +47,17 @@ export interface CharacterChatUi {
sendDraft: () => void;
}
export function getCharacterChatRecord(state: GameState, characterId: string): CharacterChatRecord {
return state.characterChats[characterId] ?? {
history: [],
summary: '',
updatedAt: null,
};
export function getCharacterChatRecord(
state: GameState,
characterId: string,
): CharacterChatRecord {
return (
state.characterChats[characterId] ?? {
history: [],
summary: '',
updatedAt: null,
}
);
}
export function trimCharacterChatHistory(history: CharacterChatTurn[]) {
@@ -66,7 +71,10 @@ export function buildLocalCharacterChatSummary(
) {
const latestTurns = history
.slice(-4)
.map(turn => `${turn.speaker === 'player' ? '玩家' : character.name}: ${turn.text}`)
.map(
(turn) =>
`${turn.speaker === 'player' ? '玩家' : character.name}: ${turn.text}`,
)
.join(' ');
const currentSummary = latestTurns
@@ -111,7 +119,9 @@ type CharacterChatTargetStatus = {
affinity?: number | null;
};
function buildTargetStatus(target: CharacterChatTarget): CharacterChatTargetStatus {
function buildTargetStatus(
target: CharacterChatTarget,
): CharacterChatTargetStatus {
return {
roleLabel: target.roleLabel,
hp: target.hp,
@@ -129,9 +139,13 @@ export function useCharacterChatFlow({
}: {
gameState: GameState;
setGameState: Dispatch<SetStateAction<GameState>>;
buildStoryContextFromState: (state: GameState) => StoryGenerationContext;
buildStoryContextFromState: (
state: GameState,
extras?: { currentStory?: null },
) => StoryGenerationContext;
}) {
const [characterChatModal, setCharacterChatModal] = useState<CharacterChatModalState | null>(null);
const [characterChatModal, setCharacterChatModal] =
useState<CharacterChatModalState | null>(null);
const loadCharacterChatSuggestions = async (
target: CharacterChatTarget,
@@ -139,7 +153,7 @@ export function useCharacterChatFlow({
summary: string,
) => {
if (!gameState.worldType || !gameState.playerCharacter) {
setCharacterChatModal(current =>
setCharacterChatModal((current) =>
current && current.target.character.id === target.character.id
? {
...current,
@@ -151,7 +165,7 @@ export function useCharacterChatFlow({
return;
}
setCharacterChatModal(current =>
setCharacterChatModal((current) =>
current && current.target.character.id === target.character.id
? {
...current,
@@ -172,7 +186,7 @@ export function useCharacterChatFlow({
buildTargetStatus(target),
);
setCharacterChatModal(current =>
setCharacterChatModal((current) =>
current && current.target.character.id === target.character.id
? {
...current,
@@ -183,7 +197,7 @@ export function useCharacterChatFlow({
);
} catch (error) {
console.error('Failed to generate character chat suggestions:', error);
setCharacterChatModal(current =>
setCharacterChatModal((current) =>
current && current.target.character.id === target.character.id
? {
...current,
@@ -213,7 +227,11 @@ export function useCharacterChatFlow({
};
const sendCharacterChatDraft = async () => {
if (!characterChatModal || !gameState.worldType || !gameState.playerCharacter) {
if (
!characterChatModal ||
!gameState.worldType ||
!gameState.playerCharacter
) {
return;
}
@@ -223,7 +241,10 @@ export function useCharacterChatFlow({
}
const target = characterChatModal.target;
const existingRecord = getCharacterChatRecord(gameState, target.character.id);
const existingRecord = getCharacterChatRecord(
gameState,
target.character.id,
);
const baseMessages = trimCharacterChatHistory(characterChatModal.messages);
const nextMessages = trimCharacterChatHistory([
...baseMessages,
@@ -233,12 +254,12 @@ export function useCharacterChatFlow({
},
]);
setCharacterChatModal(current =>
setCharacterChatModal((current) =>
current && current.target.character.id === target.character.id
? {
...current,
draft: '',
messages: [...nextMessages, {speaker: 'character', text: ''}],
messages: [...nextMessages, { speaker: 'character', text: '' }],
suggestions: [],
isSending: true,
isLoadingSuggestions: true,
@@ -261,12 +282,12 @@ export function useCharacterChatFlow({
draft,
buildTargetStatus(target),
{
onUpdate: text => {
setCharacterChatModal(current =>
onUpdate: (text) => {
setCharacterChatModal((current) =>
current && current.target.character.id === target.character.id
? {
...current,
messages: [...nextMessages, {speaker: 'character', text}],
messages: [...nextMessages, { speaker: 'character', text }],
}
: current,
);
@@ -275,7 +296,7 @@ export function useCharacterChatFlow({
);
} catch (error) {
console.error('Failed to stream character panel chat reply:', error);
setCharacterChatModal(current =>
setCharacterChatModal((current) =>
current && current.target.character.id === target.character.id
? {
...current,
@@ -283,10 +304,12 @@ export function useCharacterChatFlow({
messages: baseMessages,
isSending: false,
isLoadingSuggestions: false,
error: error instanceof Error ? error.message : '未知智能生成错误',
suggestions: current.suggestions.length > 0
? current.suggestions
: buildLocalCharacterChatSuggestions(target.character),
error:
error instanceof Error ? error.message : '未知智能生成错误',
suggestions:
current.suggestions.length > 0
? current.suggestions
: buildLocalCharacterChatSuggestions(target.character),
}
: current,
);
@@ -315,7 +338,11 @@ export function useCharacterChatFlow({
);
} catch (error) {
console.error('Failed to summarize character chat:', error);
nextSummary = buildLocalCharacterChatSummary(target.character, finalMessages, existingRecord.summary);
nextSummary = buildLocalCharacterChatSummary(
target.character,
finalMessages,
existingRecord.summary,
);
}
const nextRecord: CharacterChatRecord = {
@@ -324,10 +351,10 @@ export function useCharacterChatFlow({
updatedAt: new Date().toISOString(),
};
setGameState(current =>
setGameState((current) =>
buildCharacterChatRecordUpdate(current, target.character.id, nextRecord),
);
setCharacterChatModal(current =>
setCharacterChatModal((current) =>
current && current.target.character.id === target.character.id
? {
...current,
@@ -346,8 +373,14 @@ export function useCharacterChatFlow({
modal: characterChatModal,
openChat: openCharacterChat,
closeChat: () => setCharacterChatModal(null),
setDraft: (value: string) => setCharacterChatModal(current => (current ? {...current, draft: value} : current)),
useSuggestion: (value: string) => setCharacterChatModal(current => (current ? {...current, draft: value} : current)),
setDraft: (value: string) =>
setCharacterChatModal((current) =>
current ? { ...current, draft: value } : current,
),
useSuggestion: (value: string) =>
setCharacterChatModal((current) =>
current ? { ...current, draft: value } : current,
),
refreshSuggestions: () => {
if (!characterChatModal) {
return;