Add 'Alive' field to character cards in story summary
This commit is contained in:
+158
-122
@@ -1,4 +1,4 @@
|
|||||||
// DeepSeek API сервис для генерации историй
|
// DeepSeek API service for story generation
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Story,
|
Story,
|
||||||
@@ -9,11 +9,11 @@ import type {
|
|||||||
|
|
||||||
const DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions";
|
const DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions";
|
||||||
|
|
||||||
// Настройки контекста
|
// Context settings
|
||||||
const RECENT_MESSAGES_COUNT = 6; // Последние N сообщений для контекста
|
const RECENT_MESSAGES_COUNT = 6; // Last N messages for context
|
||||||
const SUMMARY_THRESHOLD = 20; // После скольких сообщений генерировать сводку
|
const SUMMARY_THRESHOLD = 20; // After how many messages to generate summary
|
||||||
|
|
||||||
// API ключ должен храниться в переменных окружения
|
// API key should be stored in environment variables
|
||||||
const getApiKey = () => import.meta.env.VITE_DEEPSEEK_API_KEY || "";
|
const getApiKey = () => import.meta.env.VITE_DEEPSEEK_API_KEY || "";
|
||||||
|
|
||||||
interface DeepSeekMessage {
|
interface DeepSeekMessage {
|
||||||
@@ -43,7 +43,7 @@ export async function sendMessage(
|
|||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"DeepSeek API ключ не настроен. Добавьте VITE_DEEPSEEK_API_KEY в .env файл",
|
"DeepSeek API key not configured. Add VITE_DEEPSEEK_API_KEY to your .env file",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ export async function sendMessage(
|
|||||||
|
|
||||||
const data: DeepSeekResponse = await response.json();
|
const data: DeepSeekResponse = await response.json();
|
||||||
|
|
||||||
// Логируем использование кэша (для отладки)
|
// Log cache usage (for debugging)
|
||||||
if (data.usage) {
|
if (data.usage) {
|
||||||
console.log(
|
console.log(
|
||||||
`[DeepSeek] Tokens - Prompt: ${data.usage.prompt_tokens}, Completion: ${data.usage.completion_tokens}`,
|
`[DeepSeek] Tokens - Prompt: ${data.usage.prompt_tokens}, Completion: ${data.usage.completion_tokens}`,
|
||||||
@@ -83,7 +83,7 @@ export async function sendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Streaming версия sendMessage - возвращает текст по частям
|
* Streaming version of sendMessage - returns text in chunks
|
||||||
*/
|
*/
|
||||||
export async function sendMessageStream(
|
export async function sendMessageStream(
|
||||||
messages: DeepSeekMessage[],
|
messages: DeepSeekMessage[],
|
||||||
@@ -95,7 +95,7 @@ export async function sendMessageStream(
|
|||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"DeepSeek API ключ не настроен. Добавьте VITE_DEEPSEEK_API_KEY в .env файл",
|
"DeepSeek API key not configured. Add VITE_DEEPSEEK_API_KEY to your .env file",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ export async function sendMessageStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Строит базовый системный промпт (правила стиля) - КЭШИРУЕТСЯ
|
* Builds base system prompt (style rules) - CACHED
|
||||||
*/
|
*/
|
||||||
export function buildStyleRules(
|
export function buildStyleRules(
|
||||||
story: Story,
|
story: Story,
|
||||||
@@ -167,91 +167,91 @@ export function buildStyleRules(
|
|||||||
|
|
||||||
const playerDescription = player?.description
|
const playerDescription = player?.description
|
||||||
? player.description.replace(/\{user\}/gi, player.name)
|
? player.description.replace(/\{user\}/gi, player.name)
|
||||||
: "Не указано";
|
: "Not specified";
|
||||||
|
|
||||||
// NSFW блок
|
// NSFW block
|
||||||
const nsfwBlock = story.isNsfw
|
const nsfwBlock = story.isNsfw
|
||||||
? `
|
? `
|
||||||
=== NSFW РЕЖИМ АКТИВЕН ===
|
=== NSFW MODE ACTIVE ===
|
||||||
Это история для взрослых. Сняты ограничения на:
|
This is an adult story. Restrictions lifted for:
|
||||||
— Сцены физического насилия, крови, травм и смерти
|
- Physical violence, blood, injuries, and death scenes
|
||||||
— Сцены сексуального характера и интимной близости
|
- Sexual content and intimate scenes
|
||||||
— Тёмные темы: пытки, жестокость, моральная серость
|
- Dark themes: torture, cruelty, moral ambiguity
|
||||||
— Откровенные описания тел, ран, боли
|
- Explicit descriptions of bodies, wounds, pain
|
||||||
|
|
||||||
ПРАВИЛА NSFW:
|
NSFW RULES:
|
||||||
1. Описывай сцены детально, но художественно
|
1. Describe scenes in detail, but artistically
|
||||||
2. Насилие должно иметь последствия и вес
|
2. Violence must have consequences and weight
|
||||||
3. Интимные сцены — по инициативе игрока
|
3. Intimate scenes only on player's initiative
|
||||||
4. Не цензурируй происходящее
|
4. Do not censor what happens
|
||||||
5. Сохраняй атмосферу и тон истории
|
5. Maintain the atmosphere and tone of the story
|
||||||
`
|
`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
// Если есть пользовательские правила - используем их
|
// If there are custom rules - use them
|
||||||
if (story.narrativeRules && story.narrativeRules.trim()) {
|
if (story.narrativeRules && story.narrativeRules.trim()) {
|
||||||
return `${story.narrativeRules}
|
return `${story.narrativeRules}
|
||||||
${nsfwBlock}
|
${nsfwBlock}
|
||||||
=== МЕТАДАННЫЕ ===
|
=== METADATA ===
|
||||||
ЯЗЫК: ${story.language}
|
LANGUAGE: ${story.language}
|
||||||
ЖАНР: ${story.genre.join(", ")}
|
GENRE: ${story.genre.join(", ")}
|
||||||
СЕТТИНГ: ${settingInfo}
|
SETTING: ${settingInfo}
|
||||||
|
|
||||||
=== ПЕРСОНАЖ ИГРОКА ===
|
=== PLAYER CHARACTER ===
|
||||||
Имя: ${player?.name || "Герой"}
|
Name: ${player?.name || "Hero"}
|
||||||
Описание: ${playerDescription}`;
|
Description: ${playerDescription}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Дефолтные правила для историй без кастомных настроек
|
// Default rules for stories without custom settings
|
||||||
return `Ты — РассказчикGPT, ведущий интерактивную историю.
|
return `You are StorytellerGPT, running an interactive story.
|
||||||
|
|
||||||
=== МЕТАДАННЫЕ ===
|
=== METADATA ===
|
||||||
ЯЗЫК: ${story.language}
|
LANGUAGE: ${story.language}
|
||||||
ЖАНР: ${story.genre.join(", ")}
|
GENRE: ${story.genre.join(", ")}
|
||||||
СЕТТИНГ: ${settingInfo}
|
SETTING: ${settingInfo}
|
||||||
${nsfwBlock}
|
${nsfwBlock}
|
||||||
=== ПРАВИЛА ПОВЕСТВОВАНИЯ ===
|
=== NARRATIVE RULES ===
|
||||||
1. Игрок сам пишет свои действия и реплики
|
1. Player writes their own actions and dialogue
|
||||||
2. Вплетай действия игрока в сцену, описывай реакции персонажей и последствия
|
2. Weave player actions into the scene, describe character reactions and consequences
|
||||||
3. НИКОГДА не принимай решений за игрока
|
3. NEVER make decisions for the player
|
||||||
4. НИКОГДА не задавай вопросы игроку
|
4. NEVER ask the player questions
|
||||||
5. НИКОГДА не предлагай варианты действий
|
5. NEVER offer action choices
|
||||||
6. НИКОГДА не додумывай намерения игрока
|
6. NEVER assume player intentions
|
||||||
7. Не делай таймскипов без явного указания
|
7. No time skips without explicit indication
|
||||||
8. Если действие не написано игроком — оно НЕ произошло
|
8. If action not written by player — it did NOT happen
|
||||||
|
|
||||||
=== ПРАВИЛА ГЛАВНОГО ГЕРОЯ ===
|
=== PROTAGONIST RULES ===
|
||||||
Ты НЕ ИМЕЕШЬ ПРАВА:
|
You are NOT ALLOWED to:
|
||||||
— описывать физические действия ГГ
|
— describe MC's physical actions
|
||||||
— описывать его внутренние ощущения, эмоции или мысли
|
— describe their internal sensations, emotions, or thoughts
|
||||||
— вкладывать в него реплики или слова
|
— put words or dialogue in their mouth
|
||||||
Пока игрок сам не напишет действие или реплику,
|
Until player writes an action or dialogue,
|
||||||
герой считается неподвижным, молчащим и наблюдающим.
|
the hero is considered motionless, silent, and observing.
|
||||||
|
|
||||||
=== РЕАКЦИИ НА РЕПЛИКИ ГГ ===
|
=== REACTIONS TO MC's DIALOGUE ===
|
||||||
Персонажи обязаны явно реагировать на слова ГГ:
|
Characters MUST explicitly react to MC's words:
|
||||||
— отвечать на заданные вопросы
|
— answer asked questions
|
||||||
— менять тон, поведение или атмосферу
|
— change tone, behavior, or atmosphere
|
||||||
— показывать паузы, напряжение, замешательство
|
— show pauses, tension, confusion
|
||||||
Запрещено игнорировать реплики ГГ.
|
Ignoring MC's dialogue is FORBIDDEN.
|
||||||
|
|
||||||
=== ФОРМАТ ДИАЛОГОВ (ОБЯЗАТЕЛЬНО!) ===
|
=== DIALOGUE FORMAT (MANDATORY!) ===
|
||||||
ВСЕ реплики персонажей оформляй через двойные звёздочки: **"текст реплики"**
|
ALL character dialogue must be formatted with double asterisks: **"dialogue text"**
|
||||||
Пример правильного формата:
|
Correct format example:
|
||||||
Бекк нахмурилась. **"Не доверяю ему,"** — процедила она сквозь зубы.
|
Bekk frowned. **"I don't trust him,"** she muttered through her teeth.
|
||||||
Лапис мягко улыбнулась. **"Всё будет хорошо."**
|
Lapis smiled softly. **"Everything will be alright."**
|
||||||
|
|
||||||
НЕ используй обычные кавычки без звёздочек!
|
DO NOT use regular quotes without asterisks!
|
||||||
Описание действий и окружения — обычным текстом без звёздочек.
|
Descriptions of actions and surroundings — plain text without asterisks.
|
||||||
Отвечай на языке: ${story.language}
|
Respond in language: ${story.language}
|
||||||
|
|
||||||
=== ПЕРСОНАЖ ИГРОКА ===
|
=== PLAYER CHARACTER ===
|
||||||
Имя: ${player?.name || "Герой"}
|
Name: ${player?.name || "Hero"}
|
||||||
Описание: ${playerDescription}`;
|
Description: ${playerDescription}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Строит контекст мира (лор) - КЭШИРУЕТСЯ
|
* Builds world context (lore) - CACHED
|
||||||
*/
|
*/
|
||||||
export function buildWorldContext(story: Story): string {
|
export function buildWorldContext(story: Story): string {
|
||||||
const charactersInfo =
|
const charactersInfo =
|
||||||
@@ -259,46 +259,46 @@ export function buildWorldContext(story: Story): string {
|
|||||||
? story.characters
|
? story.characters
|
||||||
.map((c) => `- ${c.name} (${c.role}): ${c.description}`)
|
.map((c) => `- ${c.name} (${c.role}): ${c.description}`)
|
||||||
.join("\n")
|
.join("\n")
|
||||||
: "Не указаны";
|
: "Not specified";
|
||||||
|
|
||||||
return `
|
return `
|
||||||
=== МИР ===
|
=== WORLD ===
|
||||||
Название: ${story.world.name}
|
Name: ${story.world.name}
|
||||||
Описание: ${story.world.description}
|
Description: ${story.world.description}
|
||||||
Правила мира: ${story.world.rules.join("; ")}
|
World rules: ${story.world.rules.join("; ")}
|
||||||
|
|
||||||
=== ПЕРСОНАЖИ МИРА ===
|
=== WORLD CHARACTERS ===
|
||||||
${charactersInfo}
|
${charactersInfo}
|
||||||
|
|
||||||
=== ОСНОВНОЙ СЮЖЕТ ===
|
=== MAIN PLOT ===
|
||||||
${story.plot}`;
|
${story.plot}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Строит динамический контекст (состояние + сводка)
|
* Builds dynamic context (state + summary)
|
||||||
*/
|
*/
|
||||||
export function buildDynamicContext(session: GameSession): string {
|
export function buildDynamicContext(session: GameSession): string {
|
||||||
const state = session.currentState;
|
const state = session.currentState;
|
||||||
const summary = session.storySummary || "История только началась.";
|
const summary = session.storySummary || "The story just began.";
|
||||||
const keyEvents = session.keyEvents?.length
|
const keyEvents = session.keyEvents?.length
|
||||||
? session.keyEvents.slice(-5).join("\n- ")
|
? session.keyEvents.slice(-5).join("\n- ")
|
||||||
: "Пока нет значимых событий.";
|
: "No significant events yet.";
|
||||||
|
|
||||||
return `
|
return `
|
||||||
=== ТЕКУЩЕЕ СОСТОЯНИЕ ===
|
=== CURRENT STATE ===
|
||||||
Локация: ${state.location}
|
Location: ${state.location}
|
||||||
Здоровье: ${state.health}%
|
Health: ${state.health}%
|
||||||
Инвентарь: ${state.inventory.length > 0 ? state.inventory.join(", ") : "Пусто"}
|
Inventory: ${state.inventory.length > 0 ? state.inventory.join(", ") : "Empty"}
|
||||||
|
|
||||||
=== СВОДКА ИСТОРИИ ===
|
=== STORY SUMMARY ===
|
||||||
${summary}
|
${summary}
|
||||||
|
|
||||||
=== КЛЮЧЕВЫЕ СОБЫТИЯ ===
|
=== KEY EVENTS ===
|
||||||
- ${keyEvents}`;
|
- ${keyEvents}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Полный системный промпт (для обратной совместимости)
|
* Full system prompt (for backwards compatibility)
|
||||||
*/
|
*/
|
||||||
export function buildSystemPrompt(
|
export function buildSystemPrompt(
|
||||||
story: Story,
|
story: Story,
|
||||||
@@ -308,7 +308,7 @@ export function buildSystemPrompt(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Генерирует ответ с оптимизированным контекстом
|
* Generates response with optimized context
|
||||||
*/
|
*/
|
||||||
export async function generateStoryResponse(
|
export async function generateStoryResponse(
|
||||||
story: Story,
|
story: Story,
|
||||||
@@ -317,19 +317,19 @@ export async function generateStoryResponse(
|
|||||||
player?: PlayerCharacter,
|
player?: PlayerCharacter,
|
||||||
session?: GameSession,
|
session?: GameSession,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// 1. Правила стиля (кэшируется DeepSeek)
|
// 1. Style rules (cached by DeepSeek)
|
||||||
const styleRules = buildStyleRules(story, player);
|
const styleRules = buildStyleRules(story, player);
|
||||||
|
|
||||||
// 2. Контекст мира (кэшируется DeepSeek)
|
// 2. World context (cached by DeepSeek)
|
||||||
const worldContext = buildWorldContext(story);
|
const worldContext = buildWorldContext(story);
|
||||||
|
|
||||||
// 3. Динамический контекст (состояние + сводка)
|
// 3. Dynamic context (state + summary)
|
||||||
const dynamicContext = session ? buildDynamicContext(session) : "";
|
const dynamicContext = session ? buildDynamicContext(session) : "";
|
||||||
|
|
||||||
// 4. Последние N сообщений (не вся история!)
|
// 4. Last N messages (not the full history!)
|
||||||
const recentMessages = chatHistory.slice(-RECENT_MESSAGES_COUNT);
|
const recentMessages = chatHistory.slice(-RECENT_MESSAGES_COUNT);
|
||||||
|
|
||||||
// Собираем финальный системный промпт
|
// Build final system prompt
|
||||||
const systemPrompt = styleRules + "\n" + worldContext + "\n" + dynamicContext;
|
const systemPrompt = styleRules + "\n" + worldContext + "\n" + dynamicContext;
|
||||||
|
|
||||||
const messages: DeepSeekMessage[] = [
|
const messages: DeepSeekMessage[] = [
|
||||||
@@ -341,12 +341,12 @@ export async function generateStoryResponse(
|
|||||||
{ role: "user", content: userMessage },
|
{ role: "user", content: userMessage },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Используем temperature из настроек истории (по умолчанию 1.3)
|
// Use temperature from story settings (default 1.3)
|
||||||
return sendMessage(messages, story.temperature || 1.3);
|
return sendMessage(messages, story.temperature || 1.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Streaming версия generateStoryResponse
|
* Streaming version of generateStoryResponse
|
||||||
*/
|
*/
|
||||||
export async function generateStoryResponseStream(
|
export async function generateStoryResponseStream(
|
||||||
story: Story,
|
story: Story,
|
||||||
@@ -376,14 +376,14 @@ export async function generateStoryResponseStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Генерирует сводку истории (вызывать периодически)
|
* Generates story summary (call periodically)
|
||||||
*/
|
*/
|
||||||
export async function generateStorySummary(
|
export async function generateStorySummary(
|
||||||
story: Story,
|
story: Story,
|
||||||
messages: ChatMessage[],
|
messages: ChatMessage[],
|
||||||
previousSummary?: string,
|
previousSummary?: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// Берём сообщения для суммаризации (исключая последние, они свежие)
|
// Get messages for summarization (excluding recent ones, they're fresh)
|
||||||
const messagesToSummarize = messages.slice(0, -RECENT_MESSAGES_COUNT);
|
const messagesToSummarize = messages.slice(0, -RECENT_MESSAGES_COUNT);
|
||||||
|
|
||||||
if (messagesToSummarize.length < SUMMARY_THRESHOLD) {
|
if (messagesToSummarize.length < SUMMARY_THRESHOLD) {
|
||||||
@@ -391,29 +391,62 @@ export async function generateStorySummary(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const conversationText = messagesToSummarize
|
const conversationText = messagesToSummarize
|
||||||
.map((m) => `${m.role === "user" ? "Игрок" : "Рассказчик"}: ${m.content}`)
|
.map((m) => `${m.role === "user" ? "Player" : "Narrator"}: ${m.content}`)
|
||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
|
|
||||||
const prompt = previousSummary
|
const characterTemplate = `
|
||||||
? `Обнови сводку истории, добавив новые события.
|
=== [CHARACTER NAME] ===
|
||||||
|
- Appearance (brief):
|
||||||
|
- Personality and speech style:
|
||||||
|
- Promises made (brief):
|
||||||
|
- Current location:
|
||||||
|
- Alive (yes/no/unknown):
|
||||||
|
- How they address MC (formal/informal):
|
||||||
|
- Attitude towards MC:
|
||||||
|
- Romantic relationship with MC:
|
||||||
|
- What character ALREADY said or did (brief):
|
||||||
|
- What character KNOWS and DOESN'T KNOW (brief):`;
|
||||||
|
|
||||||
ПРЕДЫДУЩАЯ СВОДКА:
|
const prompt = previousSummary
|
||||||
|
? `Update the story summary with new events.
|
||||||
|
|
||||||
|
PREVIOUS SUMMARY:
|
||||||
${previousSummary}
|
${previousSummary}
|
||||||
|
|
||||||
НОВЫЕ СОБЫТИЯ:
|
NEW EVENTS:
|
||||||
${conversationText}
|
${conversationText}
|
||||||
|
|
||||||
Напиши обновлённую сводку (3-5 предложений), сохраняя ключевые моменты:`
|
Write an updated summary in the following format:
|
||||||
: `Создай краткую сводку событий этой истории (3-5 предложений):
|
|
||||||
|
=== GENERAL SUMMARY ===
|
||||||
|
Briefly (3-4 sentences): key events, hero's location, important decisions.
|
||||||
|
|
||||||
|
=== CHARACTER CARDS ===
|
||||||
|
For EACH character that appeared in the story, fill out a card:
|
||||||
|
${characterTemplate}
|
||||||
|
|
||||||
|
Update character info based on new events. Maintain consistency.
|
||||||
|
Write the summary in language: ${story.language}`
|
||||||
|
: `Create a summary of this story's events:
|
||||||
|
|
||||||
${conversationText}
|
${conversationText}
|
||||||
|
|
||||||
Сводка должна содержать: что произошло, где находится герой, какие важные решения принял:`;
|
Write the summary in the following format:
|
||||||
|
|
||||||
|
=== GENERAL SUMMARY ===
|
||||||
|
Briefly (3-4 sentences): what happened, hero's location, important decisions.
|
||||||
|
|
||||||
|
=== CHARACTER CARDS ===
|
||||||
|
For EACH character that appeared in the story, fill out a card:
|
||||||
|
${characterTemplate}
|
||||||
|
|
||||||
|
If info is missing — write "unknown" or skip the field.
|
||||||
|
Write the summary in language: ${story.language}`;
|
||||||
|
|
||||||
const summaryMessages: DeepSeekMessage[] = [
|
const summaryMessages: DeepSeekMessage[] = [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
content: `Ты - помощник для создания сводок историй. Пиши кратко и по существу на ${story.language}.`,
|
content: `You are a story summary assistant. Write concisely and to the point. Pay special attention to romantic storylines and character relationships — this is important for story consistency. Fill character cards only based on what actually happened in the story. Output in language: ${story.language}`,
|
||||||
},
|
},
|
||||||
{ role: "user", content: prompt },
|
{ role: "user", content: prompt },
|
||||||
];
|
];
|
||||||
@@ -422,17 +455,20 @@ ${conversationText}
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Извлекает ключевые события из ответа AI
|
* Extracts key events from AI response
|
||||||
*/
|
*/
|
||||||
export async function extractKeyEvents(
|
export async function extractKeyEvents(
|
||||||
aiResponse: string,
|
aiResponse: string,
|
||||||
existingEvents: string[] = [],
|
existingEvents: string[] = [],
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
// Простая эвристика: ищем важные действия
|
// Simple heuristics: look for important actions
|
||||||
const importantPatterns = [
|
const importantPatterns = [
|
||||||
/(?:ты |вы )(?:получил|нашёл|победил|убил|спас|встретил|узнал|открыл|достиг)/gi,
|
/(?:ты |you )(?:получил|found|defeated|killed|saved|met|learned|discovered|reached)/gi,
|
||||||
/(?:новый |новая |новое )(?:квест|задание|способность|предмет|союзник)/gi,
|
/(?:new |новый |новая |новое )(?:quest|task|ability|item|ally|квест|задание|способность|предмет|союзник)/gi,
|
||||||
/(?:умер|погиб|потерял|предал)/gi,
|
/(?:died|perished|lost|betrayed|умер|погиб|потерял|предал)/gi,
|
||||||
|
// Romantic events
|
||||||
|
/(?:kiss|embrace|confess|love|flirt|date|romantic|поцелу|обнял|признал|влюбил|призналась|призналось|флирт|свидание|романтич)/gi,
|
||||||
|
/(?:held|took|squeezed|held hands|держ|взял|сжал).*(?:hand|palm|руку|ладонь|за руку)/gi,
|
||||||
];
|
];
|
||||||
|
|
||||||
const newEvents: string[] = [];
|
const newEvents: string[] = [];
|
||||||
@@ -440,7 +476,7 @@ export async function extractKeyEvents(
|
|||||||
for (const pattern of importantPatterns) {
|
for (const pattern of importantPatterns) {
|
||||||
const matches = aiResponse.match(pattern);
|
const matches = aiResponse.match(pattern);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
// Извлекаем предложение с событием
|
// Extract sentence with the event
|
||||||
const sentences = aiResponse.split(/[.!?]/);
|
const sentences = aiResponse.split(/[.!?]/);
|
||||||
for (const sentence of sentences) {
|
for (const sentence of sentences) {
|
||||||
if (
|
if (
|
||||||
@@ -455,7 +491,7 @@ export async function extractKeyEvents(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Объединяем с существующими, ограничиваем до 10
|
// Combine with existing, limit to 10
|
||||||
const allEvents = [...existingEvents, ...newEvents];
|
const allEvents = [...existingEvents, ...newEvents];
|
||||||
return allEvents.slice(-10);
|
return allEvents.slice(-10);
|
||||||
}
|
}
|
||||||
@@ -470,17 +506,17 @@ export async function generateStoryDescription(
|
|||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
content:
|
content:
|
||||||
"Ты - писатель исекай историй. Создавай краткие, захватывающие описания для историй.",
|
"You are an isekai story writer. Create brief, captivating descriptions for stories.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: `Создай краткое описание (2-3 предложения) для исекай истории:
|
content: `Create a brief description (2-3 sentences) for an isekai story:
|
||||||
Название: ${title}
|
Title: ${title}
|
||||||
Жанр: ${genre.join(", ")}
|
Genre: ${genre.join(", ")}
|
||||||
Сеттинг: ${setting.join(", ")}
|
Setting: ${setting.join(", ")}
|
||||||
Мир: ${worldDescription}
|
World: ${worldDescription}
|
||||||
|
|
||||||
Описание должно быть интригующим и заставлять хотеть начать приключение.`,
|
The description should be intriguing and make the reader want to start the adventure. Write in Russian.`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user