ibrohm commited on
Commit
096c895
Β·
1 Parent(s): 16a83e0

feat: Add Gemini 2.5 Flash Prompt Engineering and Flux support

Browse files
Files changed (4) hide show
  1. .env +2 -0
  2. bot/bot.js +69 -147
  3. package-lock.json +11 -0
  4. package.json +1 -0
.env CHANGED
@@ -2,3 +2,5 @@ PORT=7860
2
  MONGO_URI=mongodb+srv://ibrohm135:mansur5754@cluster0.intw8qq.mongodb.net/savdo?appName=Cluster0
3
  BOT_TOKEN=8606505490:AAETdDAWWis5jLEXEmD2k6wRRrhHGubelBI
4
  ADMIN_ID=6309900880
 
 
 
2
  MONGO_URI=mongodb+srv://ibrohm135:mansur5754@cluster0.intw8qq.mongodb.net/savdo?appName=Cluster0
3
  BOT_TOKEN=8606505490:AAETdDAWWis5jLEXEmD2k6wRRrhHGubelBI
4
  ADMIN_ID=6309900880
5
+
6
+ GEMINI_API_KEY=AIzaSyDtNZY4hzgbnaSD1Y1N2N94qGCJ2arKfq4
bot/bot.js CHANGED
@@ -6,6 +6,9 @@ const Banner = require('../models/Banner');
6
  const dns = require('dns');
7
  const https = require('https');
8
  const http = require('http');
 
 
 
9
 
10
  dns.setDefaultResultOrder('ipv4first');
11
 
@@ -31,100 +34,27 @@ function downloadImage(url) {
31
  });
32
  }
33
 
34
- // O'zbekcha => Inglizcha tarjima lug'ati (150+ so'z)
35
- const uzToEn = {
36
- // === KIYIM & MODA ===
37
- 'kiyim': 'clothing apparel', 'kiyimlar': 'clothing fashion collection', 'libos': 'outfit elegant dress',
38
- 'manti': 'formal blazer coat overcoat', 'mantiya': 'formal blazer coat', 'palto': 'coat winter jacket',
39
- 'kostyum': 'formal business suit', 'koylak': 'shirt blouse', 'ko\'ylak': 'shirt blouse',
40
- 'shim': 'pants trousers', 'jinsi': 'jeans denim', 'futbolka': 'tshirt casual',
41
- 'kurtka': 'jacket outerwear', 'sviter': 'sweater knitwear', 'yubka': 'skirt',
42
- 'plashch': 'raincoat', 'shortik': 'shorts', 'pidjak': 'blazer jacket',
43
- 'galstuk': 'tie necktie', 'shapka': 'hat cap beanie', 'sharf': 'scarf',
44
- 'poyafzal': 'shoes footwear', 'krossovka': 'sneakers trainers', 'tufli': 'formal shoes heels',
45
- 'sumka': 'bag handbag purse', 'kamar': 'belt leather', 'soat': 'watch luxury wristwatch',
46
- 'ko\'zoynak': 'sunglasses eyewear', 'uzuk': 'ring jewelry', 'sirga': 'earrings jewelry',
47
- 'marjonlar': 'necklace beads', 'aksessuar': 'accessories fashion',
48
- // === ODAMLAR ===
49
- 'yigit': 'young man handsome', 'qiz': 'young woman beautiful girl', 'ayol': 'woman elegant lady',
50
- 'erkak': 'man gentleman', 'talaba': 'student university campus', 'bolalar': 'children kids cute',
51
- 'oila': 'family together', 'bola': 'child kid', 'kelin': 'bride beautiful',
52
- 'kuyov': 'groom handsome', 'model': 'fashion model professional',
53
- // === TADBIRLAR ===
54
- 'bitiruv': 'graduation ceremony celebration', 'oqshom': 'evening gala night party',
55
- 'bayram': 'celebration holiday festive joyful', 'toy': 'wedding ceremony luxurious',
56
- 'tug\'ilgan': 'birthday party celebration', 'kun': 'day',
57
- 'mehmon': 'guest party gathering', 'konsert': 'concert music performance',
58
- 'festival': 'festival carnival vibrant',
59
- // === MARKETING & SAVDO ===
60
- 'chegirma': 'sale discount mega promotion', 'aksiya': 'special offer flash deal sale',
61
- 'chegirmalar': 'discounts promotions deals', 'yangilik': 'new arrivals fresh trending',
62
- 'yangi': 'new fresh latest', 'kolleksiya': 'collection exclusive', 'bepul': 'free bonus gift',
63
- 'cheklangan': 'limited edition exclusive', 'tez': 'fast quick express', 'buyurtma': 'order delivery',
64
- 'yetkazish': 'delivery shipping fast', 'foiz': 'percent off discount',
65
- 'super': 'super mega amazing', 'maxsus': 'special exclusive VIP',
66
- 'eng': 'best top number one', 'arzon': 'affordable budget cheap',
67
- 'sifatli': 'premium quality high-end', 'original': 'original authentic genuine',
68
- 'top': 'top bestseller trending', 'hit': 'bestseller popular trending',
69
- // === TEXNOLOGIYA ===
70
- 'texnika': 'electronics technology gadgets', 'telefon': 'smartphone mobile phone',
71
- 'kompyuter': 'computer laptop desktop', 'planshet': 'tablet iPad digital',
72
- 'quloqchin': 'headphones earbuds audio', 'kamera': 'camera photography DSLR',
73
- 'televizor': 'television TV screen', 'printer': 'printer scanner office',
74
- 'naushnik': 'headphones wireless bluetooth', 'zaryadka': 'charger power bank',
75
- 'robot': 'robot technology AI smart', 'dron': 'drone aerial photography',
76
- // === RANGLAR ===
77
- 'qizil': 'red crimson', 'ko\'k': 'blue navy', 'yashil': 'green emerald',
78
- 'sariq': 'yellow golden bright', 'oq': 'white clean pure', 'qora': 'black dark sleek',
79
- 'pushti': 'pink rose', 'binafsha': 'purple violet', 'to\'q': 'dark deep rich',
80
- 'och': 'light pastel soft', 'oltin': 'gold golden luxurious', 'kumush': 'silver metallic',
81
- 'rangli': 'colorful vibrant rainbow', 'rang': 'color hue tone',
82
- // === FASLLAR & OB-HAVO ===
83
- 'yil': 'year annual', 'yoz': 'summer sunny warm', 'qish': 'winter cold snowy',
84
- 'kuz': 'autumn fall harvest', 'bahor': 'spring blossom fresh',
85
- 'issiq': 'hot warm tropical', 'sovuq': 'cold cool freezing',
86
- 'yomg\'ir': 'rain rainy', 'quyosh': 'sun sunshine bright',
87
- // === JOY & MUHIT ===
88
- 'dokon': 'store shop boutique', 'savdo': 'shopping commerce business',
89
- 'bozor': 'market marketplace bazaar', 'uy': 'home interior house', 'hovli': 'garden yard',
90
- 'mebel': 'furniture interior design', 'oshxona': 'kitchen cooking culinary',
91
- 'yotoqxona': 'bedroom cozy interior', 'ofis': 'office workspace business',
92
- 'restoran': 'restaurant dining fine', 'kafe': 'cafe coffeeshop trendy',
93
- 'park': 'park nature outdoor', 'shahar': 'city urban modern skyline',
94
- 'tog\'': 'mountain landscape nature', 'dengiz': 'sea ocean beach tropical',
95
- 'ko\'cha': 'street urban lifestyle',
96
- // === KAYFIYAT & SIFAT ===
97
- 'zamonaviy': 'modern contemporary chic', 'chiroyli': 'beautiful gorgeous stunning',
98
- 'hashamatli': 'luxurious opulent elegant', 'oddiy': 'simple minimalist clean',
99
- 'klassik': 'classic timeless elegant', 'romantik': 'romantic lovely dreamy',
100
- 'premium': 'premium luxury exclusive', 'professional': 'professional expert polished',
101
- 'sport': 'sport athletic fitness active', 'moda': 'fashion trendy stylish haute couture',
102
- 'ajoyib': 'amazing wonderful fantastic', 'zo\'r': 'cool awesome great',
103
- 'go\'zal': 'beautiful gorgeous pretty',
104
- // === HARAKATLAR ===
105
- 'kiyib': 'wearing dressed in', 'turgan': 'standing posing', 'yurgan': 'walking strolling',
106
- 'o\'tirgan': 'sitting relaxed', 'yugurgan': 'running jogging active',
107
- 'ko\'rsatib': 'showing demonstrating showcasing', 'sotib': 'buying shopping purchasing',
108
- // === SOVG'A & ULOV ===
109
- 'sovga': 'gift present wrapped surprise', 'mashina': 'car automobile vehicle luxury',
110
- 'avto': 'auto car vehicle', 'velosiped': 'bicycle cycling sport',
111
- // === GRAMMATIK SO'ZLAR ===
112
- 'va': 'and', 'bilan': 'with', 'uchun': 'for', 'da': 'in at', 'ning': 'of',
113
- 'ga': 'to towards', 'dan': 'from', 'lar': '', 'har': 'every each',
114
- 'eng': 'most best top', 'juda': 'very extremely highly',
115
- // === FILTER SO'ZLAR ===
116
- 'rasmini': '', 'chizib': '', 'ber': '', 'bersin': '', 'rasm': '', 'yasab': '', 'yasa': ''
117
- };
118
-
119
- function translatePrompt(uzText) {
120
- let words = uzText.toLowerCase().split(/\s+/);
121
- let translated = words.map(w => {
122
- for (const [uz, en] of Object.entries(uzToEn)) {
123
- if (w === uz || w.startsWith(uz)) return en;
124
- }
125
- return w; // Topilmasa o'zi qoladi
126
- }).filter(w => w.length > 0).join(' ');
127
- return translated + ', wide landscape banner, professional advertising photography, cinematic lighting, vivid colors, high detail, photorealistic, no text on image';
128
  }
129
 
130
  // AI Banner Shablon Promptlari
@@ -869,39 +799,50 @@ if (process.env.BOT_TOKEN && process.env.BOT_TOKEN !== 'YOUR_TELEGRAM_BOT_TOKEN'
869
  const ts1 = Date.now();
870
  const ts2 = ts1 + 12345;
871
  const encoded = encodeURIComponent(finalPrompt);
872
- const url1 = `https://image.pollinations.ai/prompt/${encoded}?width=1024&height=512&seed=${ts1}&nologo=true`;
873
- const url2 = `https://image.pollinations.ai/prompt/${encoded}?width=1024&height=512&seed=${ts2}&nologo=true`;
874
 
875
- await ctx.reply('⏳ AI 2 ta variant yaratmoqda... (20-40 soniya kutib turing)');
 
 
 
 
876
 
877
  try {
878
- // Ikkalasini parallel yuklab olish
879
- const [buffer1, buffer2] = await Promise.all([
880
  downloadImage(url1),
881
  downloadImage(url2)
882
  ]);
883
 
884
- // 1-variant
885
- const msg1 = await ctx.replyWithPhoto({ source: buffer1, filename: 'variant1.jpg' }, {
886
- caption: `🎨 *Variant 1*\nπŸ“ _"${originalPrompt}"_`,
887
- parse_mode: 'Markdown',
888
- ...Markup.inlineKeyboard([
889
- [Markup.button.callback('βœ… Shu rasmni tanlash', 'ai_pick_1')]
890
- ])
891
- });
892
-
893
- // 2-variant
894
- const msg2 = await ctx.replyWithPhoto({ source: buffer2, filename: 'variant2.jpg' }, {
895
- caption: `🎨 *Variant 2*\nπŸ“ _"${originalPrompt}"_`,
896
- parse_mode: 'Markdown',
897
- ...Markup.inlineKeyboard([
898
- [Markup.button.callback('βœ… Shu rasmni tanlash', 'ai_pick_2')]
899
- ])
900
- });
901
-
902
- // FileID larni olish
903
- const fileId1 = msg1.photo[msg1.photo.length - 1].file_id;
904
- const fileId2 = msg2.photo[msg2.photo.length - 1].file_id;
 
 
 
 
 
 
 
 
 
905
 
906
  appState[userId] = {
907
  step: 'ai_picking_variant',
@@ -1142,7 +1083,8 @@ if (process.env.BOT_TOKEN && process.env.BOT_TOKEN !== 'YOUR_TELEGRAM_BOT_TOKEN'
1142
  // AI Banner β€” Shablon bilan
1143
  if (state.step === 'ai_template_prompt') {
1144
  const userText = text.toLowerCase() === 'davom' ? '' : text;
1145
- const finalPrompt = state.templateBase + (userText ? ', ' + translatePrompt(userText) : '') + ', wide landscape banner, professional advertising, cinematic lighting, vivid colors, photorealistic, no text on image';
 
1146
  appState[userId] = { step: 'ai_generating', lastPrompt: text, templateBase: state.templateBase, finalPrompt };
1147
  await generateAIBannerVariants(ctx, finalPrompt, text || aiTemplates[state.templateKey]?.label || 'AI Banner');
1148
  return;
@@ -1150,7 +1092,8 @@ if (process.env.BOT_TOKEN && process.env.BOT_TOKEN !== 'YOUR_TELEGRAM_BOT_TOKEN'
1150
 
1151
  // AI Banner β€” Erkin yozish
1152
  if (state.step === 'ai_free_prompt_input') {
1153
- const finalPrompt = translatePrompt(text);
 
1154
  appState[userId] = { step: 'ai_generating', lastPrompt: text, finalPrompt };
1155
  await generateAIBannerVariants(ctx, finalPrompt, text);
1156
  return;
@@ -1158,31 +1101,10 @@ if (process.env.BOT_TOKEN && process.env.BOT_TOKEN !== 'YOUR_TELEGRAM_BOT_TOKEN'
1158
 
1159
  // AI Banner (eski usul backup)
1160
  if (state.step === 'ai_banner_prompt') {
1161
- const prompt = text;
1162
- appState[userId] = { step: 'ai_banner_waiting', lastPrompt: prompt };
1163
- const ts = Date.now();
1164
- const aiPrompt = translatePrompt(prompt);
1165
- const encodedPrompt = encodeURIComponent(aiPrompt);
1166
- const imgUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=1024&height=512&seed=${ts}&nologo=true`;
1167
-
1168
- ctx.reply('⏳ AI rasm yaratilmoqda... (10-30 soniya kutib turing)');
1169
-
1170
- try {
1171
- const buffer = await downloadImage(imgUrl);
1172
-
1173
- await ctx.replyWithPhoto({ source: buffer, filename: 'banner.jpg' }, {
1174
- caption: `🎨 *AI Banner tayyor!*\n\nπŸ“ _"${prompt}"_\n\nBu rasmni banner sifatida qo'shmoqchimisiz?`,
1175
- parse_mode: 'Markdown',
1176
- ...Markup.inlineKeyboard([
1177
- [Markup.button.callback('βœ… Tasdiqlash va qo\'shish', 'ai_save_last')],
1178
- [Markup.button.callback('πŸ”„ Qayta yaratish', 'ai_regenerate'), Markup.button.callback('❌ Bekor qilish', 'action_banners')]
1179
- ])
1180
- });
1181
- appState[userId].lastImageUrl = imgUrl;
1182
- } catch(e) {
1183
- ctx.reply('❌ Rasm yaratishda xatolik: ' + e.message + '\nQayta urining.', getMainMenu());
1184
- delete appState[userId];
1185
- }
1186
  return;
1187
  }
1188
 
 
6
  const dns = require('dns');
7
  const https = require('https');
8
  const http = require('http');
9
+ const { GoogleGenerativeAI } = require('@google/generative-ai');
10
+
11
+ const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
12
 
13
  dns.setDefaultResultOrder('ipv4first');
14
 
 
34
  });
35
  }
36
 
37
+ // AI yordamida (Gemini) promptni professional darajada kengaytirish
38
+ async function enhancePromptWithGemini(userText, templateBase = '') {
39
+ if (!process.env.GEMINI_API_KEY) {
40
+ return (templateBase ? templateBase + ', ' : '') + userText + ', professional advertising banner, highly detailed';
41
+ }
42
+ try {
43
+ const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
44
+ const prompt = `You are an expert AI image prompt engineer. The user wants to generate an e-commerce banner.
45
+ Convert the following short idea (often in Uzbek) into a highly detailed, professional, comma-separated English prompt for a text-to-image AI (like Flux or Midjourney).
46
+ Ensure it describes the visual elements beautifully. Include stylistic tags like: wide landscape banner, professional advertising photography, cinematic lighting, vivid colors, photorealistic, 8k resolution, highly detailed, no text on image.
47
+ If a template context is provided, combine it smoothly.
48
+ Template Context: ${templateBase || 'None'}
49
+ User's Idea: "${userText}"
50
+
51
+ Respond ONLY with the final English prompt string, and nothing else.`;
52
+ const result = await model.generateContent(prompt);
53
+ return result.response.text().trim();
54
+ } catch (e) {
55
+ console.error("Gemini error:", e);
56
+ return (templateBase ? templateBase + ', ' : '') + userText + ', professional advertising banner, cinematic lighting, photorealistic';
57
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  }
59
 
60
  // AI Banner Shablon Promptlari
 
799
  const ts1 = Date.now();
800
  const ts2 = ts1 + 12345;
801
  const encoded = encodeURIComponent(finalPrompt);
 
 
802
 
803
+ // Hozirgi eng maxsus fotorealistik model: FLUX
804
+ const url1 = `https://image.pollinations.ai/prompt/${encoded}?width=1024&height=512&seed=${ts1}&nologo=true&model=flux`;
805
+ const url2 = `https://image.pollinations.ai/prompt/${encoded}?width=1024&height=512&seed=${ts2}&nologo=true&model=flux`;
806
+
807
+ await ctx.reply('⏳ Gemini promptni kengaytirib, Flux 2 ta variant yaratmoqda... (Kuting, 20-40 soniya)');
808
 
809
  try {
810
+ // Ikkalasini xavfsiz yuklab olish (biri xato qilsa ham ikkinchisi ishlaydi)
811
+ const results = await Promise.allSettled([
812
  downloadImage(url1),
813
  downloadImage(url2)
814
  ]);
815
 
816
+ let msg1, msg2;
817
+ let fileId1 = null, fileId2 = null;
818
+
819
+ // 1-variant tekshiriladi
820
+ if (results[0].status === 'fulfilled') {
821
+ msg1 = await ctx.replyWithPhoto({ source: results[0].value, filename: 'variant1.jpg' }, {
822
+ caption: `🎨 *Variant 1*\nπŸ“ _"${originalPrompt}"_\n\nπŸ€– _Gemini kuchi bilan yozildi_`,
823
+ parse_mode: 'Markdown',
824
+ ...Markup.inlineKeyboard([[Markup.button.callback('βœ… Shu rasmni tanlash', 'ai_pick_1')]])
825
+ });
826
+ fileId1 = msg1.photo[msg1.photo.length - 1].file_id;
827
+ } else {
828
+ await ctx.reply('⚠️ Variant 1 ni yuklab olishda serverda xatolik yuz berdi.');
829
+ }
830
+
831
+ // 2-variant tekshiriladi
832
+ if (results[1].status === 'fulfilled') {
833
+ msg2 = await ctx.replyWithPhoto({ source: results[1].value, filename: 'variant2.jpg' }, {
834
+ caption: `🎨 *Variant 2*\nπŸ“ _"${originalPrompt}"_\n\nπŸ€– _Gemini kuchi bilan yozildi_`,
835
+ parse_mode: 'Markdown',
836
+ ...Markup.inlineKeyboard([[Markup.button.callback('βœ… Shu rasmni tanlash', 'ai_pick_2')]])
837
+ });
838
+ fileId2 = msg2.photo[msg2.photo.length - 1].file_id;
839
+ } else {
840
+ await ctx.reply('⚠️ Variant 2 ni yuklab olishda serverda xatolik yuz berdi.');
841
+ }
842
+
843
+ if (!fileId1 && !fileId2) {
844
+ throw new Error("Ikkala rasm ham yuklanmadi. Iltimos, qayta urining.");
845
+ }
846
 
847
  appState[userId] = {
848
  step: 'ai_picking_variant',
 
1083
  // AI Banner β€” Shablon bilan
1084
  if (state.step === 'ai_template_prompt') {
1085
  const userText = text.toLowerCase() === 'davom' ? '' : text;
1086
+ await ctx.reply('🧠 Gemini loyihangizni anglab, mukammal prompt tuzmoqda...');
1087
+ const finalPrompt = await enhancePromptWithGemini(userText, state.templateBase);
1088
  appState[userId] = { step: 'ai_generating', lastPrompt: text, templateBase: state.templateBase, finalPrompt };
1089
  await generateAIBannerVariants(ctx, finalPrompt, text || aiTemplates[state.templateKey]?.label || 'AI Banner');
1090
  return;
 
1092
 
1093
  // AI Banner β€” Erkin yozish
1094
  if (state.step === 'ai_free_prompt_input') {
1095
+ await ctx.reply('🧠 Gemini sizning fikringiz asosida mukammal prompt tuzmoqda...');
1096
+ const finalPrompt = await enhancePromptWithGemini(text);
1097
  appState[userId] = { step: 'ai_generating', lastPrompt: text, finalPrompt };
1098
  await generateAIBannerVariants(ctx, finalPrompt, text);
1099
  return;
 
1101
 
1102
  // AI Banner (eski usul backup)
1103
  if (state.step === 'ai_banner_prompt') {
1104
+ await ctx.reply('🧠 Gemini orqali prompt tayyorlanmoqda...');
1105
+ const finalPrompt = await enhancePromptWithGemini(text);
1106
+ appState[userId] = { step: 'ai_generating', lastPrompt: text, finalPrompt };
1107
+ await generateAIBannerVariants(ctx, finalPrompt, text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1108
  return;
1109
  }
1110
 
package-lock.json CHANGED
@@ -9,14 +9,25 @@
9
  "version": "1.0.0",
10
  "license": "ISC",
11
  "dependencies": {
 
12
  "cors": "^2.8.6",
13
  "dotenv": "^17.4.1",
14
  "express": "^5.2.1",
15
  "mongoose": "^9.4.1",
16
  "multer": "^2.1.1",
 
17
  "telegraf": "^4.16.3"
18
  }
19
  },
 
 
 
 
 
 
 
 
 
20
  "node_modules/@mongodb-js/saslprep": {
21
  "version": "1.4.6",
22
  "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz",
 
9
  "version": "1.0.0",
10
  "license": "ISC",
11
  "dependencies": {
12
+ "@google/generative-ai": "^0.24.1",
13
  "cors": "^2.8.6",
14
  "dotenv": "^17.4.1",
15
  "express": "^5.2.1",
16
  "mongoose": "^9.4.1",
17
  "multer": "^2.1.1",
18
+ "node-fetch": "^2.7.0",
19
  "telegraf": "^4.16.3"
20
  }
21
  },
22
+ "node_modules/@google/generative-ai": {
23
+ "version": "0.24.1",
24
+ "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz",
25
+ "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==",
26
+ "license": "Apache-2.0",
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ }
30
+ },
31
  "node_modules/@mongodb-js/saslprep": {
32
  "version": "1.4.6",
33
  "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz",
package.json CHANGED
@@ -11,6 +11,7 @@
11
  "license": "ISC",
12
  "type": "commonjs",
13
  "dependencies": {
 
14
  "cors": "^2.8.6",
15
  "dotenv": "^17.4.1",
16
  "express": "^5.2.1",
 
11
  "license": "ISC",
12
  "type": "commonjs",
13
  "dependencies": {
14
+ "@google/generative-ai": "^0.24.1",
15
  "cors": "^2.8.6",
16
  "dotenv": "^17.4.1",
17
  "express": "^5.2.1",