feat: multiple sessions per story with streaming AI responses
This commit is contained in:
@@ -4,18 +4,23 @@ import { useAuth } from "../contexts/AuthContext";
|
||||
import {
|
||||
getStory,
|
||||
deleteStory as apiDeleteStory,
|
||||
getSession,
|
||||
getSessionsList,
|
||||
getPlayerCharacters,
|
||||
} from "../services/api";
|
||||
import type { Story, GameSession, PlayerCharacter } from "../types";
|
||||
import type { Story, PlayerCharacter } from "../types";
|
||||
import "./StoryDetailPage.css";
|
||||
|
||||
interface SessionsInfo {
|
||||
count: number;
|
||||
totalMessages: number;
|
||||
}
|
||||
|
||||
export default function StoryDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { isAuthenticated } = useAuth();
|
||||
const [story, setStory] = useState<Story | null>(null);
|
||||
const [session, setSession] = useState<GameSession | null>(null);
|
||||
const [sessionsInfo, setSessionsInfo] = useState<SessionsInfo | null>(null);
|
||||
const [playerCharacters, setPlayerCharacters] = useState<PlayerCharacter[]>(
|
||||
[],
|
||||
);
|
||||
@@ -39,10 +44,13 @@ export default function StoryDetailPage() {
|
||||
...foundStory,
|
||||
id: (foundStory as any)._id || foundStory.id,
|
||||
});
|
||||
const existingSession = await getSession(id);
|
||||
if (existingSession) {
|
||||
setSession(existingSession);
|
||||
setSelectedCharacter(existingSession.playerId || null);
|
||||
const sessions = await getSessionsList(id);
|
||||
if (sessions.length > 0) {
|
||||
const totalMessages = sessions.reduce((sum, s) => sum + s.messagesCount, 0);
|
||||
setSessionsInfo({
|
||||
count: sessions.length,
|
||||
totalMessages,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +89,7 @@ export default function StoryDetailPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
if (!sessionsInfo) {
|
||||
// Новая игра — показываем выбор персонажа
|
||||
setShowCharacterSelect(true);
|
||||
} else {
|
||||
@@ -98,16 +106,6 @@ export default function StoryDetailPage() {
|
||||
navigate(`/play/${story!.id}?character=${selectedCharacter}`);
|
||||
};
|
||||
|
||||
// Примерный подсчёт токенов (1 токен ≈ 3 символа для русского текста)
|
||||
const estimateTokens = (messages: GameSession["messages"] | undefined) => {
|
||||
if (!messages || messages.length === 0) return 0;
|
||||
const totalChars = messages.reduce(
|
||||
(sum, msg) => sum + msg.content.length,
|
||||
0,
|
||||
);
|
||||
return Math.round(totalChars / 3);
|
||||
};
|
||||
|
||||
const formatTokens = (tokens: number) => {
|
||||
if (tokens >= 1000000) return `${(tokens / 1000000).toFixed(1)}M`;
|
||||
if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}K`;
|
||||
@@ -302,24 +300,22 @@ export default function StoryDetailPage() {
|
||||
)}
|
||||
</section>
|
||||
|
||||
{session && (
|
||||
{sessionsInfo && (
|
||||
<section className="detail-section session-info">
|
||||
<h2>🎮 Текущий прогресс</h2>
|
||||
<div className="session-stats">
|
||||
<div className="stat">
|
||||
<span className="stat-label">Сообщений</span>
|
||||
<span className="stat-value">{session.messages.length}</span>
|
||||
<span className="stat-label">Сессий</span>
|
||||
<span className="stat-value">{sessionsInfo.count}</span>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<span className="stat-label">Локация</span>
|
||||
<span className="stat-value">
|
||||
{session.currentState.location || "Неизвестно"}
|
||||
</span>
|
||||
<span className="stat-label">Сообщений</span>
|
||||
<span className="stat-value">{sessionsInfo.totalMessages}</span>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<span className="stat-label">≈ Токенов</span>
|
||||
<span className="stat-value">
|
||||
{formatTokens(estimateTokens(session.messages))}
|
||||
{formatTokens(sessionsInfo.totalMessages * 50)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -328,7 +324,7 @@ export default function StoryDetailPage() {
|
||||
|
||||
<div className="story-actions">
|
||||
<button onClick={handleStartGame} className="action-btn play-btn">
|
||||
{session ? "🎮 Продолжить игру" : "🎮 Начать приключение"}
|
||||
{sessionsInfo ? "🎮 Продолжить игру" : "🎮 Начать приключение"}
|
||||
</button>
|
||||
<Link to={`/edit/${story.id}`} className="action-btn edit-btn">
|
||||
✏️ Редактировать
|
||||
|
||||
Reference in New Issue
Block a user