PLUTON\igor.kreyda commited on
Commit
66e3a81
·
0 Parent(s):

Initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ node_modules/
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Testing
7
+ coverage/
8
+
9
+ # Production
10
+ build/
11
+ dist/
12
+
13
+ # Misc
14
+ .DS_Store
15
+ .env
16
+ .env.local
17
+ .env.development.local
18
+ .env.test.local
19
+ .env.production.local
20
+
21
+ # Logs
22
+ npm-debug.log*
23
+ yarn-debug.log*
24
+ yarn-error.log*
25
+
26
+ # Editor directories and files
27
+ .idea
28
+ .vscode
29
+ *.swp
30
+ *.swo
app/bot/actions.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Bot Action Handlers
3
+ * Handles specific bot commands and logic to keep bot.js clean.
4
+ */
5
+
6
+ const { getUserSession, setAwaitingEdit } = require('./userSession');
7
+
8
+ /**
9
+ * Handles the /edit_story command.
10
+ * @param {Object} ctx - Telegram context.
11
+ */
12
+ async function handleEditCommand(ctx) {
13
+ const userId = ctx.from.id;
14
+ const session = getUserSession(userId);
15
+
16
+ if (!session.lastPrompt || !session.lastStory) {
17
+ console.log(`[Actions] User ${userId} tried to edit without history.`);
18
+ return ctx.reply("Сначала сгенерируйте историю. Просто отправьте мне тему для поста.");
19
+ }
20
+
21
+ // Set state: user is now expected to send edit instructions
22
+ setAwaitingEdit(userId, true);
23
+
24
+ await ctx.reply("Что исправить?");
25
+ }
26
+
27
+ /**
28
+ * Handles the /edit_image command.
29
+ * @param {Object} ctx - Telegram context.
30
+ */
31
+ async function handleEditImageCommand(ctx) {
32
+ const userId = ctx.from.id;
33
+ const session = getUserSession(userId);
34
+
35
+ // Check if we have a last image to edit
36
+ if (!session.lastImage) {
37
+ console.log(`[Actions] User ${userId} tried to edit image without history.`);
38
+ return ctx.reply("Сначала сгенерируйте изображение.");
39
+ }
40
+
41
+ const { setAwaitingImageEdit } = require('./userSession');
42
+ setAwaitingImageEdit(userId, true);
43
+
44
+ await ctx.reply("Опишите, как изменить изображение?");
45
+ }
46
+
47
+ module.exports = {
48
+ handleEditCommand,
49
+ handleEditImageCommand
50
+ };
app/bot/bot.js ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Main Bot Logic Module
3
+ * Initializes Telegraf bot and defines commands/events.
4
+ */
5
+
6
+ const { Telegraf, Markup } = require('telegraf');
7
+
8
+ // This function will be called from index.js
9
+ function setupBot(token, webAppUrl) {
10
+ if (!token) {
11
+ throw new Error('BOT_TOKEN is missing!');
12
+ }
13
+
14
+ const bot = new Telegraf(token);
15
+
16
+ // Initial setup for the menu button
17
+ // Note: setChatMenuButton might need to be called on specific user interaction or globally once logic is decided.
18
+ // For now, let's put it in the start command to ensure it updates for the user.
19
+
20
+ bot.start(async (ctx) => {
21
+ const welcomeMessage = `
22
+ Привет! Я AI Post Generator Bot. 🤖
23
+
24
+ Я помогу тебе создавать уникальные посты и изображения.
25
+ Нажми кнопку ниже (или "Меню" -> "Настройки"), чтобы настроить параметры генерации.
26
+ `;
27
+
28
+ try {
29
+ // Set the persistent Menu Button (bottom left of chat input)
30
+ // This button opens the Web App directly.
31
+ await ctx.setChatMenuButton({
32
+ type: 'web_app',
33
+ text: 'Настройки',
34
+ web_app: { url: `${webAppUrl}/app/index.html` }
35
+ });
36
+ console.log('Menu button set for user:', ctx.from.id);
37
+ } catch (e) {
38
+ console.error('Failed to set menu button:', e);
39
+ }
40
+
41
+ // Send a message with an inline button as well
42
+ await ctx.reply(welcomeMessage, Markup.inlineKeyboard([
43
+ Markup.button.webApp('Открыть настройки ⚙️', `${webAppUrl}/app/index.html`)
44
+ ]));
45
+ });
46
+
47
+ // Imports for logic
48
+ const { buildStoryPrompt, buildImagePrompt, buildEditPrompt, buildImageRegenerationPrompt } = require('../../prompts/promptBuilder');
49
+ const { generateText } = require('../../services/llmService');
50
+ const { generateImage, regenerateImage } = require('../../services/imageService');
51
+ const { storySystemPrompt } = require('../../prompts/storySystemPrompt');
52
+ const { stylePresets } = require('../../prompts/stylePresets');
53
+ const { getCurrentPreset, getUserSession, updateLastGeneration, updateLastImage, setAwaitingEdit, setAwaitingImageEdit } = require('./userSession');
54
+ const { handleEditCommand, handleEditImageCommand } = require('./actions');
55
+
56
+ bot.help((ctx) => ctx.reply('Send /start to open the settings app.'));
57
+
58
+ // Command Handlers
59
+ bot.command('edit_story', handleEditCommand);
60
+ bot.command('edit_image', handleEditImageCommand);
61
+
62
+ // Text Message Handler
63
+ bot.on('text', async (ctx) => {
64
+ const userMessage = ctx.message.text;
65
+
66
+ // Ignore commands
67
+ if (userMessage.startsWith('/')) return;
68
+
69
+ try {
70
+ // Show typing status
71
+ await ctx.sendChatAction('typing');
72
+
73
+ // 1. Get Settings from session
74
+ const userId = ctx.from.id;
75
+ const session = getUserSession(userId);
76
+ const selectedPreset = getCurrentPreset(userId);
77
+
78
+ console.log(`[Bot] Active Preset: ${selectedPreset.preset_name}, Awaiting Edit: ${session.awaitingEdit}, Awaiting Image Edit: ${session.awaitingImageEdit}`);
79
+
80
+ // --- 2a. Check for Image Edit State ---
81
+ if (session.awaitingImageEdit && session.lastImage) {
82
+ console.log(`[Bot] Processing IMAGE EDIT request.`);
83
+ const regenPrompt = buildImageRegenerationPrompt(userMessage);
84
+
85
+ // Regenerate
86
+ const newBase64 = await regenerateImage(regenPrompt, session.lastImage);
87
+
88
+ // Send and Save
89
+ await ctx.replyWithPhoto({ source: Buffer.from(newBase64, 'base64') });
90
+ updateLastImage(userId, newBase64);
91
+ setAwaitingImageEdit(userId, false);
92
+ return; // Stop here, don't generate story
93
+ }
94
+
95
+ let currentPrompt = "";
96
+
97
+ // 2. Build Prompt based on state (Text Edit)
98
+ if (session.awaitingEdit && session.lastPrompt && session.lastStory) {
99
+ console.log(`[Bot] Processing TEXT EDIT request from user ${userId}`);
100
+ currentPrompt = buildEditPrompt(userMessage, session.lastPrompt, session.lastStory);
101
+ } else {
102
+ currentPrompt = buildStoryPrompt(
103
+ userMessage,
104
+ storySystemPrompt,
105
+ selectedPreset
106
+ );
107
+ }
108
+
109
+ // 3. Call LLM Service for Story
110
+ let responseText = await generateText(currentPrompt);
111
+
112
+ // --- Tags Logic ---
113
+ const userTagsString = session.tags || "";
114
+ // Split user tags by space/newline, filter empty, ensure they start with #
115
+ const userTagsArray = userTagsString.split(/[\s\n]+/).filter(t => t.startsWith('#'));
116
+
117
+ // Regex to capture hashtags at the end of the text
118
+ const tagRegex = /((?:#[\w\u0590-\u05ff]+(?:\s+|$))+)$/u;
119
+
120
+ let storyText = responseText.trim();
121
+ const llmTagsArray = [];
122
+
123
+ const match = storyText.match(tagRegex);
124
+ if (match) {
125
+ const tagsPart = match[1];
126
+ storyText = storyText.substring(0, storyText.length - tagsPart.length).trim();
127
+ const extracted = tagsPart.match(/#[\w\u0590-\u05ff]+/gu);
128
+ if (extracted) {
129
+ llmTagsArray.push(...extracted);
130
+ }
131
+ }
132
+
133
+ // Merge unique tags
134
+ const allTags = [...new Set([...llmTagsArray, ...userTagsArray])];
135
+
136
+ // Format: Story text + double newline + all tags in one line
137
+ const finalMessage = `${storyText}\n\n${allTags.join(' ')}`;
138
+
139
+ // 4. Send Story Response to user
140
+ await ctx.reply(finalMessage);
141
+
142
+ // --- Save History and Reset State ---
143
+ updateLastGeneration(userId, currentPrompt, storyText);
144
+ setAwaitingEdit(userId, false);
145
+
146
+
147
+ // 5. Generate Image Prompt based on the Story
148
+ await ctx.sendChatAction('upload_photo');
149
+
150
+ // Keep "upload_photo" status alive
151
+ const statusInterval = setInterval(() => {
152
+ ctx.sendChatAction('upload_photo').catch(e => console.error('[Bot] Action error:', e));
153
+ }, 4000);
154
+
155
+ try {
156
+ // If this was an edit, we might want to regenerate image for the new story OR just keep it.
157
+ // Current workflow: Always generate new image for new story/edited story.
158
+
159
+ // If it's a Text Edit, we probably want a new image too?
160
+ // Let's assume yes for now, or we can make it optional.
161
+ // The user requested to uncomment the block, implying we want images back.
162
+
163
+ const imagePromptLayout = buildImagePrompt(
164
+ responseText,
165
+ selectedPreset.image_style_suffix
166
+ );
167
+
168
+ // Get refined prompt from LLM
169
+ const refinedImagePrompt = await generateText(imagePromptLayout);
170
+ console.log('[Bot] Refined Image Prompt:', refinedImagePrompt);
171
+
172
+ // 6. Generate Image
173
+ const base64Image = await generateImage(refinedImagePrompt);
174
+
175
+ // 7. Send Photo
176
+ await ctx.replyWithPhoto({ source: Buffer.from(base64Image, 'base64') });
177
+
178
+ // Save for future edits
179
+ updateLastImage(userId, base64Image);
180
+
181
+ } finally {
182
+ clearInterval(statusInterval);
183
+ }
184
+
185
+
186
+ } catch (error) {
187
+ console.error('Error handling message:', error);
188
+ await ctx.reply('Произошла ошибка при генерации ответа. Попробуйте позже.');
189
+ }
190
+ });
191
+
192
+ return bot;
193
+ }
194
+
195
+ module.exports = { setupBot };
app/bot/userSession.js ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * User Session Store
3
+ * Simple in-memory storage for user settings (active preset, etc).
4
+ * In a real app, this should be a database (Postgres, MongoDB).
5
+ */
6
+
7
+ const userSessions = {};
8
+ const { stylePresets } = require('../../prompts/stylePresets');
9
+
10
+ // Default preset (usually the first one)
11
+ const DEFAULT_PRESET_NAME = stylePresets[0].preset_name;
12
+
13
+ /**
14
+ * Returns the user session or initializes it with default values.
15
+ * @param {number|string} userId
16
+ */
17
+ function getUserSession(userId) {
18
+ if (!userSessions[userId]) {
19
+ userSessions[userId] = {
20
+ currentPreset: DEFAULT_PRESET_NAME,
21
+ creativity: 0.7,
22
+ tags: "#fblifestyle #kindnessmatters #kindness",
23
+ lastPrompt: null,
24
+ lastStory: null,
25
+ lastImage: null,
26
+ awaitingEdit: false,
27
+ awaitingImageEdit: false
28
+ };
29
+ }
30
+ return userSessions[userId];
31
+ }
32
+
33
+ /**
34
+ * Updates various user settings in the session.
35
+ */
36
+ function updateUserSession(userId, data) {
37
+ const session = getUserSession(userId);
38
+
39
+ // Update Preset if provided
40
+ if (data.presetName) {
41
+ const preset = stylePresets.find(p => p.preset_name === data.presetName);
42
+ if (preset) {
43
+ session.currentPreset = data.presetName;
44
+ }
45
+ }
46
+
47
+ // Update Creativity if provided
48
+ if (data.creativity !== undefined) {
49
+ session.creativity = parseFloat(data.creativity);
50
+ }
51
+
52
+ // Update Tags if provided
53
+ if (data.tags !== undefined) {
54
+ session.tags = data.tags;
55
+ }
56
+
57
+ return true;
58
+ }
59
+
60
+ /**
61
+ * Saves the last generation context for editing.
62
+ */
63
+ function updateLastGeneration(userId, prompt, story) {
64
+ const session = getUserSession(userId);
65
+ session.lastPrompt = prompt;
66
+ session.lastStory = story;
67
+ return true;
68
+ }
69
+
70
+ /**
71
+ * Saves the last generated image.
72
+ */
73
+ function updateLastImage(userId, imageBase64) {
74
+ const session = getUserSession(userId);
75
+ session.lastImage = imageBase64;
76
+ return true;
77
+ }
78
+
79
+ /**
80
+ * Sets the "awaiting edit message" state.
81
+ */
82
+ function setAwaitingEdit(userId, value) {
83
+ const session = getUserSession(userId);
84
+ session.awaitingEdit = !!value;
85
+ }
86
+
87
+ function setAwaitingImageEdit(userId, value) {
88
+ const session = getUserSession(userId);
89
+ session.awaitingImageEdit = !!value;
90
+ }
91
+
92
+ /**
93
+ * Returns the full preset object for the user's current choice.
94
+ */
95
+ function getCurrentPreset(userId) {
96
+ const session = getUserSession(userId);
97
+ return stylePresets.find(p => p.preset_name === session.currentPreset) || stylePresets[0];
98
+ }
99
+
100
+ module.exports = {
101
+ getUserSession,
102
+ updateUserSession,
103
+ getCurrentPreset,
104
+ updateLastGeneration,
105
+ updateLastImage,
106
+ setAwaitingEdit,
107
+ setAwaitingImageEdit
108
+ };
app/miniApp/app.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Main Mini App Logic
3
+ * Handles tab switching and initialization of sub-components.
4
+ */
5
+
6
+ document.addEventListener('DOMContentLoaded', () => {
7
+ // Initialize Tabs
8
+ const tabs = document.querySelectorAll('.nav-item');
9
+ const contents = document.querySelectorAll('.tab-content');
10
+
11
+ tabs.forEach(tab => {
12
+ tab.addEventListener('click', () => {
13
+ // Remove active class from all
14
+ tabs.forEach(t => t.classList.remove('active'));
15
+ contents.forEach(c => c.classList.remove('active'));
16
+
17
+ // Add active class to clicked
18
+ tab.classList.add('active');
19
+
20
+ // Show corresponding content
21
+ const targetId = tab.dataset.tab;
22
+ document.getElementById(targetId).classList.add('active');
23
+ });
24
+ });
25
+
26
+ // Initialize individual tab logic
27
+ if (window.settingsTab) window.settingsTab.init();
28
+ if (window.storageTab) window.storageTab.init();
29
+
30
+ // Telegram WebApp expansion
31
+ if (window.Telegram && window.Telegram.WebApp) {
32
+ window.Telegram.WebApp.ready();
33
+ window.Telegram.WebApp.expand();
34
+ }
35
+ });
app/miniApp/index.html ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1">
7
+ <title>Configurator</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="styles.css">
10
+ <!-- Telegram Web App Script -->
11
+ <script src="https://telegram.org/js/telegram-web-app.js"></script>
12
+ </head>
13
+
14
+ <body>
15
+
16
+ <div class="main-content">
17
+ <!-- Settings Tab Content (Default) -->
18
+ <div id="settings-tab" class="tab-content active">
19
+ <h2>Настройки</h2>
20
+
21
+ <div class="form-group">
22
+ <label for="tags-input">Теги (разделяйте пробелом)</label>
23
+ <textarea id="tags-input" class="tags-box"
24
+ placeholder="#tag1 #tag2">#fblifestyle #kindnessmatters #kindness</textarea>
25
+ </div>
26
+
27
+ <div class="form-group">
28
+ <label for="creativity">Креативность (Температура)</label>
29
+ <input type="range" id="creativity" min="0" max="1" step="0.1" value="0.7">
30
+ <div class="slider-value" id="creativity-val">0.7</div>
31
+ </div>
32
+
33
+ <div class="form-group">
34
+ <label for="preset-select">Пресет</label>
35
+ <select id="preset-select">
36
+ <!-- Options populated by JS -->
37
+ </select>
38
+ </div>
39
+
40
+ <div class="form-group">
41
+ <label for="preset-description">Описание пресета</label>
42
+ <textarea id="preset-description" class="description-box" readonly></textarea>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Storage Tab Content -->
47
+ <div id="storage-tab" class="tab-content">
48
+ <h2>Хранилище</h2>
49
+ <div class="form-group">
50
+ <p style="color: #888; text-align: center;">Здесь будут сохраненные материалы.</p>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Bottom Navigation -->
56
+ <nav class="bottom-nav">
57
+ <div class="nav-item active" data-tab="settings-tab">
58
+ <svg class="nav-icon" viewBox="0 0 24 24">
59
+ <path
60
+ d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.85,9.49l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z" />
61
+ </svg>
62
+ <span class="nav-label">Настройки</span>
63
+ </div>
64
+ <div class="nav-item" data-tab="storage-tab">
65
+ <svg class="nav-icon" viewBox="0 0 24 24">
66
+ <path
67
+ d="M20,6h-8l-2-2H4C2.9,4,2.01,4.9,2.01,6L2,18c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M14,16H6v-2h8V16z M18,12H6v-2h12V12z" />
68
+ </svg>
69
+ <span class="nav-label">Хранилище</span>
70
+ </div>
71
+ </nav>
72
+
73
+ <!-- Shared Data -->
74
+ <script src="../../prompts/stylePresets.js"></script>
75
+
76
+ <script src="settings.js"></script>
77
+ <script src="storage.js"></script>
78
+ <script src="app.js"></script>
79
+ </body>
80
+
81
+ </html>
app/miniApp/settings.js ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Settings Tab Logic
3
+ * Handles interaction within the Settings tab.
4
+ */
5
+
6
+ const settingsTab = {
7
+ init: function () {
8
+ console.log("Settings Tab Initialized");
9
+ this.cacheDOM();
10
+ this.bindEvents();
11
+ this.loadPresets();
12
+ },
13
+
14
+ cacheDOM: function () {
15
+ this.creativitySlider = document.getElementById('creativity');
16
+ this.creativityValue = document.getElementById('creativity-val');
17
+ this.presetSelect = document.getElementById('preset-select');
18
+ this.presetDesc = document.getElementById('preset-description');
19
+ this.tagsInput = document.getElementById('tags-input');
20
+ },
21
+
22
+ bindEvents: function () {
23
+ if (this.creativitySlider) {
24
+ this.creativitySlider.addEventListener('input', (e) => {
25
+ this.creativityValue.textContent = e.target.value;
26
+ // Optional: Save on change
27
+ // this.saveSettings();
28
+ });
29
+ this.creativitySlider.addEventListener('change', () => this.saveSettings());
30
+ }
31
+
32
+ if (this.presetSelect) {
33
+ this.presetSelect.addEventListener('change', (e) => {
34
+ this.updateDescription(e.target.value);
35
+ // updateDescription calls saveSettings, so we are good.
36
+ });
37
+ }
38
+
39
+ if (this.tagsInput) {
40
+ this.tagsInput.addEventListener('change', () => {
41
+ this.saveSettings();
42
+ });
43
+ // Also save on blur to be safe
44
+ this.tagsInput.addEventListener('blur', () => {
45
+ this.saveSettings();
46
+ });
47
+ }
48
+ },
49
+
50
+ loadPresets: function () {
51
+ // Load presets from the shared global variable (loaded via script tag)
52
+ if (typeof window.stylePresets === 'undefined') {
53
+ console.error("Error: stylePresets not loaded. Ensure stylePresets.js is included.");
54
+ this.presetSelect.innerHTML = '<option disabled>Presets not available</option>';
55
+ return;
56
+ }
57
+
58
+ const presets = window.stylePresets;
59
+
60
+ // Clear existing options
61
+ this.presetSelect.innerHTML = '';
62
+
63
+ presets.forEach(preset => {
64
+ const option = document.createElement('option');
65
+ // Using preset_name as value.
66
+ option.value = preset.preset_name;
67
+ option.textContent = preset.preset_name.replace(/_/g, ' '); // Beautify display name
68
+ option.dataset.description = preset.description;
69
+ this.presetSelect.appendChild(option);
70
+ });
71
+
72
+ // Trigger change to set initial description
73
+ if (presets.length > 0) {
74
+ this.updateDescription(presets[0].preset_name);
75
+ }
76
+ },
77
+
78
+ updateDescription: function (presetId) {
79
+ const selectedOption = this.presetSelect.querySelector(`option[value="${presetId}"]`);
80
+ if (selectedOption) {
81
+ this.presetDesc.value = selectedOption.dataset.description;
82
+ this.saveSettings();
83
+ } else {
84
+ this.presetDesc.value = "";
85
+ }
86
+ },
87
+
88
+ saveSettings: function () {
89
+ // Get user ID from Telegram WebApp
90
+ if (!window.Telegram || !window.Telegram.WebApp || !window.Telegram.WebApp.initDataUnsafe.user) {
91
+ console.warn("Settings not saved: Not running inside Telegram or user info missing.");
92
+ return;
93
+ }
94
+
95
+ const userId = window.Telegram.WebApp.initDataUnsafe.user.id;
96
+
97
+ // Gather all settings from DOM
98
+ const presetName = this.presetSelect.value;
99
+ const creativity = this.creativitySlider.value;
100
+ const tags = this.tagsInput.value;
101
+
102
+ console.log(`Saving settings for user ${userId}: Preset=${presetName}, Creativity=${creativity}, Tags=${tags}`);
103
+
104
+ fetch('/api/settings', {
105
+ method: 'POST',
106
+ headers: {
107
+ 'Content-Type': 'application/json'
108
+ },
109
+ body: JSON.stringify({
110
+ userId,
111
+ presetName,
112
+ creativity,
113
+ tags
114
+ })
115
+ })
116
+ .then(response => response.json())
117
+ .then(data => {
118
+ if (data.success) {
119
+ console.log("Settings saved successfully");
120
+ } else {
121
+ console.error("Failed to save settings:", data.error);
122
+ }
123
+ })
124
+ .catch(err => {
125
+ console.error("Error saving settings:", err);
126
+ });
127
+ }
128
+ };
129
+
130
+ // Export for use in app.js if using modules, or just global if simple script inclusion.
131
+ // For simple usage without bundlers, we can attach to window or just leave it available.
132
+ window.settingsTab = settingsTab;
app/miniApp/storage.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Storage Tab Logic
3
+ * Handles interaction within the Storage tab.
4
+ */
5
+
6
+ const storageTab = {
7
+ init: function () {
8
+ console.log("Storage Tab Initialized");
9
+ // Logic for loading stored items will go here
10
+ }
11
+ };
12
+
13
+ window.storageTab = storageTab;
app/miniApp/styles.css ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg-color: #121212;
3
+ --surface-color: #1e1e1e;
4
+ --primary-color: #6200ee;
5
+ --primary-variant: #3700b3;
6
+ --secondary-color: #03dac6;
7
+ --text-primary: #ffffff;
8
+ --text-secondary: #b0b0b0;
9
+ --border-radius: 12px;
10
+ --spacing-unit: 16px;
11
+ --bottom-nav-height: 60px;
12
+ }
13
+
14
+ * {
15
+ box-sizing: border-box;
16
+ margin: 0;
17
+ padding: 0;
18
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
19
+ }
20
+
21
+ body {
22
+ background-color: var(--bg-color);
23
+ color: var(--text-primary);
24
+ height: 100vh;
25
+ display: flex;
26
+ flex-direction: column;
27
+ overflow: hidden;
28
+ }
29
+
30
+ /* Header/Content Area */
31
+ .main-content {
32
+ flex: 1;
33
+ overflow-y: auto;
34
+ padding: var(--spacing-unit);
35
+ padding-bottom: calc(var(--bottom-nav-height) + var(--spacing-unit));
36
+ }
37
+
38
+ .tab-content {
39
+ display: none;
40
+ animation: fadeIn 0.3s ease-in-out;
41
+ }
42
+
43
+ .tab-content.active {
44
+ display: block;
45
+ }
46
+
47
+ @keyframes fadeIn {
48
+ from {
49
+ opacity: 0;
50
+ transform: translateY(10px);
51
+ }
52
+
53
+ to {
54
+ opacity: 1;
55
+ transform: translateY(0);
56
+ }
57
+ }
58
+
59
+ h2 {
60
+ margin-bottom: var(--spacing-unit);
61
+ font-weight: 600;
62
+ color: var(--text-primary);
63
+ }
64
+
65
+ /* Form Elements */
66
+ .form-group {
67
+ margin-bottom: 24px;
68
+ background: var(--surface-color);
69
+ padding: 16px;
70
+ border-radius: var(--border-radius);
71
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
72
+ }
73
+
74
+ label {
75
+ display: block;
76
+ margin-bottom: 8px;
77
+ color: var(--text-secondary);
78
+ font-size: 0.9em;
79
+ font-weight: 500;
80
+ }
81
+
82
+ /* Slider */
83
+ input[type=range] {
84
+ width: 100%;
85
+ -webkit-appearance: none;
86
+ appearance: none;
87
+ background: transparent;
88
+ }
89
+
90
+ input[type=range]::-webkit-slider-thumb {
91
+ -webkit-appearance: none;
92
+ height: 20px;
93
+ width: 20px;
94
+ border-radius: 50%;
95
+ background: var(--primary-color);
96
+ cursor: pointer;
97
+ margin-top: -8px;
98
+ box-shadow: 0 0 10px rgba(98, 0, 238, 0.5);
99
+ }
100
+
101
+ input[type=range]::-webkit-slider-runnable-track {
102
+ width: 100%;
103
+ height: 4px;
104
+ cursor: pointer;
105
+ background: #444;
106
+ border-radius: 2px;
107
+ }
108
+
109
+ input[type=range]:focus {
110
+ outline: none;
111
+ }
112
+
113
+ .slider-value {
114
+ text-align: right;
115
+ font-size: 0.85em;
116
+ color: var(--primary-color);
117
+ margin-top: 4px;
118
+ }
119
+
120
+ /* Select */
121
+ select {
122
+ width: 100%;
123
+ padding: 12px;
124
+ background-color: #2c2c2c;
125
+ color: var(--text-primary);
126
+ border: 1px solid #444;
127
+ border-radius: 8px;
128
+ font-size: 1em;
129
+ appearance: none;
130
+ background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
131
+ background-repeat: no-repeat;
132
+ background-position: right 1rem center;
133
+ background-size: 1em;
134
+ }
135
+
136
+ select:focus {
137
+ outline: none;
138
+ border-color: var(--primary-color);
139
+ }
140
+
141
+ /* Description Box */
142
+ .description-box {
143
+ width: 100%;
144
+ min-height: 100px;
145
+ padding: 12px;
146
+ background-color: #2c2c2c;
147
+ color: #ddd;
148
+ border: 1px solid #444;
149
+ border-radius: 8px;
150
+ font-size: 0.95em;
151
+ line-height: 1.5;
152
+ resize: none;
153
+ }
154
+
155
+ /* Tags Box */
156
+ .tags-box {
157
+ width: 100%;
158
+ min-height: 80px;
159
+ padding: 12px;
160
+ background-color: #2c2c2c;
161
+ color: #ddd;
162
+ border: 1px solid #444;
163
+ border-radius: 8px;
164
+ font-size: 0.95em;
165
+ line-height: 1.5;
166
+ resize: none;
167
+ font-family: monospace;
168
+ /* To make tags look code-like */
169
+ }
170
+
171
+ /* Bottom Navigation */
172
+ .bottom-nav {
173
+ position: fixed;
174
+ bottom: 0;
175
+ width: 100%;
176
+ height: var(--bottom-nav-height);
177
+ background-color: var(--surface-color);
178
+ display: flex;
179
+ justify-content: space-around;
180
+ align-items: center;
181
+ border-top: 1px solid #333;
182
+ box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2);
183
+ z-index: 100;
184
+ }
185
+
186
+ .nav-item {
187
+ display: flex;
188
+ flex-direction: column;
189
+ align-items: center;
190
+ justify-content: center;
191
+ color: var(--text-secondary);
192
+ padding: 8px;
193
+ cursor: pointer;
194
+ transition: color 0.2s;
195
+ width: 50%;
196
+ }
197
+
198
+ .nav-item.active {
199
+ color: var(--primary-color);
200
+ }
201
+
202
+ .nav-icon {
203
+ width: 24px;
204
+ height: 24px;
205
+ margin-bottom: 4px;
206
+ fill: currentColor;
207
+ }
208
+
209
+ .nav-label {
210
+ font-size: 0.75em;
211
+ }
index.js ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('dotenv').config();
2
+ const express = require('express');
3
+ const cors = require('cors');
4
+ const path = require('path');
5
+
6
+ // Modular imports
7
+ const { setupBot } = require('./app/bot/bot');
8
+
9
+ const app = express();
10
+ const PORT = process.env.PORT || 3000;
11
+ const WEBAPP_URL = process.env.WEBAPP_URL || 'https://your-domain.com';
12
+
13
+ // --- Express Server Setup ---
14
+ app.use(cors());
15
+ app.use(express.json());
16
+
17
+ // Request logging middleware
18
+ app.use((req, res, next) => {
19
+ console.log(`[Server] ${req.method} ${req.url}`);
20
+ next();
21
+ });
22
+
23
+ // Serve static files for Mini App
24
+ // We map '/app' URL path to the local 'app/miniApp' directory
25
+ app.use('/app', express.static(path.join(__dirname, 'app/miniApp')));
26
+
27
+ // Serve prompts so the Mini App can load stylePresets.js
28
+ // We map '/prompts' URL path to the local 'prompts' directory
29
+ app.use('/prompts', express.static(path.join(__dirname, 'prompts')));
30
+
31
+ // Basic health check
32
+ app.get('/', (req, res) => {
33
+ res.send('Bot and Mini App Server are running.');
34
+ });
35
+
36
+ // --- API Endpoints ---
37
+ const { updateUserSession } = require('./app/bot/userSession');
38
+
39
+ app.post('/api/settings', (req, res) => {
40
+ console.log('[API] Received /api/settings request:', req.body);
41
+ const { userId, presetName, creativity, tags } = req.body;
42
+
43
+ if (!userId) {
44
+ return res.status(400).json({ error: 'Missing userId' });
45
+ }
46
+
47
+ // We pass all fields to the update function
48
+ const success = updateUserSession(userId, { presetName, creativity, tags });
49
+
50
+ if (success) {
51
+ console.log(`[API] Settings updated for user ${userId}`);
52
+ res.json({ success: true });
53
+ } else {
54
+ res.status(500).json({ error: 'Failed to update settings' });
55
+ }
56
+ });
57
+
58
+ // --- Bot Setup ---
59
+ let bot;
60
+ try {
61
+ bot = setupBot(process.env.BOT_TOKEN, WEBAPP_URL);
62
+ } catch (error) {
63
+ console.error("Failed to initialize bot:", error.message);
64
+ process.exit(1);
65
+ }
66
+
67
+ // --- Start Server & Bot ---
68
+ async function start() {
69
+ try {
70
+ // Start Express Server
71
+ app.listen(PORT, () => {
72
+ console.log(`🚀 Server running on port ${PORT}`);
73
+ console.log(`🌍 Web App URL: ${WEBAPP_URL}/app/index.html`);
74
+ });
75
+
76
+ // Start Telegram Bot
77
+ console.log('🤖 Bot is launching...');
78
+ await bot.launch();
79
+ console.log('✅ Bot is running.');
80
+
81
+ } catch (e) {
82
+ console.error('❌ Error starting app:', e);
83
+ }
84
+ }
85
+
86
+ // Graceful stop
87
+ process.once('SIGINT', () => {
88
+ if (bot) bot.stop('SIGINT');
89
+ process.exit(0);
90
+ });
91
+ process.once('SIGTERM', () => {
92
+ if (bot) bot.stop('SIGTERM');
93
+ process.exit(0);
94
+ });
95
+
96
+ start();
package-lock.json ADDED
@@ -0,0 +1,1149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "aipostgenerator",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "aipostgenerator",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "axios": "^1.13.5",
13
+ "cors": "^2.8.6",
14
+ "dotenv": "^17.2.4",
15
+ "express": "^5.2.1",
16
+ "telegraf": "^4.16.3"
17
+ }
18
+ },
19
+ "node_modules/@telegraf/types": {
20
+ "version": "7.1.0",
21
+ "resolved": "https://registry.npmjs.org/@telegraf/types/-/types-7.1.0.tgz",
22
+ "integrity": "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==",
23
+ "license": "MIT"
24
+ },
25
+ "node_modules/abort-controller": {
26
+ "version": "3.0.0",
27
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
28
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "event-target-shim": "^5.0.0"
32
+ },
33
+ "engines": {
34
+ "node": ">=6.5"
35
+ }
36
+ },
37
+ "node_modules/accepts": {
38
+ "version": "2.0.0",
39
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
40
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
41
+ "license": "MIT",
42
+ "dependencies": {
43
+ "mime-types": "^3.0.0",
44
+ "negotiator": "^1.0.0"
45
+ },
46
+ "engines": {
47
+ "node": ">= 0.6"
48
+ }
49
+ },
50
+ "node_modules/asynckit": {
51
+ "version": "0.4.0",
52
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
53
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
54
+ "license": "MIT"
55
+ },
56
+ "node_modules/axios": {
57
+ "version": "1.13.5",
58
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
59
+ "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
60
+ "license": "MIT",
61
+ "dependencies": {
62
+ "follow-redirects": "^1.15.11",
63
+ "form-data": "^4.0.5",
64
+ "proxy-from-env": "^1.1.0"
65
+ }
66
+ },
67
+ "node_modules/body-parser": {
68
+ "version": "2.2.2",
69
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
70
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
71
+ "license": "MIT",
72
+ "dependencies": {
73
+ "bytes": "^3.1.2",
74
+ "content-type": "^1.0.5",
75
+ "debug": "^4.4.3",
76
+ "http-errors": "^2.0.0",
77
+ "iconv-lite": "^0.7.0",
78
+ "on-finished": "^2.4.1",
79
+ "qs": "^6.14.1",
80
+ "raw-body": "^3.0.1",
81
+ "type-is": "^2.0.1"
82
+ },
83
+ "engines": {
84
+ "node": ">=18"
85
+ },
86
+ "funding": {
87
+ "type": "opencollective",
88
+ "url": "https://opencollective.com/express"
89
+ }
90
+ },
91
+ "node_modules/buffer-alloc": {
92
+ "version": "1.2.0",
93
+ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
94
+ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
95
+ "license": "MIT",
96
+ "dependencies": {
97
+ "buffer-alloc-unsafe": "^1.1.0",
98
+ "buffer-fill": "^1.0.0"
99
+ }
100
+ },
101
+ "node_modules/buffer-alloc-unsafe": {
102
+ "version": "1.1.0",
103
+ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
104
+ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
105
+ "license": "MIT"
106
+ },
107
+ "node_modules/buffer-fill": {
108
+ "version": "1.0.0",
109
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
110
+ "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==",
111
+ "license": "MIT"
112
+ },
113
+ "node_modules/bytes": {
114
+ "version": "3.1.2",
115
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
116
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
117
+ "license": "MIT",
118
+ "engines": {
119
+ "node": ">= 0.8"
120
+ }
121
+ },
122
+ "node_modules/call-bind-apply-helpers": {
123
+ "version": "1.0.2",
124
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
125
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
126
+ "license": "MIT",
127
+ "dependencies": {
128
+ "es-errors": "^1.3.0",
129
+ "function-bind": "^1.1.2"
130
+ },
131
+ "engines": {
132
+ "node": ">= 0.4"
133
+ }
134
+ },
135
+ "node_modules/call-bound": {
136
+ "version": "1.0.4",
137
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
138
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
139
+ "license": "MIT",
140
+ "dependencies": {
141
+ "call-bind-apply-helpers": "^1.0.2",
142
+ "get-intrinsic": "^1.3.0"
143
+ },
144
+ "engines": {
145
+ "node": ">= 0.4"
146
+ },
147
+ "funding": {
148
+ "url": "https://github.com/sponsors/ljharb"
149
+ }
150
+ },
151
+ "node_modules/combined-stream": {
152
+ "version": "1.0.8",
153
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
154
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
155
+ "license": "MIT",
156
+ "dependencies": {
157
+ "delayed-stream": "~1.0.0"
158
+ },
159
+ "engines": {
160
+ "node": ">= 0.8"
161
+ }
162
+ },
163
+ "node_modules/content-disposition": {
164
+ "version": "1.0.1",
165
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
166
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
167
+ "license": "MIT",
168
+ "engines": {
169
+ "node": ">=18"
170
+ },
171
+ "funding": {
172
+ "type": "opencollective",
173
+ "url": "https://opencollective.com/express"
174
+ }
175
+ },
176
+ "node_modules/content-type": {
177
+ "version": "1.0.5",
178
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
179
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
180
+ "license": "MIT",
181
+ "engines": {
182
+ "node": ">= 0.6"
183
+ }
184
+ },
185
+ "node_modules/cookie": {
186
+ "version": "0.7.2",
187
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
188
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
189
+ "license": "MIT",
190
+ "engines": {
191
+ "node": ">= 0.6"
192
+ }
193
+ },
194
+ "node_modules/cookie-signature": {
195
+ "version": "1.2.2",
196
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
197
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
198
+ "license": "MIT",
199
+ "engines": {
200
+ "node": ">=6.6.0"
201
+ }
202
+ },
203
+ "node_modules/cors": {
204
+ "version": "2.8.6",
205
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
206
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
207
+ "license": "MIT",
208
+ "dependencies": {
209
+ "object-assign": "^4",
210
+ "vary": "^1"
211
+ },
212
+ "engines": {
213
+ "node": ">= 0.10"
214
+ },
215
+ "funding": {
216
+ "type": "opencollective",
217
+ "url": "https://opencollective.com/express"
218
+ }
219
+ },
220
+ "node_modules/debug": {
221
+ "version": "4.4.3",
222
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
223
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
224
+ "license": "MIT",
225
+ "dependencies": {
226
+ "ms": "^2.1.3"
227
+ },
228
+ "engines": {
229
+ "node": ">=6.0"
230
+ },
231
+ "peerDependenciesMeta": {
232
+ "supports-color": {
233
+ "optional": true
234
+ }
235
+ }
236
+ },
237
+ "node_modules/delayed-stream": {
238
+ "version": "1.0.0",
239
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
240
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
241
+ "license": "MIT",
242
+ "engines": {
243
+ "node": ">=0.4.0"
244
+ }
245
+ },
246
+ "node_modules/depd": {
247
+ "version": "2.0.0",
248
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
249
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
250
+ "license": "MIT",
251
+ "engines": {
252
+ "node": ">= 0.8"
253
+ }
254
+ },
255
+ "node_modules/dotenv": {
256
+ "version": "17.2.4",
257
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz",
258
+ "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==",
259
+ "license": "BSD-2-Clause",
260
+ "engines": {
261
+ "node": ">=12"
262
+ },
263
+ "funding": {
264
+ "url": "https://dotenvx.com"
265
+ }
266
+ },
267
+ "node_modules/dunder-proto": {
268
+ "version": "1.0.1",
269
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
270
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
271
+ "license": "MIT",
272
+ "dependencies": {
273
+ "call-bind-apply-helpers": "^1.0.1",
274
+ "es-errors": "^1.3.0",
275
+ "gopd": "^1.2.0"
276
+ },
277
+ "engines": {
278
+ "node": ">= 0.4"
279
+ }
280
+ },
281
+ "node_modules/ee-first": {
282
+ "version": "1.1.1",
283
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
284
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
285
+ "license": "MIT"
286
+ },
287
+ "node_modules/encodeurl": {
288
+ "version": "2.0.0",
289
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
290
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
291
+ "license": "MIT",
292
+ "engines": {
293
+ "node": ">= 0.8"
294
+ }
295
+ },
296
+ "node_modules/es-define-property": {
297
+ "version": "1.0.1",
298
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
299
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
300
+ "license": "MIT",
301
+ "engines": {
302
+ "node": ">= 0.4"
303
+ }
304
+ },
305
+ "node_modules/es-errors": {
306
+ "version": "1.3.0",
307
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
308
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
309
+ "license": "MIT",
310
+ "engines": {
311
+ "node": ">= 0.4"
312
+ }
313
+ },
314
+ "node_modules/es-object-atoms": {
315
+ "version": "1.1.1",
316
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
317
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
318
+ "license": "MIT",
319
+ "dependencies": {
320
+ "es-errors": "^1.3.0"
321
+ },
322
+ "engines": {
323
+ "node": ">= 0.4"
324
+ }
325
+ },
326
+ "node_modules/es-set-tostringtag": {
327
+ "version": "2.1.0",
328
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
329
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
330
+ "license": "MIT",
331
+ "dependencies": {
332
+ "es-errors": "^1.3.0",
333
+ "get-intrinsic": "^1.2.6",
334
+ "has-tostringtag": "^1.0.2",
335
+ "hasown": "^2.0.2"
336
+ },
337
+ "engines": {
338
+ "node": ">= 0.4"
339
+ }
340
+ },
341
+ "node_modules/escape-html": {
342
+ "version": "1.0.3",
343
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
344
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
345
+ "license": "MIT"
346
+ },
347
+ "node_modules/etag": {
348
+ "version": "1.8.1",
349
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
350
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
351
+ "license": "MIT",
352
+ "engines": {
353
+ "node": ">= 0.6"
354
+ }
355
+ },
356
+ "node_modules/event-target-shim": {
357
+ "version": "5.0.1",
358
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
359
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
360
+ "license": "MIT",
361
+ "engines": {
362
+ "node": ">=6"
363
+ }
364
+ },
365
+ "node_modules/express": {
366
+ "version": "5.2.1",
367
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
368
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
369
+ "license": "MIT",
370
+ "dependencies": {
371
+ "accepts": "^2.0.0",
372
+ "body-parser": "^2.2.1",
373
+ "content-disposition": "^1.0.0",
374
+ "content-type": "^1.0.5",
375
+ "cookie": "^0.7.1",
376
+ "cookie-signature": "^1.2.1",
377
+ "debug": "^4.4.0",
378
+ "depd": "^2.0.0",
379
+ "encodeurl": "^2.0.0",
380
+ "escape-html": "^1.0.3",
381
+ "etag": "^1.8.1",
382
+ "finalhandler": "^2.1.0",
383
+ "fresh": "^2.0.0",
384
+ "http-errors": "^2.0.0",
385
+ "merge-descriptors": "^2.0.0",
386
+ "mime-types": "^3.0.0",
387
+ "on-finished": "^2.4.1",
388
+ "once": "^1.4.0",
389
+ "parseurl": "^1.3.3",
390
+ "proxy-addr": "^2.0.7",
391
+ "qs": "^6.14.0",
392
+ "range-parser": "^1.2.1",
393
+ "router": "^2.2.0",
394
+ "send": "^1.1.0",
395
+ "serve-static": "^2.2.0",
396
+ "statuses": "^2.0.1",
397
+ "type-is": "^2.0.1",
398
+ "vary": "^1.1.2"
399
+ },
400
+ "engines": {
401
+ "node": ">= 18"
402
+ },
403
+ "funding": {
404
+ "type": "opencollective",
405
+ "url": "https://opencollective.com/express"
406
+ }
407
+ },
408
+ "node_modules/finalhandler": {
409
+ "version": "2.1.1",
410
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
411
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
412
+ "license": "MIT",
413
+ "dependencies": {
414
+ "debug": "^4.4.0",
415
+ "encodeurl": "^2.0.0",
416
+ "escape-html": "^1.0.3",
417
+ "on-finished": "^2.4.1",
418
+ "parseurl": "^1.3.3",
419
+ "statuses": "^2.0.1"
420
+ },
421
+ "engines": {
422
+ "node": ">= 18.0.0"
423
+ },
424
+ "funding": {
425
+ "type": "opencollective",
426
+ "url": "https://opencollective.com/express"
427
+ }
428
+ },
429
+ "node_modules/follow-redirects": {
430
+ "version": "1.15.11",
431
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
432
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
433
+ "funding": [
434
+ {
435
+ "type": "individual",
436
+ "url": "https://github.com/sponsors/RubenVerborgh"
437
+ }
438
+ ],
439
+ "license": "MIT",
440
+ "engines": {
441
+ "node": ">=4.0"
442
+ },
443
+ "peerDependenciesMeta": {
444
+ "debug": {
445
+ "optional": true
446
+ }
447
+ }
448
+ },
449
+ "node_modules/form-data": {
450
+ "version": "4.0.5",
451
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
452
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
453
+ "license": "MIT",
454
+ "dependencies": {
455
+ "asynckit": "^0.4.0",
456
+ "combined-stream": "^1.0.8",
457
+ "es-set-tostringtag": "^2.1.0",
458
+ "hasown": "^2.0.2",
459
+ "mime-types": "^2.1.12"
460
+ },
461
+ "engines": {
462
+ "node": ">= 6"
463
+ }
464
+ },
465
+ "node_modules/form-data/node_modules/mime-db": {
466
+ "version": "1.52.0",
467
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
468
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
469
+ "license": "MIT",
470
+ "engines": {
471
+ "node": ">= 0.6"
472
+ }
473
+ },
474
+ "node_modules/form-data/node_modules/mime-types": {
475
+ "version": "2.1.35",
476
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
477
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
478
+ "license": "MIT",
479
+ "dependencies": {
480
+ "mime-db": "1.52.0"
481
+ },
482
+ "engines": {
483
+ "node": ">= 0.6"
484
+ }
485
+ },
486
+ "node_modules/forwarded": {
487
+ "version": "0.2.0",
488
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
489
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
490
+ "license": "MIT",
491
+ "engines": {
492
+ "node": ">= 0.6"
493
+ }
494
+ },
495
+ "node_modules/fresh": {
496
+ "version": "2.0.0",
497
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
498
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
499
+ "license": "MIT",
500
+ "engines": {
501
+ "node": ">= 0.8"
502
+ }
503
+ },
504
+ "node_modules/function-bind": {
505
+ "version": "1.1.2",
506
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
507
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
508
+ "license": "MIT",
509
+ "funding": {
510
+ "url": "https://github.com/sponsors/ljharb"
511
+ }
512
+ },
513
+ "node_modules/get-intrinsic": {
514
+ "version": "1.3.0",
515
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
516
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
517
+ "license": "MIT",
518
+ "dependencies": {
519
+ "call-bind-apply-helpers": "^1.0.2",
520
+ "es-define-property": "^1.0.1",
521
+ "es-errors": "^1.3.0",
522
+ "es-object-atoms": "^1.1.1",
523
+ "function-bind": "^1.1.2",
524
+ "get-proto": "^1.0.1",
525
+ "gopd": "^1.2.0",
526
+ "has-symbols": "^1.1.0",
527
+ "hasown": "^2.0.2",
528
+ "math-intrinsics": "^1.1.0"
529
+ },
530
+ "engines": {
531
+ "node": ">= 0.4"
532
+ },
533
+ "funding": {
534
+ "url": "https://github.com/sponsors/ljharb"
535
+ }
536
+ },
537
+ "node_modules/get-proto": {
538
+ "version": "1.0.1",
539
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
540
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
541
+ "license": "MIT",
542
+ "dependencies": {
543
+ "dunder-proto": "^1.0.1",
544
+ "es-object-atoms": "^1.0.0"
545
+ },
546
+ "engines": {
547
+ "node": ">= 0.4"
548
+ }
549
+ },
550
+ "node_modules/gopd": {
551
+ "version": "1.2.0",
552
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
553
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
554
+ "license": "MIT",
555
+ "engines": {
556
+ "node": ">= 0.4"
557
+ },
558
+ "funding": {
559
+ "url": "https://github.com/sponsors/ljharb"
560
+ }
561
+ },
562
+ "node_modules/has-symbols": {
563
+ "version": "1.1.0",
564
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
565
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
566
+ "license": "MIT",
567
+ "engines": {
568
+ "node": ">= 0.4"
569
+ },
570
+ "funding": {
571
+ "url": "https://github.com/sponsors/ljharb"
572
+ }
573
+ },
574
+ "node_modules/has-tostringtag": {
575
+ "version": "1.0.2",
576
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
577
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
578
+ "license": "MIT",
579
+ "dependencies": {
580
+ "has-symbols": "^1.0.3"
581
+ },
582
+ "engines": {
583
+ "node": ">= 0.4"
584
+ },
585
+ "funding": {
586
+ "url": "https://github.com/sponsors/ljharb"
587
+ }
588
+ },
589
+ "node_modules/hasown": {
590
+ "version": "2.0.2",
591
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
592
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
593
+ "license": "MIT",
594
+ "dependencies": {
595
+ "function-bind": "^1.1.2"
596
+ },
597
+ "engines": {
598
+ "node": ">= 0.4"
599
+ }
600
+ },
601
+ "node_modules/http-errors": {
602
+ "version": "2.0.1",
603
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
604
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
605
+ "license": "MIT",
606
+ "dependencies": {
607
+ "depd": "~2.0.0",
608
+ "inherits": "~2.0.4",
609
+ "setprototypeof": "~1.2.0",
610
+ "statuses": "~2.0.2",
611
+ "toidentifier": "~1.0.1"
612
+ },
613
+ "engines": {
614
+ "node": ">= 0.8"
615
+ },
616
+ "funding": {
617
+ "type": "opencollective",
618
+ "url": "https://opencollective.com/express"
619
+ }
620
+ },
621
+ "node_modules/iconv-lite": {
622
+ "version": "0.7.2",
623
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
624
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
625
+ "license": "MIT",
626
+ "dependencies": {
627
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
628
+ },
629
+ "engines": {
630
+ "node": ">=0.10.0"
631
+ },
632
+ "funding": {
633
+ "type": "opencollective",
634
+ "url": "https://opencollective.com/express"
635
+ }
636
+ },
637
+ "node_modules/inherits": {
638
+ "version": "2.0.4",
639
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
640
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
641
+ "license": "ISC"
642
+ },
643
+ "node_modules/ipaddr.js": {
644
+ "version": "1.9.1",
645
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
646
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
647
+ "license": "MIT",
648
+ "engines": {
649
+ "node": ">= 0.10"
650
+ }
651
+ },
652
+ "node_modules/is-promise": {
653
+ "version": "4.0.0",
654
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
655
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
656
+ "license": "MIT"
657
+ },
658
+ "node_modules/math-intrinsics": {
659
+ "version": "1.1.0",
660
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
661
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
662
+ "license": "MIT",
663
+ "engines": {
664
+ "node": ">= 0.4"
665
+ }
666
+ },
667
+ "node_modules/media-typer": {
668
+ "version": "1.1.0",
669
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
670
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
671
+ "license": "MIT",
672
+ "engines": {
673
+ "node": ">= 0.8"
674
+ }
675
+ },
676
+ "node_modules/merge-descriptors": {
677
+ "version": "2.0.0",
678
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
679
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
680
+ "license": "MIT",
681
+ "engines": {
682
+ "node": ">=18"
683
+ },
684
+ "funding": {
685
+ "url": "https://github.com/sponsors/sindresorhus"
686
+ }
687
+ },
688
+ "node_modules/mime-db": {
689
+ "version": "1.54.0",
690
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
691
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
692
+ "license": "MIT",
693
+ "engines": {
694
+ "node": ">= 0.6"
695
+ }
696
+ },
697
+ "node_modules/mime-types": {
698
+ "version": "3.0.2",
699
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
700
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
701
+ "license": "MIT",
702
+ "dependencies": {
703
+ "mime-db": "^1.54.0"
704
+ },
705
+ "engines": {
706
+ "node": ">=18"
707
+ },
708
+ "funding": {
709
+ "type": "opencollective",
710
+ "url": "https://opencollective.com/express"
711
+ }
712
+ },
713
+ "node_modules/mri": {
714
+ "version": "1.2.0",
715
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
716
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
717
+ "license": "MIT",
718
+ "engines": {
719
+ "node": ">=4"
720
+ }
721
+ },
722
+ "node_modules/ms": {
723
+ "version": "2.1.3",
724
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
725
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
726
+ "license": "MIT"
727
+ },
728
+ "node_modules/negotiator": {
729
+ "version": "1.0.0",
730
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
731
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
732
+ "license": "MIT",
733
+ "engines": {
734
+ "node": ">= 0.6"
735
+ }
736
+ },
737
+ "node_modules/node-fetch": {
738
+ "version": "2.7.0",
739
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
740
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
741
+ "license": "MIT",
742
+ "dependencies": {
743
+ "whatwg-url": "^5.0.0"
744
+ },
745
+ "engines": {
746
+ "node": "4.x || >=6.0.0"
747
+ },
748
+ "peerDependencies": {
749
+ "encoding": "^0.1.0"
750
+ },
751
+ "peerDependenciesMeta": {
752
+ "encoding": {
753
+ "optional": true
754
+ }
755
+ }
756
+ },
757
+ "node_modules/object-assign": {
758
+ "version": "4.1.1",
759
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
760
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
761
+ "license": "MIT",
762
+ "engines": {
763
+ "node": ">=0.10.0"
764
+ }
765
+ },
766
+ "node_modules/object-inspect": {
767
+ "version": "1.13.4",
768
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
769
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
770
+ "license": "MIT",
771
+ "engines": {
772
+ "node": ">= 0.4"
773
+ },
774
+ "funding": {
775
+ "url": "https://github.com/sponsors/ljharb"
776
+ }
777
+ },
778
+ "node_modules/on-finished": {
779
+ "version": "2.4.1",
780
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
781
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
782
+ "license": "MIT",
783
+ "dependencies": {
784
+ "ee-first": "1.1.1"
785
+ },
786
+ "engines": {
787
+ "node": ">= 0.8"
788
+ }
789
+ },
790
+ "node_modules/once": {
791
+ "version": "1.4.0",
792
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
793
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
794
+ "license": "ISC",
795
+ "dependencies": {
796
+ "wrappy": "1"
797
+ }
798
+ },
799
+ "node_modules/p-timeout": {
800
+ "version": "4.1.0",
801
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz",
802
+ "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==",
803
+ "license": "MIT",
804
+ "engines": {
805
+ "node": ">=10"
806
+ }
807
+ },
808
+ "node_modules/parseurl": {
809
+ "version": "1.3.3",
810
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
811
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
812
+ "license": "MIT",
813
+ "engines": {
814
+ "node": ">= 0.8"
815
+ }
816
+ },
817
+ "node_modules/path-to-regexp": {
818
+ "version": "8.3.0",
819
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
820
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
821
+ "license": "MIT",
822
+ "funding": {
823
+ "type": "opencollective",
824
+ "url": "https://opencollective.com/express"
825
+ }
826
+ },
827
+ "node_modules/proxy-addr": {
828
+ "version": "2.0.7",
829
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
830
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
831
+ "license": "MIT",
832
+ "dependencies": {
833
+ "forwarded": "0.2.0",
834
+ "ipaddr.js": "1.9.1"
835
+ },
836
+ "engines": {
837
+ "node": ">= 0.10"
838
+ }
839
+ },
840
+ "node_modules/proxy-from-env": {
841
+ "version": "1.1.0",
842
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
843
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
844
+ "license": "MIT"
845
+ },
846
+ "node_modules/qs": {
847
+ "version": "6.14.1",
848
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
849
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
850
+ "license": "BSD-3-Clause",
851
+ "dependencies": {
852
+ "side-channel": "^1.1.0"
853
+ },
854
+ "engines": {
855
+ "node": ">=0.6"
856
+ },
857
+ "funding": {
858
+ "url": "https://github.com/sponsors/ljharb"
859
+ }
860
+ },
861
+ "node_modules/range-parser": {
862
+ "version": "1.2.1",
863
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
864
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
865
+ "license": "MIT",
866
+ "engines": {
867
+ "node": ">= 0.6"
868
+ }
869
+ },
870
+ "node_modules/raw-body": {
871
+ "version": "3.0.2",
872
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
873
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
874
+ "license": "MIT",
875
+ "dependencies": {
876
+ "bytes": "~3.1.2",
877
+ "http-errors": "~2.0.1",
878
+ "iconv-lite": "~0.7.0",
879
+ "unpipe": "~1.0.0"
880
+ },
881
+ "engines": {
882
+ "node": ">= 0.10"
883
+ }
884
+ },
885
+ "node_modules/router": {
886
+ "version": "2.2.0",
887
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
888
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
889
+ "license": "MIT",
890
+ "dependencies": {
891
+ "debug": "^4.4.0",
892
+ "depd": "^2.0.0",
893
+ "is-promise": "^4.0.0",
894
+ "parseurl": "^1.3.3",
895
+ "path-to-regexp": "^8.0.0"
896
+ },
897
+ "engines": {
898
+ "node": ">= 18"
899
+ }
900
+ },
901
+ "node_modules/safe-compare": {
902
+ "version": "1.1.4",
903
+ "resolved": "https://registry.npmjs.org/safe-compare/-/safe-compare-1.1.4.tgz",
904
+ "integrity": "sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==",
905
+ "license": "MIT",
906
+ "dependencies": {
907
+ "buffer-alloc": "^1.2.0"
908
+ }
909
+ },
910
+ "node_modules/safer-buffer": {
911
+ "version": "2.1.2",
912
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
913
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
914
+ "license": "MIT"
915
+ },
916
+ "node_modules/sandwich-stream": {
917
+ "version": "2.0.2",
918
+ "resolved": "https://registry.npmjs.org/sandwich-stream/-/sandwich-stream-2.0.2.tgz",
919
+ "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==",
920
+ "license": "Apache-2.0",
921
+ "engines": {
922
+ "node": ">= 0.10"
923
+ }
924
+ },
925
+ "node_modules/send": {
926
+ "version": "1.2.1",
927
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
928
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
929
+ "license": "MIT",
930
+ "dependencies": {
931
+ "debug": "^4.4.3",
932
+ "encodeurl": "^2.0.0",
933
+ "escape-html": "^1.0.3",
934
+ "etag": "^1.8.1",
935
+ "fresh": "^2.0.0",
936
+ "http-errors": "^2.0.1",
937
+ "mime-types": "^3.0.2",
938
+ "ms": "^2.1.3",
939
+ "on-finished": "^2.4.1",
940
+ "range-parser": "^1.2.1",
941
+ "statuses": "^2.0.2"
942
+ },
943
+ "engines": {
944
+ "node": ">= 18"
945
+ },
946
+ "funding": {
947
+ "type": "opencollective",
948
+ "url": "https://opencollective.com/express"
949
+ }
950
+ },
951
+ "node_modules/serve-static": {
952
+ "version": "2.2.1",
953
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
954
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
955
+ "license": "MIT",
956
+ "dependencies": {
957
+ "encodeurl": "^2.0.0",
958
+ "escape-html": "^1.0.3",
959
+ "parseurl": "^1.3.3",
960
+ "send": "^1.2.0"
961
+ },
962
+ "engines": {
963
+ "node": ">= 18"
964
+ },
965
+ "funding": {
966
+ "type": "opencollective",
967
+ "url": "https://opencollective.com/express"
968
+ }
969
+ },
970
+ "node_modules/setprototypeof": {
971
+ "version": "1.2.0",
972
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
973
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
974
+ "license": "ISC"
975
+ },
976
+ "node_modules/side-channel": {
977
+ "version": "1.1.0",
978
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
979
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
980
+ "license": "MIT",
981
+ "dependencies": {
982
+ "es-errors": "^1.3.0",
983
+ "object-inspect": "^1.13.3",
984
+ "side-channel-list": "^1.0.0",
985
+ "side-channel-map": "^1.0.1",
986
+ "side-channel-weakmap": "^1.0.2"
987
+ },
988
+ "engines": {
989
+ "node": ">= 0.4"
990
+ },
991
+ "funding": {
992
+ "url": "https://github.com/sponsors/ljharb"
993
+ }
994
+ },
995
+ "node_modules/side-channel-list": {
996
+ "version": "1.0.0",
997
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
998
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
999
+ "license": "MIT",
1000
+ "dependencies": {
1001
+ "es-errors": "^1.3.0",
1002
+ "object-inspect": "^1.13.3"
1003
+ },
1004
+ "engines": {
1005
+ "node": ">= 0.4"
1006
+ },
1007
+ "funding": {
1008
+ "url": "https://github.com/sponsors/ljharb"
1009
+ }
1010
+ },
1011
+ "node_modules/side-channel-map": {
1012
+ "version": "1.0.1",
1013
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1014
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1015
+ "license": "MIT",
1016
+ "dependencies": {
1017
+ "call-bound": "^1.0.2",
1018
+ "es-errors": "^1.3.0",
1019
+ "get-intrinsic": "^1.2.5",
1020
+ "object-inspect": "^1.13.3"
1021
+ },
1022
+ "engines": {
1023
+ "node": ">= 0.4"
1024
+ },
1025
+ "funding": {
1026
+ "url": "https://github.com/sponsors/ljharb"
1027
+ }
1028
+ },
1029
+ "node_modules/side-channel-weakmap": {
1030
+ "version": "1.0.2",
1031
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1032
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1033
+ "license": "MIT",
1034
+ "dependencies": {
1035
+ "call-bound": "^1.0.2",
1036
+ "es-errors": "^1.3.0",
1037
+ "get-intrinsic": "^1.2.5",
1038
+ "object-inspect": "^1.13.3",
1039
+ "side-channel-map": "^1.0.1"
1040
+ },
1041
+ "engines": {
1042
+ "node": ">= 0.4"
1043
+ },
1044
+ "funding": {
1045
+ "url": "https://github.com/sponsors/ljharb"
1046
+ }
1047
+ },
1048
+ "node_modules/statuses": {
1049
+ "version": "2.0.2",
1050
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
1051
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
1052
+ "license": "MIT",
1053
+ "engines": {
1054
+ "node": ">= 0.8"
1055
+ }
1056
+ },
1057
+ "node_modules/telegraf": {
1058
+ "version": "4.16.3",
1059
+ "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-4.16.3.tgz",
1060
+ "integrity": "sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==",
1061
+ "license": "MIT",
1062
+ "dependencies": {
1063
+ "@telegraf/types": "^7.1.0",
1064
+ "abort-controller": "^3.0.0",
1065
+ "debug": "^4.3.4",
1066
+ "mri": "^1.2.0",
1067
+ "node-fetch": "^2.7.0",
1068
+ "p-timeout": "^4.1.0",
1069
+ "safe-compare": "^1.1.4",
1070
+ "sandwich-stream": "^2.0.2"
1071
+ },
1072
+ "bin": {
1073
+ "telegraf": "lib/cli.mjs"
1074
+ },
1075
+ "engines": {
1076
+ "node": "^12.20.0 || >=14.13.1"
1077
+ }
1078
+ },
1079
+ "node_modules/toidentifier": {
1080
+ "version": "1.0.1",
1081
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1082
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1083
+ "license": "MIT",
1084
+ "engines": {
1085
+ "node": ">=0.6"
1086
+ }
1087
+ },
1088
+ "node_modules/tr46": {
1089
+ "version": "0.0.3",
1090
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
1091
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
1092
+ "license": "MIT"
1093
+ },
1094
+ "node_modules/type-is": {
1095
+ "version": "2.0.1",
1096
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
1097
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
1098
+ "license": "MIT",
1099
+ "dependencies": {
1100
+ "content-type": "^1.0.5",
1101
+ "media-typer": "^1.1.0",
1102
+ "mime-types": "^3.0.0"
1103
+ },
1104
+ "engines": {
1105
+ "node": ">= 0.6"
1106
+ }
1107
+ },
1108
+ "node_modules/unpipe": {
1109
+ "version": "1.0.0",
1110
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1111
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1112
+ "license": "MIT",
1113
+ "engines": {
1114
+ "node": ">= 0.8"
1115
+ }
1116
+ },
1117
+ "node_modules/vary": {
1118
+ "version": "1.1.2",
1119
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1120
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1121
+ "license": "MIT",
1122
+ "engines": {
1123
+ "node": ">= 0.8"
1124
+ }
1125
+ },
1126
+ "node_modules/webidl-conversions": {
1127
+ "version": "3.0.1",
1128
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
1129
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
1130
+ "license": "BSD-2-Clause"
1131
+ },
1132
+ "node_modules/whatwg-url": {
1133
+ "version": "5.0.0",
1134
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
1135
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
1136
+ "license": "MIT",
1137
+ "dependencies": {
1138
+ "tr46": "~0.0.3",
1139
+ "webidl-conversions": "^3.0.0"
1140
+ }
1141
+ },
1142
+ "node_modules/wrappy": {
1143
+ "version": "1.0.2",
1144
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1145
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1146
+ "license": "ISC"
1147
+ }
1148
+ }
1149
+ }
package.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "aipostgenerator",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "node index.js",
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "dependencies": {
14
+ "axios": "^1.13.5",
15
+ "cors": "^2.8.6",
16
+ "dotenv": "^17.2.4",
17
+ "express": "^5.2.1",
18
+ "telegraf": "^4.16.3"
19
+ }
20
+ }
prompts/imgSystemPrompt.js ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ const imgSystemPrompt = "";
2
+
3
+ module.exports = { imgSystemPrompt };
prompts/promptBuilder.js ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Prompt Builder
3
+ * Helper methods to assemble complete prompts for LLM and Image Generation services.
4
+ */
5
+
6
+ /**
7
+ * Builds the final prompt for the LLM.
8
+ * Combines the system prompt, style instruction, and user's message.
9
+ *
10
+ * @param {string} userMessage - The input text from the user.
11
+ * @param {string} systemPrompt - The base system prompt (e.g., from storySystemPrompt.js).
12
+ * @param {string} stylePreset - The chosen style preset object.
13
+ * @returns {string} - The fully assembled prompt string.
14
+ */
15
+ function buildStoryPrompt(userMessage, systemPrompt, stylePreset) {
16
+ // Format examples if available
17
+ let examplesText = "";
18
+ if (stylePreset.text_examples && stylePreset.text_examples.length > 0) {
19
+ examplesText = stylePreset.text_examples.map(ex => `- ${ex}`).join("\n");
20
+ }
21
+
22
+ const fullPrompt = `
23
+ ${systemPrompt}
24
+
25
+ TOPIC: "${userMessage}"
26
+
27
+ STYLE INSTRUCTION:
28
+ ${stylePreset.text_instruction}
29
+
30
+ REFERENCE EXAMPLES (Mimic this density and flow):
31
+ ${examplesText}
32
+ `.trim();
33
+
34
+ return fullPrompt;
35
+ }
36
+
37
+ /**
38
+ * Builds the final prompt for the Image Generator.
39
+ *
40
+ * @param {string} storyText - The context/content of the story.
41
+ * @param {string} styleSuffix - The style keywords/suffix from the preset.
42
+ * @returns {string} - The assembled image prompt.
43
+ */
44
+ function buildImagePrompt(storyText, styleSuffix) {
45
+ return `
46
+ ${styleSuffix}
47
+
48
+ STORY CONTEXT:
49
+ "${storyText}"
50
+ `.trim();
51
+ }
52
+
53
+ /**
54
+ * Builds a prompt for editing an existing story.
55
+ *
56
+ * @param {string} userMessage - The edit instructions from the user.
57
+ * @param {string} contextPrompt - The original prompt used to generate the story.
58
+ * @param {string} contextStory - The previously generated story text.
59
+ * @returns {string} - The assembled edit prompt.
60
+ */
61
+ function buildEditPrompt(userMessage, contextPrompt, contextStory) {
62
+ return `
63
+ You are an editor. Below is the background context and the story that was previously generated.
64
+
65
+ CONTEXT PROMPT (Initial Intent/Style):
66
+ "${contextPrompt}"
67
+
68
+ CURRENT GENERATED STORY:
69
+ "${contextStory}"
70
+
71
+ USER'S EDIT INSTRUCTION:
72
+ "${userMessage}"
73
+
74
+ Task: Rewrite the story based on the changes requested while maintaining the original tone, English language, and staccato rhythm.
75
+ Output ONLY the revised story text.
76
+ `.trim();
77
+ }
78
+
79
+ /**
80
+ * Builds a prompt for regenerating/editing an image.
81
+ *
82
+ * @param {string} userInstruction - The user's hint or instruction for the image.
83
+ * @returns {string} - The formatted prompt.
84
+ */
85
+ function buildImageRegenerationPrompt(userInstruction) {
86
+ return `
87
+ User Instruction: "${userInstruction}"
88
+
89
+ Task: detailed infographic, similar to the previous image but applying the user's instruction.
90
+ `.trim();
91
+ }
92
+
93
+ module.exports = {
94
+ buildStoryPrompt,
95
+ buildImagePrompt,
96
+ buildEditPrompt,
97
+ buildImageRegenerationPrompt
98
+ };
prompts/storySystemPrompt.js ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ const storySystemPrompt = `Language: Always respond in English, regardless of the input language.
2
+ Formatting: Write as a continuous paragraph without line breaks between sentences. Use a single blank line before hashtags.`;
3
+
4
+ module.exports = { storySystemPrompt };
prompts/stylePresets.js ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const stylePresets = [
2
+ {
3
+ "preset_name": "Before_After",
4
+ "description": "Emotional storytelling in 'memoir minimalism' style. Contrasts past and present with raw, staccato rhythm.",
5
+ "text_instruction": `Role: You are a master of emotional storytelling in the style of "memoir minimalism."
6
+ Task: Write a social media post about: TOPIC.
7
+ Style Guidelines (Follow strictly):
8
+ Instant Hook: Start with the main character and the core conflict in the very first sentence. No introductions.
9
+ Staccato Rhythm: Use short, punchy sentences. One sentence = one fact or action. Avoid complex conjunctions or excessive commas.
10
+ Show, Don't Tell: Do not describe how the character feels. Describe what they did or what they looked at. Let the reader feel the emotion through the facts.
11
+ Time Dynamics: Show the passage of time (days, months, years). The story must move forward, not stay static.
12
+ The "Quiet" Punchline: End with a brief, powerful sentence that provides closure without being overly dramatic.
13
+ Final Touch (Tags): At the end of the post, add 2–3 relevant hashtags. Make them specific to the story's theme and emotional takeaway.`,
14
+ "text_examples": [
15
+ "Michael waited to ring the cancer bell with me. Ten years later, I married him. We met at seventeen during cancer treatment. Different diagnoses, same hospital, same long days of waiting. Michael finished treatment first. Nurses told him to ring the bell. He didn’t. He said he’d wait. Months later, it was my turn. On my last day, Michael stood next to me. We rang the bell together. He didn’t just wait for the bell. He stayed for everything after.",
16
+ "At 55, I dropped 400 pounds to stand on my own again. My name is Linda. After my divorce 15 years ago, my weight slowly took over my life. I stayed inside, stopped driving, and depended on others. I chose operations, therapy, and daily work. Progress was slow, but it added up. I learned how to move again and trust my body. Today, I walk on my own, go out, and plan for the future. I didn’t just change my body. I changed my life."
17
+ ],
18
+ "image_style_suffix": `Role: You are a Senior Documentary Photojournalist and Image Archivist. Your mission is to convert a raw human story into a technical prompt for an image generator (NanaBanana) to create a "Then vs. Now" diptych.
19
+ The Goal: The final image must look like a real, leaked, or archived photo collage from social media. It must feel authentic, imperfect, and deeply emotional.
20
+ STEP 1: NARRATIVE DISSECTION (Internal Analysis)
21
+ Before writing the prompt, identify:
22
+ 1. **The Catalyst (Past):** What is the core struggle? (e.g., flood, illness, poverty). What are the visual markers? (Mud, hospital gowns, shivering, wet hair).
23
+ 2. **The Triumph (Present):** What is the success? (A career, a wedding, health). What is the "Uniform" of this success? (Police gear, medical scrubs, a suit).
24
+ 3. **The Human Connection:** How are they touching? (Desperate cling vs. proud embrace).
25
+ 4. **Physical Anchors:** Choose 2-3 traits to keep consistent (Ethnicity, jawline shape, eye color).
26
+
27
+ STEP 2: VISUAL STYLE & TECHNICAL SPECS
28
+ - Format: A vertical-split diptych (side-by-side) with a thin, natural white divider.
29
+ - Left Panel Aesthetic: "Analog Archive." 1990s/2000s film grain, slightly overexposed flash, flat lighting, amateur composition. Use terms like "disposable camera look" or "grainy film scan."
30
+ - Right Panel Aesthetic: "Modern Candid." High-quality smartphone photo, natural outdoor lighting, authentic skin textures (pores, slight wrinkles, sweat), "shot on iPhone" feel.
31
+ - NO-GO ZONE: Avoid "cinematic," "8k," "masterpiece," "digital art," "perfect faces." If it looks like a movie poster, you failed. It must look like a real photo.
32
+
33
+ STEP 3: OUTPUT CONSTRUCTION
34
+ Start your response ONLY with: "A raw, documentary-style side-by-side photo collage..."
35
+
36
+ Example of the detail level required:
37
+ "Left: A grainy 2005 point-and-shoot photo of a soot-covered 19-year-old girl in a torn sweater, holding a crying toddler outside a burnt house; harsh flash, red-eye effect. Right: A modern smartphone photo of the same woman, now in her 30s with realistic sun-damage on skin, smiling and hugging the same boy who is now a tall firefighter in a heavy, soot-stained yellow jacket on a sunny day."`
38
+ },
39
+ {
40
+ "preset_name": "Cyberpunk_Neon",
41
+ "description": "Мир высокого хай-тека и низкой жизни. Неон, кибер-импланты и корпоративные интриги.",
42
+ "text_instruction": "Пиши в стиле киберпанка, с акцентом на технологии и антиутопию...",
43
+ "text_examples": ["Пример 1: Неоновые огни отражались в лужах...", "Пример 2: Импланты гудели тихо..."],
44
+ "image_style_suffix": "cyberpunk style, neon lights, rainy city, futuristic, high tech low life"
45
+ },
46
+ {
47
+ "preset_name": "Fantasy_Adventure",
48
+ "description": "Эпические приключения в мире магии, драконов и героев. Яркие краски и величественные пейзажи.",
49
+ "text_instruction": "Пиши в стиле эпического фэнтези...",
50
+ "text_examples": ["Пример 1: Дракон взмыл в небо...", "Пример 2: Маг поднял посох..."],
51
+ "image_style_suffix": "fantasy art, epic landscape, magical atmosphere, vibrant colors, detailed"
52
+ }
53
+ ];
54
+
55
+ // Export for both Node.js and Browser
56
+ if (typeof module !== 'undefined' && module.exports) {
57
+ module.exports = { stylePresets };
58
+ } else {
59
+ window.stylePresets = stylePresets;
60
+ }
services/imageService.js ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Image Service
3
+ * Handles communication with Image Generation APIs.
4
+ */
5
+
6
+ const axios = require('axios'); // Assuming axios might be used
7
+
8
+ // Placeholder for API Key configuration
9
+ const API_KEY = process.env.IMG_API_KEY || process.env.LLM_API_KEY || '';
10
+ const MODEL = 'gemini-3-pro-image-preview';
11
+ const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent`;
12
+
13
+ /**
14
+ * Generates an image based on the text prompt.
15
+ * @param {string} prompt - The text description for the image.
16
+ * @param {Object} options - Optional parameters (aspectRatio, etc.)
17
+ * @returns {Promise<string>} - The base64 string of the generated image.
18
+ */
19
+ async function generateImage(prompt, options = {}) {
20
+ if (!API_KEY) {
21
+ throw new Error("IMG_API_KEY or LLM_API_KEY is not defined in environment variables.");
22
+ }
23
+
24
+ try {
25
+ console.log(`[Image Service] Sending request to ${MODEL}...`, prompt);
26
+
27
+ const url = `${API_URL}?key=${API_KEY}`;
28
+
29
+ const payload = {
30
+ contents: [{
31
+ parts: [{
32
+ text: prompt
33
+ }]
34
+ }]
35
+ };
36
+
37
+ // Add optional configuration if supported by the model and needed
38
+ /*
39
+ if (options.aspectRatio) {
40
+ payload.generationConfig = {
41
+ aspectRatio: options.aspectRatio // e.g., "1:1", "4:3", "16:9"
42
+ };
43
+ }
44
+ */
45
+
46
+ const response = await axios.post(url, payload, {
47
+ headers: {
48
+ 'Content-Type': 'application/json'
49
+ },
50
+ timeout: 120000 // 2 minutes timeout
51
+ });
52
+
53
+ // Extract base64 image data from Gemini response structure
54
+ if (response.data && response.data.candidates && response.data.candidates.length > 0) {
55
+ const candidate = response.data.candidates[0];
56
+ if (candidate.content && candidate.content.parts) {
57
+ const imagePart = candidate.content.parts.find(p => p.inlineData);
58
+ if (imagePart && imagePart.inlineData) {
59
+ return imagePart.inlineData.data; // This is the base64 string
60
+ }
61
+ }
62
+ }
63
+
64
+ console.warn("[Image Service] Unexpected response structure:", JSON.stringify(response.data));
65
+ throw new Error("Empty or invalid response from Image Generation API.");
66
+
67
+ } catch (error) {
68
+ console.error('[Image Service] Error generating image:', error.response ? JSON.stringify(error.response.data) : error.message);
69
+ throw error;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Regenerates/Edits an image based on a text prompt and an existing image.
75
+ *
76
+ * @param {string} prompt - The text instruction.
77
+ * @param {string} imageBase64 - The base64 string of the previous image.
78
+ * @returns {Promise<string>} - The base64 string of the new image.
79
+ */
80
+ async function regenerateImage(prompt, imageBase64) {
81
+ if (!API_KEY) {
82
+ throw new Error("IMG_API_KEY or LLM_API_KEY is not defined.");
83
+ }
84
+
85
+ try {
86
+ console.log(`[Image Service] Regenerating image with prompt: ${prompt}`);
87
+
88
+ const url = `${API_URL}?key=${API_KEY}`;
89
+
90
+ const payload = {
91
+ contents: [{
92
+ parts: [
93
+ { text: prompt },
94
+ {
95
+ inline_data: {
96
+ mime_type: "image/png", // Assuming PNG for now, simplest for base64
97
+ data: imageBase64
98
+ }
99
+ }
100
+ ]
101
+ }]
102
+ };
103
+
104
+ const response = await axios.post(url, payload, {
105
+ headers: { 'Content-Type': 'application/json' },
106
+ timeout: 120000
107
+ });
108
+
109
+ if (response.data && response.data.candidates && response.data.candidates.length > 0) {
110
+ const candidate = response.data.candidates[0];
111
+ if (candidate.content && candidate.content.parts) {
112
+ const imagePart = candidate.content.parts.find(p => p.inlineData);
113
+ if (imagePart && imagePart.inlineData) {
114
+ return imagePart.inlineData.data;
115
+ }
116
+ }
117
+ }
118
+
119
+ console.warn("[Image Service] Unexpected response structure:", JSON.stringify(response.data));
120
+ throw new Error("Empty or invalid response from Image Generation API.");
121
+
122
+ } catch (error) {
123
+ console.error('[Image Service] Error regenerating image:', error.response ? JSON.stringify(error.response.data) : error.message);
124
+ throw error;
125
+ }
126
+ }
127
+
128
+ module.exports = {
129
+ generateImage,
130
+ regenerateImage
131
+ };
services/llmService.js ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * LLM Service
3
+ * Handles communication with Google Gemini API.
4
+ */
5
+
6
+ const axios = require('axios');
7
+ const { llm } = require('./settings');
8
+
9
+ // Configuration
10
+ const API_KEY = process.env.LLM_API_KEY; // Using API Key from .env
11
+ const BASE_URL = llm.baseUrl;
12
+
13
+ /**
14
+ * Sends a prompt to the Gemini LLM and returns the generated text.
15
+ *
16
+ * @param {string} promptText - The fully constructed prompt string (including system instructions if embedded).
17
+ * @param {Object} config - Optional configuration overrides (temperature, etc).
18
+ * @returns {Promise<string>} - The generated response text.
19
+ */
20
+ async function generateText(promptText, config = {}) {
21
+ if (!API_KEY) {
22
+ throw new Error("API_KEY is not defined in environment variables.");
23
+ }
24
+
25
+ try {
26
+ const url = `${BASE_URL}?key=${API_KEY}`;
27
+
28
+ const payload = {
29
+ contents: [{
30
+ parts: [{
31
+ text: promptText
32
+ }]
33
+ }],
34
+ generationConfig: {
35
+ temperature: config.temperature || 0.7,
36
+ maxOutputTokens: config.maxTokens || 8192,
37
+ }
38
+ };
39
+
40
+ console.log(`[LLM Service] Sending request to ${llm.model}...`);
41
+
42
+ const response = await axios.post(url, payload, {
43
+ headers: {
44
+ 'Content-Type': 'application/json'
45
+ }
46
+ });
47
+
48
+ // Extract text from Gemini response structure
49
+ if (response.data && response.data.candidates && response.data.candidates.length > 0) {
50
+ const candidate = response.data.candidates[0];
51
+ if (candidate.content && candidate.content.parts && candidate.content.parts.length > 0) {
52
+ return candidate.content.parts[0].text;
53
+ }
54
+ }
55
+
56
+ console.warn("[LLM Service] Unexpected response structure:", JSON.stringify(response.data));
57
+ return "Error: Empty response from LLM.";
58
+
59
+ } catch (error) {
60
+ console.error('[LLM Service] Error generating text:', error.response ? error.response.data : error.message);
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ module.exports = {
66
+ generateText
67
+ };
services/settings.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Global Settings for Services
3
+ * Stores configuration for LLM and Image Generators (excluding sensitive keys).
4
+ */
5
+
6
+ // LLM Configuration
7
+ const LLM_MODEL = 'gemini-flash-latest';
8
+ const LLM_BASE_URL = `https://generativelanguage.googleapis.com/v1beta/models/${LLM_MODEL}:generateContent`;
9
+
10
+ // Image Generator Configuration
11
+ const IMG_MODEL = 'gemini-3-pro-image-preview';
12
+ const IMG_BASE_URL = `https://generativelanguage.googleapis.com/v1beta/models/${IMG_MODEL}:generateContent`;
13
+
14
+ module.exports = {
15
+ llm: {
16
+ model: LLM_MODEL,
17
+ baseUrl: LLM_BASE_URL
18
+ },
19
+ image: {
20
+ model: IMG_MODEL,
21
+ baseUrl: IMG_BASE_URL
22
+ }
23
+ };
test-llm.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const axios = require('axios');
2
+ require('dotenv').config();
3
+
4
+ const API_KEY = process.env.LLM_API_KEY;
5
+ const MODEL = 'gemini-flash-latest';
6
+ const URL = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent?key=${API_KEY}`;
7
+
8
+ async function test() {
9
+ console.log(`Testing LLM with Model: ${MODEL}`);
10
+ console.log(`URL: ${URL.replace(API_KEY, 'HIDDEN_KEY')}`);
11
+
12
+ const payload = {
13
+ contents: [{
14
+ parts: [{ text: "Hello, tell me a joke." }]
15
+ }]
16
+ };
17
+
18
+ try {
19
+ const response = await axios.post(URL, payload, {
20
+ headers: { 'Content-Type': 'application/json' }
21
+ });
22
+ console.log("Success!");
23
+ console.log(response.data.candidates[0].content.parts[0].text);
24
+ } catch (error) {
25
+ console.error("Error Status:", error.response?.status);
26
+ console.error("Error Data:", JSON.stringify(error.response?.data, null, 2));
27
+ }
28
+ }
29
+
30
+ test();