opencode-pollinations-plugin 6.4.6 → 6.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/locales/de.json +17 -2
- package/dist/locales/en.json +17 -2
- package/dist/locales/es.json +17 -2
- package/dist/locales/fr.json +17 -2
- package/dist/locales/it.json +17 -2
- package/dist/locales/zh.json +17 -2
- package/dist/server/quota.js +10 -10
- package/dist/server/toast.d.ts +1 -0
- package/dist/server/toast.js +7 -2
- package/dist/tools/index.js +8 -0
- package/dist/tools/pollinations/gen_edit_image_free.js +4 -3
- package/dist/tools/pollinations/gen_video_free.js +3 -2
- package/dist/tools/pollinations/image_enhancer.d.ts +2 -0
- package/dist/tools/pollinations/image_enhancer.js +62 -0
- package/dist/tools/pollinations/image_upscaler.d.ts +2 -0
- package/dist/tools/pollinations/image_upscaler.js +61 -0
- package/dist/tools/pollinations/imgtools/clients.d.ts +11 -0
- package/dist/tools/pollinations/imgtools/clients.js +182 -0
- package/dist/tools/pollinations/imgtools/config.d.ts +42 -0
- package/dist/tools/pollinations/imgtools/config.js +81 -0
- package/dist/tools/pollinations/imgtools/crypto.d.ts +4 -0
- package/dist/tools/pollinations/imgtools/crypto.js +34 -0
- package/dist/tools/pollinations/imgtools/helpers.d.ts +24 -0
- package/dist/tools/pollinations/imgtools/helpers.js +111 -0
- package/dist/tools/pollinations/object_remover.d.ts +2 -0
- package/dist/tools/pollinations/object_remover.js +62 -0
- package/dist/tools/power/remove_background.js +16 -60
- package/package.json +1 -1
package/dist/locales/de.json
CHANGED
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"infos": {
|
|
117
117
|
"title": "## 🍯💚 POLLINATIONS OPENCODE PLUGIN 💚🍯\n\nWillkommen **{name}** beim Pollinations OpenCode Plugin!\n\nDieses Plugin ermöglicht es, Code und Bilder zu generieren, Videos zu analysieren und nahtlos mit den besten KI-Modellen zu interagieren. Greifen Sie über Chat, Refactoring oder Terminal auf erstklassige LLMs zu.",
|
|
118
118
|
"features_title": "**Was dieses Plugin Ihnen bietet! :**",
|
|
119
|
-
"features_free": "**🛠️ Kostenlose Tools (Immer verfugbar):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuals und Dev-Tools.\n- `remove_background` : Hintergrundentfernung.\n- `extract_frames` / `extract_audio` : Medien-Extraktion.\n- `file_to_url` : Online-Hosting.\n- `gen_edit_image_free` : Kostenlose Bild-Generierung & Bearbeitung (~20/Tag).\n- `gen_video_free` : Kostenlose Video-Generierung (~5/Tag).",
|
|
119
|
+
"features_free": "**🛠️ Kostenlose Tools (Immer verfugbar):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuals und Dev-Tools.\n- `remove_background` : Hintergrundentfernung.\n- `extract_frames` / `extract_audio` : Medien-Extraktion.\n- `file_to_url` : Online-Hosting.\n- `gen_edit_image_free` : Kostenlose Bild-Generierung & Bearbeitung (~20/Tag).\n- `gen_video_free` : Kostenlose Video-Generierung (~5/Tag).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
|
|
120
120
|
"features_pro": "**💎 Pollinations Tools (Premium - Automatisiert mit Key):**\n- `polli_gen_image` : Bildgen (Flux, Gemini) + Image-to-Image.\n- `polli_gen_video` : Text-to-Video / Image-to-Video (Veo, Wan...).\n- `polli_gen_audio`/`polli_stt` : Whisper STT, ElevenLabs TTS.\n- `polli_gen_music` : Generative Musik-Engine.\n- `polli_web_search` : Verbundene Websuche.",
|
|
121
121
|
"features_config": "- **Granulare Konfiguration**, Modus- und Token-Management, Kostenschutz...",
|
|
122
122
|
"tiers_title": "> **Dein Quest Pollen Refill:** {emoji} {tier}",
|
|
@@ -445,11 +445,26 @@
|
|
|
445
445
|
"polli_login": {
|
|
446
446
|
"desc": "🔐 Startet einen schnellen Pollinations-Login (Device Flow) für den Benutzer. Zeigt einen kurzen Code + URL; der Benutzer meldet sich im Browser mit GitHub an, und das Plugin verbindet sich AUTOMATISCH nach Genehmigung (ein Toast bestätigt). Kein API-Schlüssel zum Ausführen nötig. Verwende dies, wenn der Benutzer sein Pollinations-Konto einfach verbinden möchte, ohne einen Schlüssel zu kopieren. Für einen permanenten Schlüssel sage ihm, `/poll connect sk_...` zu verwenden.",
|
|
447
447
|
"arg_wait": "Wenn true, wartet auf den Abschluss eines laufenden Logins und gibt das Endergebnis zurück (✅ verbunden / abgelaufen). Rufe zuerst ohne wait auf, um den Code anzuzeigen, dann erneut mit wait:true, nachdem du den Benutzer zur Autorisierung aufgefordert hast."
|
|
448
|
+
},
|
|
449
|
+
"object_remover": {
|
|
450
|
+
"file_not_found": "❗ File not found: {path}",
|
|
451
|
+
"no_result": "❗ No result received.",
|
|
452
|
+
"error": "❗ Error: {error}"
|
|
453
|
+
},
|
|
454
|
+
"image_upscaler": {
|
|
455
|
+
"file_not_found": "❗ File not found: {path}",
|
|
456
|
+
"no_result": "❗ No result received.",
|
|
457
|
+
"error": "❗ Error: {error}"
|
|
458
|
+
},
|
|
459
|
+
"image_enhancer": {
|
|
460
|
+
"file_not_found": "❗ File not found: {path}",
|
|
461
|
+
"no_result": "❗ No result received.",
|
|
462
|
+
"error": "❗ Error: {error}"
|
|
448
463
|
}
|
|
449
464
|
},
|
|
450
465
|
"connect_response": {
|
|
451
466
|
"title_key": "## 🍯💚 POLLINATIONS PLUGIN VERBUNDEN 💚🍯\n\nWillkommen **{name}** beim Pollinations Agent Plugin!\n**Aktueller Modus**: `{mode}`",
|
|
452
|
-
"tools_intro": "### 🚀 Multimedia Tools\n\nDieses Plugin ermöglicht es, Code und Bilder zu generieren, Videos zu analysieren und mit den besten KI-Modellen zu interagieren.\n\n**🛠️ Kostenlose Tools:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (Bild-Gen & Edit, ~20/Tag)\n- `gen_video_free` (Video-Gen, ~5/Tag)\n\n**💎 Premium Tools:**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
467
|
+
"tools_intro": "### 🚀 Multimedia Tools\n\nDieses Plugin ermöglicht es, Code und Bilder zu generieren, Videos zu analysieren und mit den besten KI-Modellen zu interagieren.\n\n**🛠️ Kostenlose Tools:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (Bild-Gen & Edit, ~20/Tag)\n- `gen_video_free` (Video-Gen, ~5/Tag)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Premium Tools:**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
453
468
|
"terminal_cmds": "### 💻 Terminal Befehle\nVerwenden Sie `/poll help` um alle Befehle anzuzeigen.",
|
|
454
469
|
"resources": "### 📚 Ressourcen\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Community beitreten\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
|
|
455
470
|
"free_models_success": "Verfügbare Modelle: {models}",
|
package/dist/locales/en.json
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"infos": {
|
|
116
116
|
"title": "## 🍯💚 POLLINATIONS OPENCODE PLUGIN 💚🍯\n\nWelcome **{name}** to the Pollinations OpenCode plugin!\n\nThis plugin empowers you to generate code, images, analyze videos, and interact with top-tier AI models seamlessly within your dev environment. Access state-of-the-art LLMs via chat, refactoring, or terminal.",
|
|
117
117
|
"features_title": "**What this plugin brings to you! :**",
|
|
118
|
-
"features_free": "**🛠️ Integrated Free Tools (Always Available):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuals and dev utilities.\n- `remove_background` : Native background removal.\n- `extract_frames` / `extract_audio` : Fast media extraction.\n- `file_to_url` : Instant online hosting of local files.\n- `gen_edit_image_free` : Free image gen & edit (~20/day).\n- `gen_video_free` : Free video gen (~5/day).",
|
|
118
|
+
"features_free": "**🛠️ Integrated Free Tools (Always Available):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuals and dev utilities.\n- `remove_background` : Native background removal.\n- `extract_frames` / `extract_audio` : Fast media extraction.\n- `file_to_url` : Instant online hosting of local files.\n- `gen_edit_image_free` : Free image gen & edit (~20/day).\n- `gen_video_free` : Free video gen (~5/day).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
|
|
119
119
|
"features_pro": "**💎 Pollinations Tools (Premium - Automated with Key) :**\n- `polli_gen_image` : Image gen (Flux, Seedream, Gemini) + Image-to-Image.\n- `polli_gen_video` : Text-to-video / Image-to-video (Veo, Wan, LTX...).\n- `polli_gen_audio`/`polli_stt` : Whisper STT, ElevenLabs TTS.\n- `polli_gen_music` : Generative music engine.\n- `polli_web_search` : Connected web search.",
|
|
120
120
|
"features_config": "- **Granular configuration**, mode and token management, tool cost protections...",
|
|
121
121
|
"tiers_title": "> **Your Quest Pollen refill:** {emoji} {tier}",
|
|
@@ -444,11 +444,26 @@
|
|
|
444
444
|
"polli_login": {
|
|
445
445
|
"desc": "🔐 Start a quick Pollinations login (device flow) for the user. Shows a short code + URL; the user signs in with GitHub in their browser, and the plugin connects AUTOMATICALLY once approved (a toast confirms). No API key needed to run this. Use when the user wants to connect their Pollinations account easily, without copy-pasting a key. For a permanent key instead, tell them to use `/poll connect sk_...`.",
|
|
446
446
|
"arg_wait": "If true, waits for an in-progress login to complete and returns the final result (✅ connected / expired). Call once without wait to show the code, then again with wait:true after telling the user to authorize."
|
|
447
|
+
},
|
|
448
|
+
"object_remover": {
|
|
449
|
+
"file_not_found": "❗ File not found: {path}",
|
|
450
|
+
"no_result": "❗ No result received.",
|
|
451
|
+
"error": "❗ Error: {error}"
|
|
452
|
+
},
|
|
453
|
+
"image_upscaler": {
|
|
454
|
+
"file_not_found": "❗ File not found: {path}",
|
|
455
|
+
"no_result": "❗ No result received.",
|
|
456
|
+
"error": "❗ Error: {error}"
|
|
457
|
+
},
|
|
458
|
+
"image_enhancer": {
|
|
459
|
+
"file_not_found": "❗ File not found: {path}",
|
|
460
|
+
"no_result": "❗ No result received.",
|
|
461
|
+
"error": "❗ Error: {error}"
|
|
447
462
|
}
|
|
448
463
|
},
|
|
449
464
|
"connect_response": {
|
|
450
465
|
"title_key": "## 🍯💚 POLLINATIONS PLUGIN CONNECTED 💚🍯\n\nWelcome **{name}** to the Pollinations Agent plugin!\n**Current Mode**: `{mode}`",
|
|
451
|
-
"tools_intro": "### 🚀 Multimedia Tools Integrated to Development\n\nThis plugin allows you to generate code, images, analyze videos and interact with the best AI models seamlessly. Access state-of-the-art LLMs via chat, refactoring, or terminal.\n\n**What this plugin brings to you:**\n\n**🛠️ Free Integrated Tools (Always Available):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (image gen & edit, ~20/day)\n- `gen_video_free` (video gen, ~5/day)\n\n**💎 Pollinations Tools (Premium):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
466
|
+
"tools_intro": "### 🚀 Multimedia Tools Integrated to Development\n\nThis plugin allows you to generate code, images, analyze videos and interact with the best AI models seamlessly. Access state-of-the-art LLMs via chat, refactoring, or terminal.\n\n**What this plugin brings to you:**\n\n**🛠️ Free Integrated Tools (Always Available):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (image gen & edit, ~20/day)\n- `gen_video_free` (video gen, ~5/day)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Pollinations Tools (Premium):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
452
467
|
"terminal_cmds": "### 💻 Terminal Commands\nUse `/poll help` to see all commands. Try `/poll models` to discover the catalog or `/poll usage` to tracking remaining pollen.",
|
|
453
468
|
"resources": "### 📚 Resources\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Join the Community\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
|
|
454
469
|
"free_models_success": "Available free models: {models}",
|
package/dist/locales/es.json
CHANGED
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"infos": {
|
|
117
117
|
"title": "## 🍯💚 PLUGIN POLLINATIONS OPENCODE 💚🍯\n\n¡Bienvenido **{name}** al plugin Pollinations OpenCode!\n\nEste plugin te permite generar código, imágenes, analizar videos e interactuar con los mejores modelos de IA sin problemas desde tu entorno de desarrollo. Accede a LLMs avanzados a través del chat, reestructuración o terminal.",
|
|
118
118
|
"features_title": "**¡Lo que este plugin trae para ti!:**",
|
|
119
|
-
"features_free": "**🛠️ Herramientas Gratuitas (Siempre Disponibles):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuales y utilidades.\n- `remove_background` : Eliminacion de fondo nativa.\n- `extract_frames` / `extract_audio` : Extraccion rapida de medios.\n- `file_to_url` : Alojamiento online instantaneo.\n- `gen_edit_image_free` : Gen y edicion de imagenes gratis (~20/dia).\n- `gen_video_free` : Gen de video gratis (~5/dia).",
|
|
119
|
+
"features_free": "**🛠️ Herramientas Gratuitas (Siempre Disponibles):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuales y utilidades.\n- `remove_background` : Eliminacion de fondo nativa.\n- `extract_frames` / `extract_audio` : Extraccion rapida de medios.\n- `file_to_url` : Alojamiento online instantaneo.\n- `gen_edit_image_free` : Gen y edicion de imagenes gratis (~20/dia).\n- `gen_video_free` : Gen de video gratis (~5/dia).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
|
|
120
120
|
"features_pro": "**💎 Herramientas Pollinations (Premium - Automatizado con Clave):**\n- `polli_gen_image` : Gen de imágenes (Flux, Gemini) + Image-to-Image.\n- `polli_gen_video` : Text-to-video / Image-to-video (Veo, Wan...).\n- `polli_gen_audio`/`polli_stt` : Whisper STT, ElevenLabs TTS.\n- `polli_gen_music` : Motor de música generativa.\n- `polli_web_search` : Búsqueda web contectada.",
|
|
121
121
|
"features_config": "- **Configuración granular**, gestión de modos y tokens, protección de costos...",
|
|
122
122
|
"tiers_title": "> **Tu refill Quest Pollen:** {emoji} {tier}",
|
|
@@ -445,11 +445,26 @@
|
|
|
445
445
|
"polli_login": {
|
|
446
446
|
"desc": "🔐 Inicia un acceso rápido a Pollinations (device flow) para el usuario. Muestra un código corto + URL; el usuario inicia sesión con GitHub en su navegador, y el plugin se conecta AUTOMÁTICAMENTE tras aprobar (un aviso confirma). No se necesita clave API para ejecutarlo. Úsalo cuando el usuario quiera conectar su cuenta de Pollinations fácilmente, sin copiar y pegar una clave. Para una clave permanente, dile que use `/poll connect sk_...`.",
|
|
447
447
|
"arg_wait": "Si es true, espera a que termine un inicio de sesión en curso y devuelve el resultado final (✅ conectado / caducado). Llama primero sin wait para mostrar el código, luego de nuevo con wait:true tras pedir al usuario que autorice."
|
|
448
|
+
},
|
|
449
|
+
"object_remover": {
|
|
450
|
+
"file_not_found": "❗ File not found: {path}",
|
|
451
|
+
"no_result": "❗ No result received.",
|
|
452
|
+
"error": "❗ Error: {error}"
|
|
453
|
+
},
|
|
454
|
+
"image_upscaler": {
|
|
455
|
+
"file_not_found": "❗ File not found: {path}",
|
|
456
|
+
"no_result": "❗ No result received.",
|
|
457
|
+
"error": "❗ Error: {error}"
|
|
458
|
+
},
|
|
459
|
+
"image_enhancer": {
|
|
460
|
+
"file_not_found": "❗ File not found: {path}",
|
|
461
|
+
"no_result": "❗ No result received.",
|
|
462
|
+
"error": "❗ Error: {error}"
|
|
448
463
|
}
|
|
449
464
|
},
|
|
450
465
|
"connect_response": {
|
|
451
466
|
"title_key": "## 🍯💚 PLUGIN POLLINATIONS CONECTADO 💚🍯\n\n¡Bienvenido **{name}** al plugin de Pollinations Agent!\n**Modo actual**: `{mode}`",
|
|
452
|
-
"tools_intro": "### 🚀 Herramientas Multimedia Integradas\n\nEste plugin te permite generar código, imágenes, analizar videos e interactuar con los mejores modelos de IA sin problemas. Accede a los LLM más avanzados a través del chat, reestructuración o terminal.\n\n**Lo que este plugin trae para ti:**\n\n**🛠️ Herramientas gratuitas (Siempre Disponibles):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (gen & edición, ~20/día)\n- `gen_video_free` (gen video, ~5/día)\n\n**💎 Herramientas Pollinations (Premium):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
467
|
+
"tools_intro": "### 🚀 Herramientas Multimedia Integradas\n\nEste plugin te permite generar código, imágenes, analizar videos e interactuar con los mejores modelos de IA sin problemas. Accede a los LLM más avanzados a través del chat, reestructuración o terminal.\n\n**Lo que este plugin trae para ti:**\n\n**🛠️ Herramientas gratuitas (Siempre Disponibles):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (gen & edición, ~20/día)\n- `gen_video_free` (gen video, ~5/día)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Herramientas Pollinations (Premium):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
453
468
|
"terminal_cmds": "### 💻 Comandos de Terminal\nUsa `/poll help` para ver todos los comandos. Prueba `/poll models` para descubrir el catálogo o `/poll usage` para seguir tu consumo de pollen.",
|
|
454
469
|
"resources": "### 📚 Recursos\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Unete a la comunidad\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
|
|
455
470
|
"free_models_success": "Modelos gratuitos disponibles: {models}",
|
package/dist/locales/fr.json
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"infos": {
|
|
116
116
|
"title": "## 🍯💚 POLLINATIONS OPENCODE PLUGIN 💚🍯\n\nBienvenue **{name}** sur le plugin Pollinations pour OpenCode !\n\nCe plugin vous permet de générer du code, des images, d'analyser des vidéos et d'interagir avec les meilleurs modèles d'Intelligence Artificielle de manière totalement transparente et intégrée à votre environnement de travail. Accédez aux capacités des LLMs de pointe, que ce soit via des requêtes de chat, la refonte de votre base de code, ou directement dans le terminal.",
|
|
117
117
|
"features_title": "**Ce Que ce plugin vous apporte en plus ! :**",
|
|
118
|
-
"features_free": "**🛠️ Outils Gratuits Intégrés (Toujours disponibles) :**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Outils visuels et dev.\n- `remove_background` : Détourage d'image natif.\n- `extract_frames` / `extract_audio` : Extraction rapide de contenu média.\n- `file_to_url` : Hébergement instantané de vos fichiers locaux en ligne.\n- `gen_edit_image_free` : Génération & édition d'images gratuite (~20/jour).\n- `gen_video_free` : Génération vidéo gratuite (~5/jour).",
|
|
118
|
+
"features_free": "**🛠️ Outils Gratuits Intégrés (Toujours disponibles) :**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Outils visuels et dev.\n- `remove_background` : Détourage d'image natif.\n- `extract_frames` / `extract_audio` : Extraction rapide de contenu média.\n- `file_to_url` : Hébergement instantané de vos fichiers locaux en ligne.\n- `gen_edit_image_free` : Génération & édition d'images gratuite (~20/jour).\n- `gen_video_free` : Génération vidéo gratuite (~5/jour).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
|
|
119
119
|
"features_pro": "**💎 Outils Pollinations (Premium - Automatisés avec votre Clé) :**\n- `polli_gen_image` : Génération d'images (Flux, Seedream, Gemini) + support Image-to-Image.\n- `polli_gen_video` : Génération vidéo text-to-video / image-to-video (Veo, Wan, LTX...).\n- `polli_gen_audio`/`polli_stt` : Transcription Whisper, Text-to-Speech ElevenLabs.\n- `polli_gen_music` : Moteur de musique générative.\n- `polli_web_search` : Recherche connectée pour étendre la base de contexte de l'agent.",
|
|
120
120
|
"features_config": "- **Une configuration granulaire**, des modes de gestions de vos tokens et de vos outils, de la sécurisation des coûts des outils consommant du pollen...",
|
|
121
121
|
"tiers_title": "> **Votre refill Quest Pollen :** {emoji} {tier}",
|
|
@@ -444,11 +444,26 @@
|
|
|
444
444
|
"polli_login": {
|
|
445
445
|
"desc": "🔐 Démarre une connexion Pollinations rapide (device flow) pour l'utilisateur. Affiche un code court + une URL ; l'utilisateur se connecte avec GitHub dans son navigateur, et le plugin se connecte AUTOMATIQUEMENT une fois approuvé (un toast confirme). Aucune clé API nécessaire pour lancer ceci. À utiliser quand l'utilisateur veut connecter son compte Pollinations facilement, sans copier-coller de clé. Pour une clé permanente, dites-lui d'utiliser `/poll connect sk_...`.",
|
|
446
446
|
"arg_wait": "Si true, attend la fin d'une connexion en cours et retourne le résultat final (✅ connecté / expiré). Appelez d'abord sans wait pour afficher le code, puis à nouveau avec wait:true après avoir dit à l'utilisateur d'autoriser."
|
|
447
|
+
},
|
|
448
|
+
"object_remover": {
|
|
449
|
+
"file_not_found": "❗ File not found: {path}",
|
|
450
|
+
"no_result": "❗ No result received.",
|
|
451
|
+
"error": "❗ Error: {error}"
|
|
452
|
+
},
|
|
453
|
+
"image_upscaler": {
|
|
454
|
+
"file_not_found": "❗ File not found: {path}",
|
|
455
|
+
"no_result": "❗ No result received.",
|
|
456
|
+
"error": "❗ Error: {error}"
|
|
457
|
+
},
|
|
458
|
+
"image_enhancer": {
|
|
459
|
+
"file_not_found": "❗ File not found: {path}",
|
|
460
|
+
"no_result": "❗ No result received.",
|
|
461
|
+
"error": "❗ Error: {error}"
|
|
447
462
|
}
|
|
448
463
|
},
|
|
449
464
|
"connect_response": {
|
|
450
465
|
"title_key": "## 🍯💚 PLUGIN POLLINATIONS CONNECTÉ 💚🍯\n\nBienvenue **{name}** dans le plugin Pollinations Agent !\n**Mode actuel** : `{mode}`",
|
|
451
|
-
"tools_intro": "### 🚀 Outils Multimédias Intégrés au Développement\n\nCe plugin vous permet de générer du code, des images, d'analyser des vidéos et d'interagir avec les meilleurs modèles IA. Accédez aux meilleurs LLMs.\n\n**Ce que ce plugin vous apporte :**\n\n**🛠️ Outils Libres Intégrés :**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (génération & édition, ~20/jour)\n- `gen_video_free` (génération vidéo, ~5/jour)\n\n**💎 Outils Pollinations (Premium) :**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
466
|
+
"tools_intro": "### 🚀 Outils Multimédias Intégrés au Développement\n\nCe plugin vous permet de générer du code, des images, d'analyser des vidéos et d'interagir avec les meilleurs modèles IA. Accédez aux meilleurs LLMs.\n\n**Ce que ce plugin vous apporte :**\n\n**🛠️ Outils Libres Intégrés :**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (génération & édition, ~20/jour)\n- `gen_video_free` (génération vidéo, ~5/jour)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Outils Pollinations (Premium) :**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
452
467
|
"terminal_cmds": "### 💻 Commandes par Terminal\nUtilisez `/poll help` pour voir toutes les commandes. Essayez `/poll models` pour découvrir le catalogue ou `/poll usage` pour suivre votre pollen restant.",
|
|
453
468
|
"resources": "### 📚 Ressources\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Rejoindre la communauté\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
|
|
454
469
|
"free_models_success": "Modèles gratuits disponibles : {models}",
|
package/dist/locales/it.json
CHANGED
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"infos": {
|
|
117
117
|
"title": "## 🍯💚 PLUGIN POLLINATIONS OPENCODE 💚🍯\n\nBenvenuto **{name}** nel plugin OpenCode di Pollinations!\n\nQuesto plugin ti consente di generare codice, immagini, analizzare video e interagire senza problemi con i migliori modelli di IA. Accedi agli LLM più avanzati tramite chat, refactoring o terminale.",
|
|
118
118
|
"features_title": "**Cosa ti offre questo plugin! :**",
|
|
119
|
-
"features_free": "**🛠️ Strumenti Gratuiti (Sempre Disponibili):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visual e dev.\n- `remove_background` : Rimozione sfondo nativa.\n- `extract_frames` / `extract_audio` : Estrazione media rapida.\n- `file_to_url` : Hosting online istantaneo.\n- `gen_edit_image_free` : Gen e modifica immagini gratis (~20/giorno).\n- `gen_video_free` : Gen video gratis (~5/giorno).",
|
|
119
|
+
"features_free": "**🛠️ Strumenti Gratuiti (Sempre Disponibili):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visual e dev.\n- `remove_background` : Rimozione sfondo nativa.\n- `extract_frames` / `extract_audio` : Estrazione media rapida.\n- `file_to_url` : Hosting online istantaneo.\n- `gen_edit_image_free` : Gen e modifica immagini gratis (~20/giorno).\n- `gen_video_free` : Gen video gratis (~5/giorno).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
|
|
120
120
|
"features_pro": "**💎 Strumenti Pollinations (Premium - Automatizzato con Chiave):**\n- `polli_gen_image` : Gen immagine (Flux, Gemini) + Image-to-Image.\n- `polli_gen_video` : Text-to-Video / Image-to-Video (Veo, Wan...).\n- `polli_gen_audio`/`polli_stt` : Whisper STT, ElevenLabs TTS.\n- `polli_gen_music` : Motore di musica generativa.\n- `polli_web_search` : Ricerca web connessa.",
|
|
121
121
|
"features_config": "- **Configurazione granulare**, gestione di modalità/token, protezioni costi...",
|
|
122
122
|
"tiers_title": "> **Il tuo refill Quest Pollen:** {emoji} {tier}",
|
|
@@ -445,11 +445,26 @@
|
|
|
445
445
|
"polli_login": {
|
|
446
446
|
"desc": "🔐 Avvia un accesso rapido a Pollinations (device flow) per l'utente. Mostra un codice breve + URL; l'utente accede con GitHub nel browser, e il plugin si connette AUTOMATICAMENTE una volta approvato (un toast conferma). Nessuna chiave API necessaria per eseguirlo. Usalo quando l'utente vuole connettere il suo account Pollinations facilmente, senza copiare e incollare una chiave. Per una chiave permanente, digli di usare `/poll connect sk_...`.",
|
|
447
447
|
"arg_wait": "Se true, attende il completamento di un accesso in corso e restituisce il risultato finale (✅ connesso / scaduto). Chiama prima senza wait per mostrare il codice, poi di nuovo con wait:true dopo aver chiesto all'utente di autorizzare."
|
|
448
|
+
},
|
|
449
|
+
"object_remover": {
|
|
450
|
+
"file_not_found": "❗ File not found: {path}",
|
|
451
|
+
"no_result": "❗ No result received.",
|
|
452
|
+
"error": "❗ Error: {error}"
|
|
453
|
+
},
|
|
454
|
+
"image_upscaler": {
|
|
455
|
+
"file_not_found": "❗ File not found: {path}",
|
|
456
|
+
"no_result": "❗ No result received.",
|
|
457
|
+
"error": "❗ Error: {error}"
|
|
458
|
+
},
|
|
459
|
+
"image_enhancer": {
|
|
460
|
+
"file_not_found": "❗ File not found: {path}",
|
|
461
|
+
"no_result": "❗ No result received.",
|
|
462
|
+
"error": "❗ Error: {error}"
|
|
448
463
|
}
|
|
449
464
|
},
|
|
450
465
|
"connect_response": {
|
|
451
466
|
"title_key": "## 🍯💚 PLUGIN POLLINATIONS CONNESSO 💚🍯\n\nBenvenuto **{name}** al plugin Pollinations Agent!\n**Modalità attuale**: `{mode}`",
|
|
452
|
-
"tools_intro": "### 🚀 Strumenti Multimediali\n\nQuesto plugin ti permette di generare codice, immagini, analizzare video.\n\n**🛠️ Strumenti Gratuiti:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (gen & modifica, ~20/giorno)\n- `gen_video_free` (gen video, ~5/giorno)\n\n**💎 Strumenti Premium:**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
467
|
+
"tools_intro": "### 🚀 Strumenti Multimediali\n\nQuesto plugin ti permette di generare codice, immagini, analizzare video.\n\n**🛠️ Strumenti Gratuiti:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (gen & modifica, ~20/giorno)\n- `gen_video_free` (gen video, ~5/giorno)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Strumenti Premium:**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
453
468
|
"terminal_cmds": "### 💻 Comandi del Terminale\nUsa `/poll help` per visualizzare tutti i comandi disponibili.",
|
|
454
469
|
"resources": "### 📚 Risorse\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Unisciti alla community\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
|
|
455
470
|
"free_models_success": "Modelli disponibili: {models}",
|
package/dist/locales/zh.json
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"infos": {
|
|
116
116
|
"title": "## 🍯💚 POLLINATIONS OPENCODE 插件 💚🍯\n\n欢迎 **{name}** 使用 Pollinations OpenCode 插件!\n\n本插件让你能够在开发环境中无缝生成代码、图像、分析视频,并与顶级 AI 模型交互。通过聊天、重构或终端访问最先进的 LLM。",
|
|
117
117
|
"features_title": "**本插件为你带来的功能:**",
|
|
118
|
-
"features_free": "**🛠️ 免费工具 (始终可用):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : 视觉和开发工具.\n- `remove_background` : 原生背景移除.\n- `extract_frames` / `extract_audio` : 快速媒体提取.\n- `file_to_url` : 即时在线托管.\n- `gen_edit_image_free` : 免费图像生成和编辑 (~20/天).\n- `gen_video_free` : 免费视频生成 (~5/天).",
|
|
118
|
+
"features_free": "**🛠️ 免费工具 (始终可用):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : 视觉和开发工具.\n- `remove_background` : 原生背景移除.\n- `extract_frames` / `extract_audio` : 快速媒体提取.\n- `file_to_url` : 即时在线托管.\n- `gen_edit_image_free` : 免费图像生成和编辑 (~20/天).\n- `gen_video_free` : 免费视频生成 (~5/天).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
|
|
119
119
|
"features_pro": "**💎 Pollinations 工具 (高级 - 使用密钥自动化):**\n- `polli_gen_image`:图像生成 (Flux, Seedream, Gemini) + 图生图。\n- `polli_gen_video`:文生视频 / 图生视频 (Veo, Wan, LTX...)。\n- `polli_gen_audio`/`polli_stt`:Whisper 语音转文字, ElevenLabs 文字转语音。\n- `polli_gen_music`:生成式音乐引擎。\n- `polli_web_search`:联网搜索。",
|
|
120
120
|
"features_config": "- **精细化配置**、模式与令牌管理、工具费用保护...",
|
|
121
121
|
"tiers_title": "> **你的 Quest Pollen 补充:** {emoji} {tier}",
|
|
@@ -444,11 +444,26 @@
|
|
|
444
444
|
"polli_login": {
|
|
445
445
|
"desc": "🔐 为用户启动快速 Pollinations 登录 (设备流程)。显示一个短代码 + URL;用户在浏览器中用 GitHub 登录,批准后插件会自动连接 (有提示确认)。运行此功能无需 API 密钥。当用户想要轻松连接其 Pollinations 账户而无需复制粘贴密钥时使用。如需永久密钥,请告诉他们使用 `/poll connect sk_...`。",
|
|
446
446
|
"arg_wait": "如果为 true,则等待进行中的登录完成并返回最终结果 (✅ 已连接 / 已过期)。先不带 wait 调用以显示代码,然后在告知用户授权后再带 wait:true 调用。"
|
|
447
|
+
},
|
|
448
|
+
"object_remover": {
|
|
449
|
+
"file_not_found": "❗ File not found: {path}",
|
|
450
|
+
"no_result": "❗ No result received.",
|
|
451
|
+
"error": "❗ Error: {error}"
|
|
452
|
+
},
|
|
453
|
+
"image_upscaler": {
|
|
454
|
+
"file_not_found": "❗ File not found: {path}",
|
|
455
|
+
"no_result": "❗ No result received.",
|
|
456
|
+
"error": "❗ Error: {error}"
|
|
457
|
+
},
|
|
458
|
+
"image_enhancer": {
|
|
459
|
+
"file_not_found": "❗ File not found: {path}",
|
|
460
|
+
"no_result": "❗ No result received.",
|
|
461
|
+
"error": "❗ Error: {error}"
|
|
447
462
|
}
|
|
448
463
|
},
|
|
449
464
|
"connect_response": {
|
|
450
465
|
"title_key": "## 🍯💚 POLLINATIONS 插件已连接 💚🍯\n\n欢迎 **{name}** 使用 Pollinations 代理插件!\n**当前模式**:`{mode}`",
|
|
451
|
-
"tools_intro": "### 🚀 集成到开发中的多媒体工具\n\n本插件让你能够无缝生成代码、图像、分析视频并与最佳 AI 模型交互。通过聊天、重构或终端访问最先进的 LLM。\n\n**本插件为你带来:**\n\n**🛠️ 免费集成工具:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (图像生成和编辑, ~20/天)\n- `gen_video_free` (视频生成, ~5/天)\n\n**💎 Pollinations 工具 (高级):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
466
|
+
"tools_intro": "### 🚀 集成到开发中的多媒体工具\n\n本插件让你能够无缝生成代码、图像、分析视频并与最佳 AI 模型交互。通过聊天、重构或终端访问最先进的 LLM。\n\n**本插件为你带来:**\n\n**🛠️ 免费集成工具:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (图像生成和编辑, ~20/天)\n- `gen_video_free` (视频生成, ~5/天)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Pollinations 工具 (高级):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
|
|
452
467
|
"terminal_cmds": "### 💻 终端命令\n使用 `/poll help` 查看所有命令。试试 `/poll models` 发现目录,或 `/poll usage` 跟踪剩余 Pollen。",
|
|
453
468
|
"resources": "### 📚 资源\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** 加入社区\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
|
|
454
469
|
"free_models_success": "可用免费模型:{models}",
|
package/dist/server/quota.js
CHANGED
|
@@ -171,16 +171,7 @@ export async function getQuotaStatus(forceRefresh = false) {
|
|
|
171
171
|
const totalBalance = balanceRes.total
|
|
172
172
|
?? balanceRes.balance
|
|
173
173
|
?? 0;
|
|
174
|
-
|
|
175
|
-
const paidPollen = paidPollenNative !== undefined
|
|
176
|
-
? paidPollenNative
|
|
177
|
-
: Math.max(0, totalBalance - cleanTierRemaining);
|
|
178
|
-
const cleanWalletBalance = Math.max(0, parseFloat(paidPollen.toFixed(4)));
|
|
179
|
-
const tierAlertPercent = tierLimit > 0 ? (cleanTierRemaining / tierLimit * 100) : 0;
|
|
180
|
-
const tierNeedsAlert = tierLimit > 0 && tierAlertPercent <= config.thresholds.tier;
|
|
181
|
-
const walletNeedsAlert = cleanWalletBalance > 0 && cleanWalletBalance < (config.thresholds.wallet || 0.5);
|
|
182
|
-
logQuota(`Fetch Success. Allowance: ${allowance}, Paid: ${cleanWalletBalance}, Total: ${totalBalance}`);
|
|
183
|
-
// Fetch quest stash (cached 5 min)
|
|
174
|
+
// Fetch quest stash BEFORE paidPollen calculation (cached 5 min)
|
|
184
175
|
const nowStash = Date.now();
|
|
185
176
|
if (!cachedStash || (nowStash - lastStashFetch) > STASH_CACHE_TTL) {
|
|
186
177
|
try {
|
|
@@ -190,6 +181,15 @@ export async function getQuotaStatus(forceRefresh = false) {
|
|
|
190
181
|
catch { /* keep previous */ }
|
|
191
182
|
}
|
|
192
183
|
const questStash = cachedStash?.questStash ?? 0;
|
|
184
|
+
const paidPollenNative = balanceRes.pack;
|
|
185
|
+
const paidPollen = paidPollenNative !== undefined
|
|
186
|
+
? paidPollenNative
|
|
187
|
+
: Math.max(0, totalBalance - cleanTierRemaining - questStash);
|
|
188
|
+
const cleanWalletBalance = Math.max(0, parseFloat(paidPollen.toFixed(4)));
|
|
189
|
+
const tierAlertPercent = tierLimit > 0 ? (cleanTierRemaining / tierLimit * 100) : 0;
|
|
190
|
+
const tierNeedsAlert = tierLimit > 0 && tierAlertPercent <= config.thresholds.tier;
|
|
191
|
+
const walletNeedsAlert = cleanWalletBalance > 0 && cleanWalletBalance < (config.thresholds.wallet || 0.5);
|
|
192
|
+
logQuota(`Fetch Success. Allowance: ${allowance}, Stash: ${questStash}, Paid: ${cleanWalletBalance}, Total: ${totalBalance}`);
|
|
193
193
|
cachedQuota = {
|
|
194
194
|
tierRemaining: cleanTierRemaining,
|
|
195
195
|
tierUsed,
|
package/dist/server/toast.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export declare function setGlobalClient(client: any): void;
|
|
|
2
2
|
export declare function emitLogToast(type: 'info' | 'warning' | 'error' | 'success', message: string, title?: string): void;
|
|
3
3
|
export declare function emitStatusToast(type: 'info' | 'warning' | 'error' | 'success', message: string, title?: string, metadata?: {
|
|
4
4
|
filePath?: string;
|
|
5
|
+
freeTool?: boolean;
|
|
5
6
|
params?: Record<string, any>;
|
|
6
7
|
}): void;
|
|
7
8
|
export declare function createToastHooks(client: any): {
|
package/dist/server/toast.js
CHANGED
|
@@ -31,9 +31,14 @@ export function emitStatusToast(type, message, title, metadata) {
|
|
|
31
31
|
finalMessage += `\n📁 ${metadata.filePath}`;
|
|
32
32
|
}
|
|
33
33
|
if (type === 'success' || type === 'error') {
|
|
34
|
-
//
|
|
34
|
+
// Skip quota display for non-polli tools (free tools)
|
|
35
|
+
if (metadata?.freeTool === true) {
|
|
36
|
+
dispatchToast('status', type, finalMessage, title || 'Pollinations Status');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// En arrière-plan, récupère le quota EN LIVE (pas de cache)
|
|
35
40
|
import('./quota.js').then(({ getQuotaStatus, formatQuotaForToast }) => {
|
|
36
|
-
getQuotaStatus(
|
|
41
|
+
getQuotaStatus(true).then(quota => {
|
|
37
42
|
const quotaMsg = formatQuotaForToast
|
|
38
43
|
? formatQuotaForToast(quota)
|
|
39
44
|
: `🌻 Freetier: ${quota.tierRemaining.toFixed(2)}/${quota.tierLimit} | Wallet: $${quota.walletBalance.toFixed(2)}`;
|
package/dist/tools/index.js
CHANGED
|
@@ -20,6 +20,10 @@ import { rmbgKeysTool } from './power/rmbg_keys.js';
|
|
|
20
20
|
import { genEditImageFreeTool } from './pollinations/gen_edit_image_free.js';
|
|
21
21
|
import { genVideoFreeTool } from './pollinations/gen_video_free.js';
|
|
22
22
|
import { polliLoginTool } from './pollinations/polli_login.js';
|
|
23
|
+
// === FREE BONUS: standalone image processing tools (no key, no API, direct calls) ===
|
|
24
|
+
import { objectRemoverTool } from './pollinations/object_remover.js';
|
|
25
|
+
import { imageUpscalerTool } from './pollinations/image_upscaler.js';
|
|
26
|
+
import { imageEnhancerTool } from './pollinations/image_enhancer.js';
|
|
23
27
|
// === ENTER TOOLS (Require API key) ===
|
|
24
28
|
import { polliGenImageTool } from './pollinations/gen_image.js';
|
|
25
29
|
import { polliGenVideoTool } from './pollinations/gen_video.js';
|
|
@@ -65,6 +69,10 @@ export function createToolRegistry() {
|
|
|
65
69
|
tools['gen_video_free'] = genVideoFreeTool;
|
|
66
70
|
// Login tool: device-flow login, callable by any model (no key needed to run)
|
|
67
71
|
tools['polli_login'] = polliLoginTool;
|
|
72
|
+
// Standalone image processing tools (no key, no API, direct calls from user IP)
|
|
73
|
+
tools['object_remover'] = objectRemoverTool;
|
|
74
|
+
tools['image_upscaler'] = imageUpscalerTool;
|
|
75
|
+
tools['image_enhancer'] = imageEnhancerTool;
|
|
68
76
|
log(`Free tools injected: ${Object.keys(tools).length}`);
|
|
69
77
|
// === ENTER UNIVERSE: Only with valid API key (+6 tools) ===
|
|
70
78
|
if (keyPresent) {
|
|
@@ -99,7 +99,7 @@ export const genEditImageFreeTool = tool({
|
|
|
99
99
|
}
|
|
100
100
|
if (!apiResp || !apiResp.success || !apiResp.imageUrl) {
|
|
101
101
|
const reason = apiResp?.error || 'unknown';
|
|
102
|
-
emitStatusToast('error', t('tools.gen_edit_image_free.failed', { error: String(reason).substring(0, 60) }), '🆓 gen_edit_image_free');
|
|
102
|
+
emitStatusToast('error', t('tools.gen_edit_image_free.failed', { error: String(reason).substring(0, 60) }), '🆓 gen_edit_image_free', { freeTool: true });
|
|
103
103
|
return t('tools.gen_edit_image_free.api_error', { error: String(reason) });
|
|
104
104
|
}
|
|
105
105
|
// 3. Download the produced image and save it locally
|
|
@@ -133,12 +133,13 @@ export const genEditImageFreeTool = tool({
|
|
|
133
133
|
}
|
|
134
134
|
lines.push('');
|
|
135
135
|
lines.push(t('tools.gen_edit_image_free.res_note'));
|
|
136
|
-
|
|
136
|
+
const quotaMsg = after ? ` | ${after.remaining}/${after.max}/j` : '';
|
|
137
|
+
emitStatusToast('success', t('tools.gen_edit_image_free.success', { mode }) + quotaMsg, '🆓 gen_edit_image_free', { filePath, freeTool: true });
|
|
137
138
|
return lines.join('\n');
|
|
138
139
|
}
|
|
139
140
|
catch (err) {
|
|
140
141
|
const msg = err.message || String(err);
|
|
141
|
-
emitStatusToast('error', t('tools.gen_edit_image_free.failed', { error: msg.substring(0, 60) }), '🆓 gen_edit_image_free');
|
|
142
|
+
emitStatusToast('error', t('tools.gen_edit_image_free.failed', { error: msg.substring(0, 60) }), '🆓 gen_edit_image_free', { freeTool: true });
|
|
142
143
|
// Graceful degradation: explicitly point to Pollinations models
|
|
143
144
|
return t('tools.gen_edit_image_free.degraded', { error: msg.substring(0, 150) });
|
|
144
145
|
}
|
|
@@ -234,12 +234,13 @@ export const genVideoFreeTool = tool({
|
|
|
234
234
|
lines.push(t('tools.gen_video_free.res_quota', { remaining: after.remaining, max: after.max }));
|
|
235
235
|
lines.push('');
|
|
236
236
|
lines.push(t('tools.gen_video_free.res_note'));
|
|
237
|
-
|
|
237
|
+
const quotaMsg = after ? ` | ${after.remaining}/${after.max}/j` : '';
|
|
238
|
+
emitStatusToast('success', t('tools.gen_video_free.success') + quotaMsg, '🆓 gen_video_free', { filePath, freeTool: true });
|
|
238
239
|
return lines.join('\n');
|
|
239
240
|
}
|
|
240
241
|
catch (err) {
|
|
241
242
|
const msg = err.message || String(err);
|
|
242
|
-
emitStatusToast('error', t('tools.gen_video_free.failed', { error: msg.substring(0, 60) }), '🆓 gen_video_free');
|
|
243
|
+
emitStatusToast('error', t('tools.gen_video_free.failed', { error: msg.substring(0, 60) }), '🆓 gen_video_free', { freeTool: true });
|
|
243
244
|
return t('tools.gen_video_free.degraded', { error: msg.substring(0, 150) });
|
|
244
245
|
}
|
|
245
246
|
},
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// image_enhancer — Amélioration IA (débruitage, netteté) via imgupscaler.com
|
|
2
|
+
// Standalone : appel direct depuis l'IP utilisateur, pas d'API, pas de clé
|
|
3
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { ensureDir, generateFilename, getDefaultOutputDir, formatFileSize, sanitizeFilename, httpsGet, } from './shared.js';
|
|
7
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
8
|
+
import { t } from '../../locales/index.js';
|
|
9
|
+
import { processTool } from './imgtools/clients.js';
|
|
10
|
+
export const imageEnhancerTool = tool({
|
|
11
|
+
description: `Améliore la qualité d'une image : réduction du bruit, netteté, restauration des détails (gratuit, appel direct).
|
|
12
|
+
Résolutions : 1K (1024px), 2K (2048px), 4K (4096px). Défaut : 2K.
|
|
13
|
+
Temps de traitement : 30-120s. Pas de clé requise.`,
|
|
14
|
+
args: {
|
|
15
|
+
file: tool.schema.string().describe('Chemin local de l\'image à améliorer'),
|
|
16
|
+
target_longest_side: tool.schema.enum(['1024', '2048', '4096']).optional().describe('Résolution cible (1K=1024, 2K=2048, 4K=4096). Défaut: 2048'),
|
|
17
|
+
save_to: tool.schema.string().optional().describe('Dossier de sortie'),
|
|
18
|
+
filename: tool.schema.string().optional().describe('Nom du fichier de sortie (sans extension)'),
|
|
19
|
+
},
|
|
20
|
+
async execute(args, context) {
|
|
21
|
+
const imagePath = args.file;
|
|
22
|
+
if (!fs.existsSync(imagePath)) {
|
|
23
|
+
return t('tools.image_enhancer.file_not_found', { path: imagePath }) || `❌ Fichier introuvable : ${imagePath}`;
|
|
24
|
+
}
|
|
25
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
26
|
+
const mimeType = ext === '.png' ? 'image/png' : 'image/jpeg';
|
|
27
|
+
const imageData = fs.readFileSync(imagePath);
|
|
28
|
+
const targetLongestSide = parseInt(args.target_longest_side || '2048');
|
|
29
|
+
const resLabel = targetLongestSide >= 4096 ? '4K' : targetLongestSide >= 2048 ? '2K' : '1K';
|
|
30
|
+
context.metadata({ title: '✨ image_enhancer', metadata: { type: 'info', message: `Amélioration ${resLabel}...` } });
|
|
31
|
+
try {
|
|
32
|
+
const result = await processTool('enhance', {
|
|
33
|
+
data: imageData,
|
|
34
|
+
contentType: mimeType,
|
|
35
|
+
filename: path.basename(imagePath),
|
|
36
|
+
options: { targetLongestSide },
|
|
37
|
+
});
|
|
38
|
+
if (!result.imageUrl) {
|
|
39
|
+
return t('tools.image_enhancer.no_result') || '❌ Aucun résultat reçu.';
|
|
40
|
+
}
|
|
41
|
+
const dl = await httpsGet(result.imageUrl);
|
|
42
|
+
const outputDir = args.save_to ? args.save_to : getDefaultOutputDir('image_enhancer');
|
|
43
|
+
const outputFilename = (args.filename ? sanitizeFilename(args.filename) : generateFilename('enhance', 'imgupscaler', 'png'));
|
|
44
|
+
const filePath = path.join(outputDir, outputFilename.includes('.') ? outputFilename : `${outputFilename}.png`);
|
|
45
|
+
ensureDir(outputDir);
|
|
46
|
+
fs.writeFileSync(filePath, dl.data);
|
|
47
|
+
emitStatusToast("success", "✨ Enhancement terminé", "image_enhancer", { filePath, freeTool: true });
|
|
48
|
+
const fileSize = fs.statSync(filePath).size;
|
|
49
|
+
const lines = [];
|
|
50
|
+
lines.push('✨ **Image Améliorée**');
|
|
51
|
+
lines.push('━━━━━━━━━━━━━━━━━━');
|
|
52
|
+
lines.push(`Fichier : \`${filePath}\``);
|
|
53
|
+
lines.push(`Taille : ${formatFileSize(fileSize)}`);
|
|
54
|
+
lines.push(`Résolution : ${resLabel}`);
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
emitStatusToast("warning", "❌ " + (err.message?.substring(0, 80) || ""), "image_enhancer", { freeTool: true });
|
|
59
|
+
return "❌ Erreur : " + err.message;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// image_upscaler — Agrandissement 2x/4x via imgupscaler.com
|
|
2
|
+
// Standalone : appel direct depuis l'IP utilisateur, pas d'API, pas de clé
|
|
3
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { ensureDir, generateFilename, getDefaultOutputDir, formatFileSize, sanitizeFilename, httpsGet, } from './shared.js';
|
|
7
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
8
|
+
import { t } from '../../locales/index.js';
|
|
9
|
+
import { processTool } from './imgtools/clients.js';
|
|
10
|
+
export const imageUpscalerTool = tool({
|
|
11
|
+
description: `Agrandit une image 2x ou 4x (gratuit, appel direct).
|
|
12
|
+
Temps de traitement : 30-120s. Pas de clé requise.`,
|
|
13
|
+
args: {
|
|
14
|
+
file: tool.schema.string().describe('Chemin local de l\'image à agrandir'),
|
|
15
|
+
ratio: tool.schema.enum(['2', '4']).optional().describe('Facteur d\'agrandissement (2=2x, 4=4x). Défaut: 2'),
|
|
16
|
+
save_to: tool.schema.string().optional().describe('Dossier de sortie'),
|
|
17
|
+
filename: tool.schema.string().optional().describe('Nom du fichier de sortie (sans extension)'),
|
|
18
|
+
},
|
|
19
|
+
async execute(args, context) {
|
|
20
|
+
const imagePath = args.file;
|
|
21
|
+
if (!fs.existsSync(imagePath)) {
|
|
22
|
+
return t('tools.image_upscaler.file_not_found', { path: imagePath }) || `❌ Fichier introuvable : ${imagePath}`;
|
|
23
|
+
}
|
|
24
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
25
|
+
const mimeType = ext === '.png' ? 'image/png' : 'image/jpeg';
|
|
26
|
+
const imageData = fs.readFileSync(imagePath);
|
|
27
|
+
const ratio = args.ratio === '4' ? '400' : '200';
|
|
28
|
+
const ratioLabel = args.ratio === '4' ? '4x' : '2x';
|
|
29
|
+
context.metadata({ title: '📐 image_upscaler', metadata: { type: 'info', message: `Agrandissement ${ratioLabel}...` } });
|
|
30
|
+
try {
|
|
31
|
+
const result = await processTool('upscale', {
|
|
32
|
+
data: imageData,
|
|
33
|
+
contentType: mimeType,
|
|
34
|
+
filename: path.basename(imagePath),
|
|
35
|
+
options: { ratio },
|
|
36
|
+
});
|
|
37
|
+
if (!result.imageUrl) {
|
|
38
|
+
return t('tools.image_upscaler.no_result') || '❌ Aucun résultat reçu.';
|
|
39
|
+
}
|
|
40
|
+
const dl = await httpsGet(result.imageUrl);
|
|
41
|
+
const outputDir = args.save_to ? args.save_to : getDefaultOutputDir('image_upscaler');
|
|
42
|
+
const outputFilename = (args.filename ? sanitizeFilename(args.filename) : generateFilename('upscale', 'imgupscaler', 'png'));
|
|
43
|
+
const filePath = path.join(outputDir, outputFilename.includes('.') ? outputFilename : `${outputFilename}.png`);
|
|
44
|
+
ensureDir(outputDir);
|
|
45
|
+
fs.writeFileSync(filePath, dl.data);
|
|
46
|
+
emitStatusToast("success", "📐 Upscale terminé", "image_upscaler", { filePath, freeTool: true });
|
|
47
|
+
const fileSize = fs.statSync(filePath).size;
|
|
48
|
+
const lines = [];
|
|
49
|
+
lines.push('📐 **Image Agrandie**');
|
|
50
|
+
lines.push('━━━━━━━━━━━━━━━━━━');
|
|
51
|
+
lines.push(`Fichier : \`${filePath}\``);
|
|
52
|
+
lines.push(`Taille : ${formatFileSize(fileSize)}`);
|
|
53
|
+
lines.push(`Ratio : ${ratioLabel}`);
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
emitStatusToast("warning", "❌ " + (err.message?.substring(0, 80) || ""), "image_upscaler", { freeTool: true });
|
|
58
|
+
return "❌ Erreur : " + err.message;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ToolInput {
|
|
2
|
+
data: Buffer;
|
|
3
|
+
contentType: string;
|
|
4
|
+
filename: string;
|
|
5
|
+
options?: Record<string, string | number>;
|
|
6
|
+
}
|
|
7
|
+
export interface ToolResult {
|
|
8
|
+
imageUrl: string;
|
|
9
|
+
downloadUrls: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function processTool(toolName: string, input: ToolInput): Promise<ToolResult>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// imgtools/clients.ts — orchestration upload → poll → download pour les 4 outils
|
|
2
|
+
// Adapté depuis /home/fkomp/Bureau/oracle/dev-serveur/iamges-tools-api/modules/clients.js
|
|
3
|
+
// Mode standalone : appels directs depuis l'IP utilisateur, pas de queue, pas d'API
|
|
4
|
+
import { TOOLS, UA } from './config.js';
|
|
5
|
+
import { getKey, encrypt, decrypt } from './crypto.js';
|
|
6
|
+
import { buildMultipart, httpsPost, getDims, aspectRatio } from './helpers.js';
|
|
7
|
+
const BASE_HEADERS = {
|
|
8
|
+
'User-Agent': UA,
|
|
9
|
+
'Accept': '*/*',
|
|
10
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
11
|
+
};
|
|
12
|
+
function headersFor(t, contentType) {
|
|
13
|
+
return {
|
|
14
|
+
...BASE_HEADERS,
|
|
15
|
+
'Origin': `https://${t.host}`,
|
|
16
|
+
'Referer': t.page,
|
|
17
|
+
...(contentType ? { 'Content-Type': contentType } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function normalizeUrls(dUrls) {
|
|
21
|
+
if (Array.isArray(dUrls))
|
|
22
|
+
return dUrls.filter(u => u && String(u).trim()).map(u => String(u).trim());
|
|
23
|
+
if (dUrls && typeof dUrls === 'object') {
|
|
24
|
+
return Object.values(dUrls).filter(u => u && String(u).trim()).map(u => String(u).trim());
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
function hasUrls(dUrls) {
|
|
29
|
+
return normalizeUrls(dUrls).length > 0;
|
|
30
|
+
}
|
|
31
|
+
function resolveUrls(t, urls) {
|
|
32
|
+
const resolved = urls.map(u => {
|
|
33
|
+
if (String(u).startsWith('http'))
|
|
34
|
+
return String(u);
|
|
35
|
+
if (t.resultBase) {
|
|
36
|
+
const fn = String(u).split('/').pop() || '';
|
|
37
|
+
return `${t.resultBase}/${fn}`;
|
|
38
|
+
}
|
|
39
|
+
return String(u);
|
|
40
|
+
});
|
|
41
|
+
return { imageUrl: resolved[0] || '', downloadUrls: resolved };
|
|
42
|
+
}
|
|
43
|
+
// ─── Legacy (rmbg, upscale) ──────────────────────────────────────────
|
|
44
|
+
async function uploadLegacy(t, file) {
|
|
45
|
+
const mpFields = [
|
|
46
|
+
{ name: 'file', value: file.data, filename: file.filename || 'image.jpg', contentType: file.contentType || 'image/jpeg' },
|
|
47
|
+
];
|
|
48
|
+
if (t.uploadFields) {
|
|
49
|
+
for (const [k, v] of Object.entries(t.uploadFields)) {
|
|
50
|
+
let val = v;
|
|
51
|
+
if (k === 'ratio' && file.options?.ratio)
|
|
52
|
+
val = String(file.options.ratio);
|
|
53
|
+
mpFields.push({ name: k, value: val });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const { boundary, body } = buildMultipart(mpFields);
|
|
57
|
+
const hdrs = headersFor(t, `multipart/form-data; boundary=${boundary}`);
|
|
58
|
+
const res = await httpsPost(t.host, t.uploadPath, hdrs, body);
|
|
59
|
+
if (res.status !== 200)
|
|
60
|
+
throw new Error(`Upload ${t.name} failed: ${res.status} ${res.body.toString('utf8').slice(0, 200)}`);
|
|
61
|
+
let json;
|
|
62
|
+
try {
|
|
63
|
+
json = JSON.parse(res.body.toString('utf8'));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new Error(`Upload ${t.name} non-JSON: ${res.body.toString('utf8').slice(0, 200)}`);
|
|
67
|
+
}
|
|
68
|
+
if (json.downloadUrls && hasUrls(json.downloadUrls)) {
|
|
69
|
+
return { code: null, directUrls: normalizeUrls(json.downloadUrls) };
|
|
70
|
+
}
|
|
71
|
+
const code = json.code || json.taskId || json.taskCode || (json.data && (json.data.code || json.data.taskId));
|
|
72
|
+
if (!code)
|
|
73
|
+
throw new Error(`Upload ${t.name}: no code in ${JSON.stringify(json).slice(0, 300)}`);
|
|
74
|
+
return { code: String(code), directUrls: [] };
|
|
75
|
+
}
|
|
76
|
+
async function pollLegacy(t, code) {
|
|
77
|
+
const codes = Array.isArray(code) ? code : [code];
|
|
78
|
+
const bodyObj = t.statusBodyBuilder ? t.statusBodyBuilder(codes) : { type: t.statusType, [t.statusField]: codes };
|
|
79
|
+
const body = JSON.stringify(bodyObj);
|
|
80
|
+
const hdrs = headersFor(t, 'application/json');
|
|
81
|
+
const res = await httpsPost(t.host, t.statusPath, hdrs, body);
|
|
82
|
+
if (res.status !== 200)
|
|
83
|
+
throw new Error(`Status ${t.name} failed: ${res.status}`);
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(res.body.toString('utf8'));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
throw new Error(`Status ${t.name} non-JSON`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ─── Encrypted (ruo, enhance) ────────────────────────────────────────
|
|
92
|
+
function buildUploadParams(t, file, dims, extra) {
|
|
93
|
+
const ratio = aspectRatio(dims.width, dims.height);
|
|
94
|
+
if (t.name === 'ruo') {
|
|
95
|
+
const p = String(extra.prompt || 'remove unwanted objects');
|
|
96
|
+
return {
|
|
97
|
+
type: t.statusType, selected_model: t.selectedModel, model_name: t.modelName,
|
|
98
|
+
user_id: 'anonymous', tool: t.tool,
|
|
99
|
+
positive_prompts: p, resolved_prompt: p, raw_prompt: p,
|
|
100
|
+
aspect_ratio: ratio, prompt_image_refs: ['primary'], image_order_map: ['primary'],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (t.name === 'enhance') {
|
|
104
|
+
const prompt = t.prompt;
|
|
105
|
+
const tls = Number(extra.targetLongestSide || 1024);
|
|
106
|
+
return {
|
|
107
|
+
positive_prompts: prompt, resolved_prompt: prompt, aspect_ratio: ratio,
|
|
108
|
+
selected_model: t.selectedModel, model_name: t.modelName,
|
|
109
|
+
user_id: 'anonymous', type: t.statusType, target_longest_side: tls,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`Unknown encrypted tool: ${t.name}`);
|
|
113
|
+
}
|
|
114
|
+
async function uploadEncrypted(t, file) {
|
|
115
|
+
const key = getKey(t.crypto.salt);
|
|
116
|
+
const dims = getDims(file.data) || { width: 0, height: 0 };
|
|
117
|
+
const params = buildUploadParams(t, file, dims, file.options || {});
|
|
118
|
+
const enc = encrypt(params, key);
|
|
119
|
+
const { boundary, body } = buildMultipart([
|
|
120
|
+
{ name: 'params', value: enc },
|
|
121
|
+
{ name: 'file', value: file.data, filename: file.filename || 'image.jpg', contentType: file.contentType || 'image/jpeg' },
|
|
122
|
+
{ name: 'selected_model', value: t.selectedModel },
|
|
123
|
+
]);
|
|
124
|
+
const hdrs = headersFor(t, `multipart/form-data; boundary=${boundary}`);
|
|
125
|
+
const res = await httpsPost(t.host, t.uploadPath, hdrs, body);
|
|
126
|
+
if (res.status !== 200)
|
|
127
|
+
throw new Error(`Upload ${t.name} failed: ${res.status}`);
|
|
128
|
+
const json = JSON.parse(res.body.toString('utf8'));
|
|
129
|
+
if (!json.data_enc)
|
|
130
|
+
throw new Error(`Upload ${t.name}: no data_enc in ${JSON.stringify(json).slice(0, 200)}`);
|
|
131
|
+
const dec = decrypt(json.data_enc, key);
|
|
132
|
+
if (!dec.code)
|
|
133
|
+
throw new Error(`Upload ${t.name}: no code in decrypted ${JSON.stringify(dec).slice(0, 200)}`);
|
|
134
|
+
return { code: String(dec.code) };
|
|
135
|
+
}
|
|
136
|
+
async function pollEncrypted(t, code) {
|
|
137
|
+
const key = getKey(t.crypto.salt);
|
|
138
|
+
const statusPayload = { type: t.statusType, code, user_id: 'anonymous' };
|
|
139
|
+
const enc = encrypt(statusPayload, key);
|
|
140
|
+
const body = JSON.stringify({ params: enc });
|
|
141
|
+
const hdrs = headersFor(t, 'application/json');
|
|
142
|
+
const res = await httpsPost(t.host, t.statusPath, hdrs, body);
|
|
143
|
+
if (res.status !== 200)
|
|
144
|
+
throw new Error(`Status ${t.name} failed: ${res.status}`);
|
|
145
|
+
const json = JSON.parse(res.body.toString('utf8'));
|
|
146
|
+
if (!json.data_enc)
|
|
147
|
+
throw new Error(`Status ${t.name}: no data_enc`);
|
|
148
|
+
return decrypt(json.data_enc, key);
|
|
149
|
+
}
|
|
150
|
+
// ─── Orchestrateur principal ─────────────────────────────────────────
|
|
151
|
+
export async function processTool(toolName, input) {
|
|
152
|
+
const t = TOOLS[toolName];
|
|
153
|
+
if (!t)
|
|
154
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
155
|
+
const isEncrypted = !!t.crypto;
|
|
156
|
+
const upRes = isEncrypted
|
|
157
|
+
? await uploadEncrypted(t, input)
|
|
158
|
+
: await uploadLegacy(t, input);
|
|
159
|
+
if ('directUrls' in upRes && upRes.directUrls && upRes.directUrls.length > 0) {
|
|
160
|
+
return resolveUrls(t, upRes.directUrls);
|
|
161
|
+
}
|
|
162
|
+
const code = 'code' in upRes ? upRes.code : upRes.code;
|
|
163
|
+
let status;
|
|
164
|
+
for (let i = 0; i < t.maxPolls; i++) {
|
|
165
|
+
if (i > 0)
|
|
166
|
+
await new Promise(r => setTimeout(r, t.pollInterval));
|
|
167
|
+
status = isEncrypted
|
|
168
|
+
? await pollEncrypted(t, code)
|
|
169
|
+
: await pollLegacy(t, code);
|
|
170
|
+
if (status.status === 'success' || hasUrls(status.downloadUrls))
|
|
171
|
+
break;
|
|
172
|
+
if (status.status === 'failed')
|
|
173
|
+
throw new Error(`Status ${t.name} failed: ${status.message || JSON.stringify(status).slice(0, 200)}`);
|
|
174
|
+
if (status.status && !['processing', 'pending', 'queued', 'waiting'].includes(status.status)) {
|
|
175
|
+
throw new Error(`Status ${t.name} unexpected: ${JSON.stringify(status).slice(0, 300)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const urls = status ? normalizeUrls(status.downloadUrls) : [];
|
|
179
|
+
if (!urls.length)
|
|
180
|
+
throw new Error(`Polling ${t.name} timeout: no downloadUrls after ${t.maxPolls} attempts`);
|
|
181
|
+
return resolveUrls(t, urls);
|
|
182
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export declare const UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/150.0.0.0 Safari/537.36";
|
|
2
|
+
export declare const CRYPTO: {
|
|
3
|
+
PBKDF2: {
|
|
4
|
+
iterations: number;
|
|
5
|
+
hash: string;
|
|
6
|
+
keylen: number;
|
|
7
|
+
};
|
|
8
|
+
AES_GCM: {
|
|
9
|
+
ivLen: number;
|
|
10
|
+
tagLen: number;
|
|
11
|
+
};
|
|
12
|
+
password: string;
|
|
13
|
+
};
|
|
14
|
+
export interface ToolConfig {
|
|
15
|
+
name: string;
|
|
16
|
+
label: string;
|
|
17
|
+
host: string;
|
|
18
|
+
uploadPath: string;
|
|
19
|
+
statusPath: string;
|
|
20
|
+
crypto: typeof CRYPTO & {
|
|
21
|
+
salt: string;
|
|
22
|
+
} | null;
|
|
23
|
+
statusType: number;
|
|
24
|
+
statusField: string;
|
|
25
|
+
maxPolls: number;
|
|
26
|
+
pollInterval: number;
|
|
27
|
+
downloadUrlsFormat?: string;
|
|
28
|
+
page: string;
|
|
29
|
+
uploadFields?: Record<string, string>;
|
|
30
|
+
statusBodyBuilder?: (codes: string[]) => Record<string, unknown>;
|
|
31
|
+
selectedModel?: string;
|
|
32
|
+
modelName?: string;
|
|
33
|
+
tool?: string;
|
|
34
|
+
resultBase?: string;
|
|
35
|
+
ratios?: string[];
|
|
36
|
+
targetLongestSides?: Array<{
|
|
37
|
+
label: string;
|
|
38
|
+
value: number;
|
|
39
|
+
}>;
|
|
40
|
+
prompt?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const TOOLS: Record<string, ToolConfig>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// imgtools/config.ts — endpoints, crypto params, headers des 4 outils
|
|
2
|
+
// Adapté depuis /home/fkomp/Bureau/oracle/dev-serveur/iamges-tools-api/modules/config.js
|
|
3
|
+
export const UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/150.0.0.0 Safari/537.36';
|
|
4
|
+
export const CRYPTO = {
|
|
5
|
+
PBKDF2: { iterations: 10000, hash: 'sha256', keylen: 32 },
|
|
6
|
+
AES_GCM: { ivLen: 12, tagLen: 16 },
|
|
7
|
+
password: 'vH33r_2025_AES_GCM_S3cur3_K3y_9X7mP4qR8nT2wE5yU1oI6aS3dF7gH0jK9lZ',
|
|
8
|
+
};
|
|
9
|
+
export const TOOLS = {
|
|
10
|
+
rmbg: {
|
|
11
|
+
name: 'rmbg',
|
|
12
|
+
label: 'Background Removal',
|
|
13
|
+
host: 'bgeraser.com',
|
|
14
|
+
uploadPath: '/api/bgeraser/legacy/upload',
|
|
15
|
+
statusPath: '/api/bgeraser/legacy/status',
|
|
16
|
+
crypto: null,
|
|
17
|
+
statusType: 4,
|
|
18
|
+
statusField: 'codes',
|
|
19
|
+
uploadFields: { type: '4', mattValue: '0' },
|
|
20
|
+
maxPolls: 30,
|
|
21
|
+
pollInterval: 5000,
|
|
22
|
+
downloadUrlsFormat: 'object',
|
|
23
|
+
page: 'https://bgeraser.com',
|
|
24
|
+
},
|
|
25
|
+
ruo: {
|
|
26
|
+
name: 'ruo',
|
|
27
|
+
label: 'Remove Unwanted Objects',
|
|
28
|
+
host: 'objectremover.com',
|
|
29
|
+
uploadPath: '/api/u2/upload',
|
|
30
|
+
statusPath: '/api/u2/status',
|
|
31
|
+
crypto: { ...CRYPTO, salt: 'objectremover-salt-2026' },
|
|
32
|
+
statusType: 7,
|
|
33
|
+
statusField: 'code',
|
|
34
|
+
maxPolls: 40,
|
|
35
|
+
pollInterval: 3000,
|
|
36
|
+
selectedModel: 'flux_klein/edit',
|
|
37
|
+
modelName: 'Flux Klein Edit',
|
|
38
|
+
tool: 'object-remover',
|
|
39
|
+
page: 'https://objectremover.com',
|
|
40
|
+
resultBase: 'https://access.vheer.com/results',
|
|
41
|
+
},
|
|
42
|
+
upscale: {
|
|
43
|
+
name: 'upscale',
|
|
44
|
+
label: 'Image Upscaler',
|
|
45
|
+
host: 'imgupscaler.com',
|
|
46
|
+
uploadPath: '/api/legacy/upload',
|
|
47
|
+
statusPath: '/api/legacy/status',
|
|
48
|
+
crypto: null,
|
|
49
|
+
statusType: 4,
|
|
50
|
+
statusField: 'codes',
|
|
51
|
+
statusBodyBuilder: (codes) => ({ taskId: codes[0] }),
|
|
52
|
+
uploadFields: { type: '4' },
|
|
53
|
+
maxPolls: 30,
|
|
54
|
+
pollInterval: 5000,
|
|
55
|
+
downloadUrlsFormat: 'array',
|
|
56
|
+
page: 'https://imgupscaler.com',
|
|
57
|
+
ratios: ['200', '400'],
|
|
58
|
+
},
|
|
59
|
+
enhance: {
|
|
60
|
+
name: 'enhance',
|
|
61
|
+
label: 'AI Image Enhancer',
|
|
62
|
+
host: 'imgupscaler.com',
|
|
63
|
+
uploadPath: '/api/u2/upload',
|
|
64
|
+
statusPath: '/api/u2/status',
|
|
65
|
+
crypto: { ...CRYPTO, salt: 'imgupscaler-salt-2026' },
|
|
66
|
+
statusType: 7,
|
|
67
|
+
statusField: 'code',
|
|
68
|
+
maxPolls: 40,
|
|
69
|
+
pollInterval: 3000,
|
|
70
|
+
selectedModel: 'hypir',
|
|
71
|
+
modelName: 'Hypir Enhancer',
|
|
72
|
+
page: 'https://imgupscaler.com/enhancer',
|
|
73
|
+
resultBase: 'https://access.vheer.com/results',
|
|
74
|
+
prompt: 'preserve facial identity, preserve facial geometry, restore image clarity, enhance natural details, reduce blur and noise, preserve original colors and structure',
|
|
75
|
+
targetLongestSides: [
|
|
76
|
+
{ label: '1K', value: 1024 },
|
|
77
|
+
{ label: '2K', value: 2048 },
|
|
78
|
+
{ label: '4K', value: 4096 },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function deriveKey(password: string, salt: string): Buffer;
|
|
2
|
+
export declare function encrypt(data: Record<string, unknown>, key: Buffer): string;
|
|
3
|
+
export declare function decrypt(encoded: string, key: Buffer): any;
|
|
4
|
+
export declare function getKey(salt: string): Buffer;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// imgtools/crypto.ts — AES-GCM + PBKDF2 pour ruo/enhance
|
|
2
|
+
// Adapté depuis /home/fkomp/Bureau/oracle/dev-serveur/iamges-tools-api/modules/crypto.js
|
|
3
|
+
import * as crypto from 'crypto';
|
|
4
|
+
const PBKDF2 = { iterations: 10000, hash: 'sha256', keylen: 32 };
|
|
5
|
+
const AES_GCM = { ivLen: 12, tagLen: 16 };
|
|
6
|
+
export function deriveKey(password, salt) {
|
|
7
|
+
return crypto.pbkdf2Sync(Buffer.from(password, 'utf8'), Buffer.from(salt, 'utf8'), PBKDF2.iterations, PBKDF2.keylen, PBKDF2.hash);
|
|
8
|
+
}
|
|
9
|
+
export function encrypt(data, key) {
|
|
10
|
+
const iv = crypto.randomBytes(AES_GCM.ivLen);
|
|
11
|
+
const plaintext = Buffer.from(JSON.stringify(data), 'utf8');
|
|
12
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
13
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
14
|
+
const tag = cipher.getAuthTag();
|
|
15
|
+
return Buffer.concat([iv, ciphertext, tag]).toString('base64');
|
|
16
|
+
}
|
|
17
|
+
export function decrypt(encoded, key) {
|
|
18
|
+
const buf = Buffer.from(encoded, 'base64');
|
|
19
|
+
const iv = buf.subarray(0, 12);
|
|
20
|
+
const ciphertext = buf.subarray(12, -16);
|
|
21
|
+
const tag = buf.subarray(-16);
|
|
22
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
23
|
+
decipher.setAuthTag(tag);
|
|
24
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
25
|
+
return JSON.parse(plaintext.toString('utf8'));
|
|
26
|
+
}
|
|
27
|
+
const _keyCache = {};
|
|
28
|
+
export function getKey(salt) {
|
|
29
|
+
if (_keyCache[salt])
|
|
30
|
+
return _keyCache[salt];
|
|
31
|
+
const key = deriveKey('vH33r_2025_AES_GCM_S3cur3_K3y_9X7mP4qR8nT2wE5yU1oI6aS3dF7gH0jK9lZ', salt);
|
|
32
|
+
_keyCache[salt] = key;
|
|
33
|
+
return key;
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
2
|
+
export declare function buildMultipart(fields: Array<{
|
|
3
|
+
name: string;
|
|
4
|
+
value: Buffer | string;
|
|
5
|
+
filename?: string;
|
|
6
|
+
contentType?: string;
|
|
7
|
+
}>): {
|
|
8
|
+
boundary: string;
|
|
9
|
+
body: Buffer;
|
|
10
|
+
};
|
|
11
|
+
export declare function httpsPost(host: string, path: string, headers: Record<string, string>, body: Buffer | string): Promise<{
|
|
12
|
+
status: number;
|
|
13
|
+
body: Buffer;
|
|
14
|
+
headers: Record<string, string>;
|
|
15
|
+
}>;
|
|
16
|
+
export declare function getDims(buf: Buffer): {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
} | null;
|
|
20
|
+
export declare function aspectRatio(w: number, h: number): string;
|
|
21
|
+
export declare function dlImage(url: string): Promise<{
|
|
22
|
+
body: Buffer;
|
|
23
|
+
contentType: string;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// imgtools/helpers.ts — HTTP, multipart, image dimensions
|
|
2
|
+
// Adapté depuis /home/fkomp/Bureau/oracle/dev-serveur/iamges-tools-api/modules/helpers.js
|
|
3
|
+
import * as https from 'https';
|
|
4
|
+
import * as crypto from 'crypto';
|
|
5
|
+
export function sleep(ms) {
|
|
6
|
+
return new Promise(r => setTimeout(r, ms));
|
|
7
|
+
}
|
|
8
|
+
export function buildMultipart(fields) {
|
|
9
|
+
const boundary = '----WebKitFormBoundary' + crypto.randomBytes(16).toString('hex');
|
|
10
|
+
const parts = [];
|
|
11
|
+
for (const f of fields) {
|
|
12
|
+
let h = `--${boundary}\r\nContent-Disposition: form-data; name="${f.name}"`;
|
|
13
|
+
if (f.filename) {
|
|
14
|
+
h += `; filename="${f.filename}"\r\nContent-Type: ${f.contentType || 'application/octet-stream'}\r\n\r\n`;
|
|
15
|
+
parts.push(Buffer.from(h));
|
|
16
|
+
parts.push(typeof f.value === 'string' ? Buffer.from(f.value) : f.value);
|
|
17
|
+
parts.push(Buffer.from('\r\n'));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
h += `\r\n\r\n${f.value}\r\n`;
|
|
21
|
+
parts.push(Buffer.from(h));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
parts.push(Buffer.from(`--${boundary}--\r\n`));
|
|
25
|
+
return { boundary, body: Buffer.concat(parts) };
|
|
26
|
+
}
|
|
27
|
+
export function httpsPost(host, path, headers, body) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const req = https.request({
|
|
30
|
+
hostname: host,
|
|
31
|
+
path,
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Length': Buffer.byteLength(body),
|
|
35
|
+
...headers,
|
|
36
|
+
},
|
|
37
|
+
timeout: 120000,
|
|
38
|
+
}, (res) => {
|
|
39
|
+
const chunks = [];
|
|
40
|
+
res.on('data', c => chunks.push(c));
|
|
41
|
+
res.on('end', () => {
|
|
42
|
+
resolve({
|
|
43
|
+
status: res.statusCode || 0,
|
|
44
|
+
body: Buffer.concat(chunks),
|
|
45
|
+
headers: res.headers,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
req.on('error', reject);
|
|
50
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
51
|
+
if (typeof body === 'string')
|
|
52
|
+
req.write(body);
|
|
53
|
+
else
|
|
54
|
+
req.write(body);
|
|
55
|
+
req.end();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function getDims(buf) {
|
|
59
|
+
try {
|
|
60
|
+
// PNG
|
|
61
|
+
if (buf[0] === 0x89 && buf[1] === 0x50) {
|
|
62
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
63
|
+
}
|
|
64
|
+
// JPEG
|
|
65
|
+
if (buf[0] === 0xff && buf[1] === 0xd8) {
|
|
66
|
+
let offset = 2;
|
|
67
|
+
while (offset < buf.length) {
|
|
68
|
+
if (buf[offset] !== 0xff)
|
|
69
|
+
break;
|
|
70
|
+
const marker = buf[offset + 1];
|
|
71
|
+
if (marker === 0xc0 || marker === 0xc2) {
|
|
72
|
+
return { width: buf.readUInt16BE(offset + 7), height: buf.readUInt16BE(offset + 5) };
|
|
73
|
+
}
|
|
74
|
+
offset += 2 + buf.readUInt16BE(offset + 2);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function aspectRatio(w, h) {
|
|
84
|
+
if (w === 0 || h === 0)
|
|
85
|
+
return '1:1';
|
|
86
|
+
const r = w / h;
|
|
87
|
+
if (r > 1.7)
|
|
88
|
+
return '16:9';
|
|
89
|
+
if (r > 1.2)
|
|
90
|
+
return '4:3';
|
|
91
|
+
if (r > 0.8)
|
|
92
|
+
return '1:1';
|
|
93
|
+
if (r > 0.55)
|
|
94
|
+
return '3:4';
|
|
95
|
+
return '9:16';
|
|
96
|
+
}
|
|
97
|
+
export async function dlImage(url) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
https.get(url, { timeout: 15000 }, (res) => {
|
|
100
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
101
|
+
return reject(new Error(`Download failed: ${res.statusCode}`));
|
|
102
|
+
}
|
|
103
|
+
const chunks = [];
|
|
104
|
+
res.on('data', c => chunks.push(c));
|
|
105
|
+
res.on('end', () => resolve({
|
|
106
|
+
body: Buffer.concat(chunks),
|
|
107
|
+
contentType: res.headers['content-type'] || 'image/png',
|
|
108
|
+
}));
|
|
109
|
+
}).on('error', reject);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// object_remover — Suppression d'objets par prompt via objectremover.com
|
|
2
|
+
// Standalone : appel direct depuis l'IP utilisateur, pas d'API, pas de clé
|
|
3
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { ensureDir, generateFilename, getDefaultOutputDir, formatFileSize, sanitizeFilename, httpsGet, } from './shared.js';
|
|
7
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
8
|
+
import { t } from '../../locales/index.js';
|
|
9
|
+
import { processTool } from './imgtools/clients.js';
|
|
10
|
+
export const objectRemoverTool = tool({
|
|
11
|
+
description: `Supprime un objet d'une image via un prompt (gratuit, appel direct).
|
|
12
|
+
Exemples : "remove the person", "erase the text", "delete the car".
|
|
13
|
+
Temps de traitement : 30-120s. Pas de clé requise.`,
|
|
14
|
+
args: {
|
|
15
|
+
file: tool.schema.string().describe('Chemin local de l\'image à traiter'),
|
|
16
|
+
prompt: tool.schema.string().describe('Description de l\'objet à supprimer (ex: "remove the red car")'),
|
|
17
|
+
save_to: tool.schema.string().optional().describe('Dossier de sortie'),
|
|
18
|
+
filename: tool.schema.string().optional().describe('Nom du fichier de sortie (sans extension)'),
|
|
19
|
+
},
|
|
20
|
+
async execute(args, context) {
|
|
21
|
+
const imagePath = args.file;
|
|
22
|
+
if (!fs.existsSync(imagePath)) {
|
|
23
|
+
return t('tools.object_remover.file_not_found', { path: imagePath }) || `❌ Fichier introuvable : ${imagePath}`;
|
|
24
|
+
}
|
|
25
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
26
|
+
const mimeType = ext === '.png' ? 'image/png' : 'image/jpeg';
|
|
27
|
+
const imageData = fs.readFileSync(imagePath);
|
|
28
|
+
const prompt = args.prompt || 'remove unwanted objects';
|
|
29
|
+
context.metadata({ title: '🧹 object_remover', metadata: { type: 'info', message: `Suppression de "${prompt}"...` } });
|
|
30
|
+
try {
|
|
31
|
+
const result = await processTool('ruo', {
|
|
32
|
+
data: imageData,
|
|
33
|
+
contentType: mimeType,
|
|
34
|
+
filename: path.basename(imagePath),
|
|
35
|
+
options: { prompt },
|
|
36
|
+
});
|
|
37
|
+
if (!result.imageUrl) {
|
|
38
|
+
return t('tools.object_remover.no_result') || '❌ Aucun résultat reçu.';
|
|
39
|
+
}
|
|
40
|
+
const dl = await httpsGet(result.imageUrl);
|
|
41
|
+
const outputDir = args.save_to ? args.save_to : getDefaultOutputDir('object_remover');
|
|
42
|
+
const outputFilename = (args.filename ? sanitizeFilename(args.filename) : generateFilename('ruo', 'object-remover', 'png'));
|
|
43
|
+
const filePath = path.join(outputDir, outputFilename.includes('.') ? outputFilename : `${outputFilename}.png`);
|
|
44
|
+
ensureDir(outputDir);
|
|
45
|
+
fs.writeFileSync(filePath, dl.data);
|
|
46
|
+
emitStatusToast("success", "🧹 Objet supprimé", "object_remover", { filePath, freeTool: true });
|
|
47
|
+
emitStatusToast('success', '🧹 Objet supprimé', 'object_remover', { filePath, freeTool: true });
|
|
48
|
+
const fileSize = fs.statSync(filePath).size;
|
|
49
|
+
const lines = [];
|
|
50
|
+
lines.push('🧹 **Objet Supprimé**');
|
|
51
|
+
lines.push('━━━━━━━━━━━━━━━━━━');
|
|
52
|
+
lines.push(`Fichier : \`${filePath}\``);
|
|
53
|
+
lines.push(`Taille : ${formatFileSize(fileSize)}`);
|
|
54
|
+
lines.push(`Prompt : ${prompt}`);
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
emitStatusToast("warning", "❌ " + (err.message?.substring(0, 80) || ""), "object_remover", { freeTool: true });
|
|
59
|
+
return "❌ Erreur : " + err.message;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -4,12 +4,11 @@ import * as https from 'https';
|
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { resolveOutputDir, formatFileSize, TOOL_DIRS } from '../shared.js';
|
|
7
|
-
import { sanitizeFilename } from '../pollinations/shared.js';
|
|
7
|
+
import { sanitizeFilename, httpsGet } from '../pollinations/shared.js';
|
|
8
|
+
import { processTool } from '../pollinations/imgtools/clients.js';
|
|
8
9
|
import { getConfigDir } from '../../server/config.js';
|
|
9
10
|
// ─── Provider Defaults ───────────────────────────────────────────────────────
|
|
10
|
-
const CUT_API_URL = 'https://cut.esprit-artificiel.com';
|
|
11
11
|
const BACKGROUNDCUT_API_URL = 'https://backgroundcut.co/api/v1/cut/';
|
|
12
|
-
const HMAC_SECRET = "super_secret_community_key_2026"; // Sel caché dans le code transpilé
|
|
13
12
|
// ─── Key Storage ─────────────────────────────────────────────────────────────
|
|
14
13
|
const KEYS_FILE = path.join(getConfigDir(), 'backgroundcut_keys.json');
|
|
15
14
|
function loadKeys() {
|
|
@@ -103,56 +102,11 @@ function getImageSize(filePath) {
|
|
|
103
102
|
}
|
|
104
103
|
return null;
|
|
105
104
|
}
|
|
106
|
-
// ─── Provider:
|
|
107
|
-
async function
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
parts.push(imageData);
|
|
112
|
-
parts.push(Buffer.from(`\r\n--${boundary}--\r\n`));
|
|
113
|
-
const body = Buffer.concat(parts);
|
|
114
|
-
const url = new URL(`${CUT_API_URL}/remove-bg`);
|
|
115
|
-
const headers = {
|
|
116
|
-
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
117
|
-
'Content-Length': body.length,
|
|
118
|
-
'User-Agent': 'OpenCode-Pollinations-Plugin/6.1',
|
|
119
|
-
};
|
|
120
|
-
// 1. Vérifier si l'utilisateur (Franck) a configuré une clé VIP localement
|
|
121
|
-
let vipKey = null;
|
|
122
|
-
try {
|
|
123
|
-
const vipPath = path.join(getConfigDir(), 'cut_vip.json');
|
|
124
|
-
if (fs.existsSync(vipPath)) {
|
|
125
|
-
const data = JSON.parse(fs.readFileSync(vipPath, 'utf-8'));
|
|
126
|
-
if (data.vip_key)
|
|
127
|
-
vipKey = data.vip_key;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
catch (e) {
|
|
131
|
-
console.error(`[Cut VIP] Error loading vip key: ${e}`);
|
|
132
|
-
}
|
|
133
|
-
if (vipKey) {
|
|
134
|
-
// Mode Fast-Lane (VIP) : la requête passe directement en tête de file
|
|
135
|
-
headers['X-Api-Key'] = vipKey;
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
// Mode Communauté (Plugin public) : Génération de la signature dynamique courte durée (Anti-leech)
|
|
139
|
-
const timestamp = Date.now().toString();
|
|
140
|
-
const payloadToSign = `request-rembg-v1:${timestamp}`;
|
|
141
|
-
const signature = require('crypto')
|
|
142
|
-
.createHmac('sha256', HMAC_SECRET)
|
|
143
|
-
.update(payloadToSign)
|
|
144
|
-
.digest('hex');
|
|
145
|
-
headers['X-Cut-Timestamp'] = timestamp;
|
|
146
|
-
headers['Authorization'] = `Bearer community:${signature}`;
|
|
147
|
-
}
|
|
148
|
-
const res = await httpRequest(url.toString(), {
|
|
149
|
-
method: 'POST',
|
|
150
|
-
headers
|
|
151
|
-
}, body);
|
|
152
|
-
if (res.statusCode >= 400) {
|
|
153
|
-
throw new Error(`CUT API Error ${res.statusCode}: ${res.body.toString().substring(0, 200)}`);
|
|
154
|
-
}
|
|
155
|
-
return res.body;
|
|
105
|
+
// ─── Provider: imgtools/rmbg (bgeraser.com) ──────────────────────────────
|
|
106
|
+
async function removeViaImgtools(imageData, mimeType, filename) {
|
|
107
|
+
const result = await processTool('rmbg', { data: imageData, contentType: mimeType, filename });
|
|
108
|
+
const res = await httpsGet(result.imageUrl);
|
|
109
|
+
return res.data;
|
|
156
110
|
}
|
|
157
111
|
// ─── Provider: BackgroundCut.co (returns JSON with output_image_url) ─────────
|
|
158
112
|
async function removeViaBackgroundCut(imageData, filename, mimeType, apiKey, quality = 'medium', returnFormat = 'png', maxResolution) {
|
|
@@ -204,11 +158,11 @@ export const removeBackgroundTool = tool({
|
|
|
204
158
|
description: `Remove the background from an image, producing a transparent PNG or WebP.
|
|
205
159
|
|
|
206
160
|
**Providers:**
|
|
207
|
-
- \`
|
|
161
|
+
- \`imgtools\` (default) — rmbg via bgeraser.com. Free.
|
|
208
162
|
- \`backgroundcut\` — Premium API. Requires API key. Supports all parameters.
|
|
209
163
|
|
|
210
164
|
**Setup:** Use \`rmbg_keys\` tool to manage API keys.
|
|
211
|
-
**Auto mode:** Uses
|
|
165
|
+
**Auto mode:** Uses imgtools by default, falls back to BackgroundCut if key is available.`,
|
|
212
166
|
args: {
|
|
213
167
|
image_path: tool.schema.string().describe('Absolute path to the image file'),
|
|
214
168
|
filename: tool.schema.string().optional().describe('Custom output filename (e.g. "my_image.png") or name without extension'),
|
|
@@ -253,7 +207,7 @@ export const removeBackgroundTool = tool({
|
|
|
253
207
|
let effectiveProvider = provider;
|
|
254
208
|
if (provider === 'auto') {
|
|
255
209
|
// If we have keys, start with backgroundcut, else cut
|
|
256
|
-
effectiveProvider =
|
|
210
|
+
effectiveProvider = 'imgtools'; // Default to imgtools (free, no key needed)
|
|
257
211
|
}
|
|
258
212
|
// ── Info message when no BackgroundCut key ──
|
|
259
213
|
if (keysToCheck.length === 0 && (provider === 'auto' || provider === 'backgroundcut')) {
|
|
@@ -303,7 +257,7 @@ export const removeBackgroundTool = tool({
|
|
|
303
257
|
// ── Execute ──
|
|
304
258
|
try {
|
|
305
259
|
let resultBuffer = null;
|
|
306
|
-
let usedProvider = '
|
|
260
|
+
let usedProvider = 'imgtools'; // Default
|
|
307
261
|
let fallbackUsed = false;
|
|
308
262
|
let successKey = '';
|
|
309
263
|
// 1. Try BackgroundCut loop if applicable
|
|
@@ -353,12 +307,13 @@ export const removeBackgroundTool = tool({
|
|
|
353
307
|
else {
|
|
354
308
|
emitStatusToast('info', `Détourage via API Gratuite: ${basename}`, '✂️ Free RMBG');
|
|
355
309
|
}
|
|
356
|
-
|
|
310
|
+
emitStatusToast('info', `Détourage via rmbg (bgeraser.com): ${basename}`, '✂️ Free RMBG', { freeTool: true });
|
|
311
|
+
resultBuffer = await removeViaImgtools(imageData, mimeType, basename);
|
|
357
312
|
context.metadata({
|
|
358
313
|
title: "RMBG (Free)",
|
|
359
|
-
metadata: { type: 'success', message: "
|
|
314
|
+
metadata: { type: 'success', message: "Background removed (imgtools/rmbg)" }
|
|
360
315
|
});
|
|
361
|
-
usedProvider = '
|
|
316
|
+
usedProvider = 'imgtools';
|
|
362
317
|
}
|
|
363
318
|
if (!resultBuffer || resultBuffer.length < 100) {
|
|
364
319
|
return `❌ Background removal returned invalid data.`;
|
|
@@ -371,6 +326,7 @@ export const removeBackgroundTool = tool({
|
|
|
371
326
|
}
|
|
372
327
|
const dims = getImageSize(finalPath);
|
|
373
328
|
const dimStr = dims ? `${dims.width}×${dims.height}` : 'N/A';
|
|
329
|
+
emitStatusToast('success', `✂️ Fond supprimé · ${usedProvider}`, 'remove_background', { filePath: finalPath, freeTool: true });
|
|
374
330
|
const lines = [
|
|
375
331
|
`✂️ Background Removed`,
|
|
376
332
|
`━━━━━━━━━━━━━━━━━━━━━`,
|
package/package.json
CHANGED