webscout 8.2.9__py3-none-any.whl → 2026.1.19__py3-none-any.whl
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.
- webscout/AIauto.py +524 -251
- webscout/AIbase.py +247 -319
- webscout/AIutel.py +68 -703
- webscout/Bard.py +1072 -1026
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/GitToolkit/gitapi/__init__.py +20 -12
- webscout/Extra/GitToolkit/gitapi/gist.py +142 -0
- webscout/Extra/GitToolkit/gitapi/organization.py +91 -0
- webscout/Extra/GitToolkit/gitapi/repository.py +308 -195
- webscout/Extra/GitToolkit/gitapi/search.py +162 -0
- webscout/Extra/GitToolkit/gitapi/trending.py +236 -0
- webscout/Extra/GitToolkit/gitapi/user.py +128 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +82 -62
- webscout/Extra/YTToolkit/README.md +443 -375
- webscout/Extra/YTToolkit/YTdownloader.py +953 -957
- webscout/Extra/YTToolkit/__init__.py +3 -3
- webscout/Extra/YTToolkit/transcriber.py +595 -476
- webscout/Extra/YTToolkit/ytapi/README.md +230 -44
- webscout/Extra/YTToolkit/ytapi/__init__.py +22 -6
- webscout/Extra/YTToolkit/ytapi/captions.py +190 -0
- webscout/Extra/YTToolkit/ytapi/channel.py +302 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +178 -118
- webscout/Extra/YTToolkit/ytapi/hashtag.py +120 -0
- webscout/Extra/YTToolkit/ytapi/https.py +89 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +59 -59
- webscout/Extra/YTToolkit/ytapi/pool.py +8 -8
- webscout/Extra/YTToolkit/ytapi/query.py +143 -40
- webscout/Extra/YTToolkit/ytapi/shorts.py +122 -0
- webscout/Extra/YTToolkit/ytapi/stream.py +68 -63
- webscout/Extra/YTToolkit/ytapi/suggestions.py +97 -0
- webscout/Extra/YTToolkit/ytapi/utils.py +66 -62
- webscout/Extra/YTToolkit/ytapi/video.py +403 -232
- webscout/Extra/__init__.py +2 -3
- webscout/Extra/gguf.py +1298 -684
- webscout/Extra/tempmail/README.md +487 -487
- webscout/Extra/tempmail/__init__.py +28 -28
- webscout/Extra/tempmail/async_utils.py +143 -141
- webscout/Extra/tempmail/base.py +172 -161
- webscout/Extra/tempmail/cli.py +191 -187
- webscout/Extra/tempmail/emailnator.py +88 -84
- webscout/Extra/tempmail/mail_tm.py +378 -361
- webscout/Extra/tempmail/temp_mail_io.py +304 -292
- webscout/Extra/weather.py +196 -194
- webscout/Extra/weather_ascii.py +17 -15
- webscout/Provider/AISEARCH/PERPLEXED_search.py +175 -0
- webscout/Provider/AISEARCH/Perplexity.py +292 -333
- webscout/Provider/AISEARCH/README.md +106 -279
- webscout/Provider/AISEARCH/__init__.py +16 -9
- webscout/Provider/AISEARCH/brave_search.py +298 -0
- webscout/Provider/AISEARCH/iask_search.py +357 -410
- webscout/Provider/AISEARCH/monica_search.py +200 -220
- webscout/Provider/AISEARCH/webpilotai_search.py +242 -255
- webscout/Provider/Algion.py +413 -0
- webscout/Provider/Andi.py +74 -69
- webscout/Provider/Apriel.py +313 -0
- webscout/Provider/Ayle.py +323 -0
- webscout/Provider/ChatSandbox.py +329 -342
- webscout/Provider/ClaudeOnline.py +365 -0
- webscout/Provider/Cohere.py +232 -208
- webscout/Provider/DeepAI.py +367 -0
- webscout/Provider/Deepinfra.py +467 -340
- webscout/Provider/EssentialAI.py +217 -0
- webscout/Provider/ExaAI.py +274 -261
- webscout/Provider/Gemini.py +175 -169
- webscout/Provider/GithubChat.py +385 -369
- webscout/Provider/Gradient.py +286 -0
- webscout/Provider/Groq.py +556 -801
- webscout/Provider/HadadXYZ.py +323 -0
- webscout/Provider/HeckAI.py +392 -375
- webscout/Provider/HuggingFace.py +387 -0
- webscout/Provider/IBM.py +340 -0
- webscout/Provider/Jadve.py +317 -291
- webscout/Provider/K2Think.py +306 -0
- webscout/Provider/Koboldai.py +221 -384
- webscout/Provider/Netwrck.py +273 -270
- webscout/Provider/Nvidia.py +310 -0
- webscout/Provider/OPENAI/DeepAI.py +489 -0
- webscout/Provider/OPENAI/K2Think.py +423 -0
- webscout/Provider/OPENAI/PI.py +463 -0
- webscout/Provider/OPENAI/README.md +890 -952
- webscout/Provider/OPENAI/TogetherAI.py +405 -0
- webscout/Provider/OPENAI/TwoAI.py +255 -357
- webscout/Provider/OPENAI/__init__.py +148 -40
- webscout/Provider/OPENAI/ai4chat.py +348 -293
- webscout/Provider/OPENAI/akashgpt.py +436 -0
- webscout/Provider/OPENAI/algion.py +303 -0
- webscout/Provider/OPENAI/{exachat.py → ayle.py} +365 -444
- webscout/Provider/OPENAI/base.py +253 -249
- webscout/Provider/OPENAI/cerebras.py +296 -0
- webscout/Provider/OPENAI/chatgpt.py +870 -556
- webscout/Provider/OPENAI/chatsandbox.py +233 -173
- webscout/Provider/OPENAI/deepinfra.py +403 -322
- webscout/Provider/OPENAI/e2b.py +2370 -1414
- webscout/Provider/OPENAI/elmo.py +278 -0
- webscout/Provider/OPENAI/exaai.py +452 -417
- webscout/Provider/OPENAI/freeassist.py +446 -0
- webscout/Provider/OPENAI/gradient.py +448 -0
- webscout/Provider/OPENAI/groq.py +380 -364
- webscout/Provider/OPENAI/hadadxyz.py +292 -0
- webscout/Provider/OPENAI/heckai.py +333 -308
- webscout/Provider/OPENAI/huggingface.py +321 -0
- webscout/Provider/OPENAI/ibm.py +425 -0
- webscout/Provider/OPENAI/llmchat.py +253 -0
- webscout/Provider/OPENAI/llmchatco.py +378 -335
- webscout/Provider/OPENAI/meta.py +541 -0
- webscout/Provider/OPENAI/netwrck.py +374 -357
- webscout/Provider/OPENAI/nvidia.py +317 -0
- webscout/Provider/OPENAI/oivscode.py +348 -287
- webscout/Provider/OPENAI/openrouter.py +328 -0
- webscout/Provider/OPENAI/pydantic_imports.py +1 -172
- webscout/Provider/OPENAI/sambanova.py +397 -0
- webscout/Provider/OPENAI/sonus.py +305 -304
- webscout/Provider/OPENAI/textpollinations.py +370 -339
- webscout/Provider/OPENAI/toolbaz.py +375 -413
- webscout/Provider/OPENAI/typefully.py +419 -355
- webscout/Provider/OPENAI/typliai.py +279 -0
- webscout/Provider/OPENAI/utils.py +314 -318
- webscout/Provider/OPENAI/wisecat.py +359 -387
- webscout/Provider/OPENAI/writecream.py +185 -163
- webscout/Provider/OPENAI/x0gpt.py +462 -365
- webscout/Provider/OPENAI/zenmux.py +380 -0
- webscout/Provider/OpenRouter.py +386 -0
- webscout/Provider/Openai.py +337 -496
- webscout/Provider/PI.py +443 -429
- webscout/Provider/QwenLM.py +346 -254
- webscout/Provider/STT/__init__.py +28 -0
- webscout/Provider/STT/base.py +303 -0
- webscout/Provider/STT/elevenlabs.py +264 -0
- webscout/Provider/Sambanova.py +317 -0
- webscout/Provider/TTI/README.md +69 -82
- webscout/Provider/TTI/__init__.py +37 -7
- webscout/Provider/TTI/base.py +147 -64
- webscout/Provider/TTI/claudeonline.py +393 -0
- webscout/Provider/TTI/magicstudio.py +292 -201
- webscout/Provider/TTI/miragic.py +180 -0
- webscout/Provider/TTI/pollinations.py +331 -221
- webscout/Provider/TTI/together.py +334 -0
- webscout/Provider/TTI/utils.py +14 -11
- webscout/Provider/TTS/README.md +186 -192
- webscout/Provider/TTS/__init__.py +43 -10
- webscout/Provider/TTS/base.py +523 -159
- webscout/Provider/TTS/deepgram.py +286 -156
- webscout/Provider/TTS/elevenlabs.py +189 -111
- webscout/Provider/TTS/freetts.py +218 -0
- webscout/Provider/TTS/murfai.py +288 -113
- webscout/Provider/TTS/openai_fm.py +364 -129
- webscout/Provider/TTS/parler.py +203 -111
- webscout/Provider/TTS/qwen.py +334 -0
- webscout/Provider/TTS/sherpa.py +286 -0
- webscout/Provider/TTS/speechma.py +693 -580
- webscout/Provider/TTS/streamElements.py +275 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TextPollinationsAI.py +331 -308
- webscout/Provider/TogetherAI.py +450 -0
- webscout/Provider/TwoAI.py +309 -475
- webscout/Provider/TypliAI.py +311 -305
- webscout/Provider/UNFINISHED/ChatHub.py +219 -209
- webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +331 -326
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +300 -295
- webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +218 -198
- webscout/Provider/UNFINISHED/Qodo.py +481 -0
- webscout/Provider/{MCPCore.py → UNFINISHED/XenAI.py} +330 -315
- webscout/Provider/UNFINISHED/Youchat.py +347 -330
- webscout/Provider/UNFINISHED/aihumanizer.py +41 -0
- webscout/Provider/UNFINISHED/grammerchecker.py +37 -0
- webscout/Provider/UNFINISHED/liner.py +342 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +246 -263
- webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +231 -224
- webscout/Provider/WiseCat.py +256 -233
- webscout/Provider/WrDoChat.py +390 -370
- webscout/Provider/__init__.py +115 -174
- webscout/Provider/ai4chat.py +181 -174
- webscout/Provider/akashgpt.py +330 -335
- webscout/Provider/cerebras.py +397 -290
- webscout/Provider/cleeai.py +236 -213
- webscout/Provider/elmo.py +291 -283
- webscout/Provider/geminiapi.py +343 -208
- webscout/Provider/julius.py +245 -223
- webscout/Provider/learnfastai.py +333 -325
- webscout/Provider/llama3mitril.py +230 -215
- webscout/Provider/llmchat.py +308 -258
- webscout/Provider/llmchatco.py +321 -306
- webscout/Provider/meta.py +996 -801
- webscout/Provider/oivscode.py +332 -309
- webscout/Provider/searchchat.py +316 -292
- webscout/Provider/sonus.py +264 -258
- webscout/Provider/toolbaz.py +359 -353
- webscout/Provider/turboseek.py +332 -266
- webscout/Provider/typefully.py +262 -202
- webscout/Provider/x0gpt.py +332 -299
- webscout/__init__.py +31 -39
- webscout/__main__.py +5 -5
- webscout/cli.py +585 -524
- webscout/client.py +1497 -70
- webscout/conversation.py +140 -436
- webscout/exceptions.py +383 -362
- webscout/litagent/__init__.py +29 -29
- webscout/litagent/agent.py +492 -455
- webscout/litagent/constants.py +60 -60
- webscout/models.py +505 -181
- webscout/optimizers.py +74 -420
- webscout/prompt_manager.py +376 -288
- webscout/sanitize.py +1514 -0
- webscout/scout/README.md +452 -404
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +7 -7
- webscout/scout/core/crawler.py +330 -210
- webscout/scout/core/scout.py +800 -607
- webscout/scout/core/search_result.py +51 -96
- webscout/scout/core/text_analyzer.py +64 -63
- webscout/scout/core/text_utils.py +412 -277
- webscout/scout/core/web_analyzer.py +54 -52
- webscout/scout/element.py +872 -478
- webscout/scout/parsers/__init__.py +70 -69
- webscout/scout/parsers/html5lib_parser.py +182 -172
- webscout/scout/parsers/html_parser.py +238 -236
- webscout/scout/parsers/lxml_parser.py +203 -178
- webscout/scout/utils.py +38 -37
- webscout/search/__init__.py +47 -0
- webscout/search/base.py +201 -0
- webscout/search/bing_main.py +45 -0
- webscout/search/brave_main.py +92 -0
- webscout/search/duckduckgo_main.py +57 -0
- webscout/search/engines/__init__.py +127 -0
- webscout/search/engines/bing/__init__.py +15 -0
- webscout/search/engines/bing/base.py +35 -0
- webscout/search/engines/bing/images.py +114 -0
- webscout/search/engines/bing/news.py +96 -0
- webscout/search/engines/bing/suggestions.py +36 -0
- webscout/search/engines/bing/text.py +109 -0
- webscout/search/engines/brave/__init__.py +19 -0
- webscout/search/engines/brave/base.py +47 -0
- webscout/search/engines/brave/images.py +213 -0
- webscout/search/engines/brave/news.py +353 -0
- webscout/search/engines/brave/suggestions.py +318 -0
- webscout/search/engines/brave/text.py +167 -0
- webscout/search/engines/brave/videos.py +364 -0
- webscout/search/engines/duckduckgo/__init__.py +25 -0
- webscout/search/engines/duckduckgo/answers.py +80 -0
- webscout/search/engines/duckduckgo/base.py +189 -0
- webscout/search/engines/duckduckgo/images.py +100 -0
- webscout/search/engines/duckduckgo/maps.py +183 -0
- webscout/search/engines/duckduckgo/news.py +70 -0
- webscout/search/engines/duckduckgo/suggestions.py +22 -0
- webscout/search/engines/duckduckgo/text.py +221 -0
- webscout/search/engines/duckduckgo/translate.py +48 -0
- webscout/search/engines/duckduckgo/videos.py +80 -0
- webscout/search/engines/duckduckgo/weather.py +84 -0
- webscout/search/engines/mojeek.py +61 -0
- webscout/search/engines/wikipedia.py +77 -0
- webscout/search/engines/yahoo/__init__.py +41 -0
- webscout/search/engines/yahoo/answers.py +19 -0
- webscout/search/engines/yahoo/base.py +34 -0
- webscout/search/engines/yahoo/images.py +323 -0
- webscout/search/engines/yahoo/maps.py +19 -0
- webscout/search/engines/yahoo/news.py +258 -0
- webscout/search/engines/yahoo/suggestions.py +140 -0
- webscout/search/engines/yahoo/text.py +273 -0
- webscout/search/engines/yahoo/translate.py +19 -0
- webscout/search/engines/yahoo/videos.py +302 -0
- webscout/search/engines/yahoo/weather.py +220 -0
- webscout/search/engines/yandex.py +67 -0
- webscout/search/engines/yep/__init__.py +13 -0
- webscout/search/engines/yep/base.py +34 -0
- webscout/search/engines/yep/images.py +101 -0
- webscout/search/engines/yep/suggestions.py +38 -0
- webscout/search/engines/yep/text.py +99 -0
- webscout/search/http_client.py +172 -0
- webscout/search/results.py +141 -0
- webscout/search/yahoo_main.py +57 -0
- webscout/search/yep_main.py +48 -0
- webscout/server/__init__.py +48 -0
- webscout/server/config.py +78 -0
- webscout/server/exceptions.py +69 -0
- webscout/server/providers.py +286 -0
- webscout/server/request_models.py +131 -0
- webscout/server/request_processing.py +404 -0
- webscout/server/routes.py +642 -0
- webscout/server/server.py +351 -0
- webscout/server/ui_templates.py +1171 -0
- webscout/swiftcli/__init__.py +79 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +574 -297
- webscout/swiftcli/core/context.py +98 -104
- webscout/swiftcli/core/group.py +268 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +243 -221
- webscout/swiftcli/decorators/options.py +247 -220
- webscout/swiftcli/decorators/output.py +392 -252
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +134 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +58 -59
- webscout/swiftcli/utils/formatting.py +251 -252
- webscout/swiftcli/utils/parsing.py +368 -267
- webscout/update_checker.py +280 -136
- webscout/utils.py +28 -14
- webscout/version.py +2 -1
- webscout/version.py.bak +3 -0
- webscout/zeroart/__init__.py +218 -135
- webscout/zeroart/base.py +70 -66
- webscout/zeroart/effects.py +155 -101
- webscout/zeroart/fonts.py +1799 -1239
- webscout-2026.1.19.dist-info/METADATA +638 -0
- webscout-2026.1.19.dist-info/RECORD +312 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/entry_points.txt +1 -1
- webscout/DWEBS.py +0 -520
- webscout/Extra/Act.md +0 -309
- webscout/Extra/GitToolkit/gitapi/README.md +0 -110
- webscout/Extra/autocoder/__init__.py +0 -9
- webscout/Extra/autocoder/autocoder.py +0 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +0 -332
- webscout/Extra/gguf.md +0 -430
- webscout/Extra/weather.md +0 -281
- webscout/Litlogger/README.md +0 -10
- webscout/Litlogger/__init__.py +0 -15
- webscout/Litlogger/formats.py +0 -4
- webscout/Litlogger/handlers.py +0 -103
- webscout/Litlogger/levels.py +0 -13
- webscout/Litlogger/logger.py +0 -92
- webscout/Provider/AI21.py +0 -177
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/AISEARCH/felo_search.py +0 -202
- webscout/Provider/AISEARCH/genspark_search.py +0 -324
- webscout/Provider/AISEARCH/hika_search.py +0 -186
- webscout/Provider/AISEARCH/scira_search.py +0 -298
- webscout/Provider/Aitopia.py +0 -316
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -791
- webscout/Provider/ChatGPTClone.py +0 -237
- webscout/Provider/ChatGPTGratis.py +0 -194
- webscout/Provider/Cloudflare.py +0 -324
- webscout/Provider/ExaChat.py +0 -358
- webscout/Provider/Flowith.py +0 -217
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/Glider.py +0 -225
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/HuggingFaceChat.py +0 -469
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/LambdaChat.py +0 -411
- webscout/Provider/Llama3.py +0 -259
- webscout/Provider/Nemotron.py +0 -218
- webscout/Provider/OLLAMA.py +0 -396
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -766
- webscout/Provider/OPENAI/Cloudflare.py +0 -378
- webscout/Provider/OPENAI/FreeGemini.py +0 -283
- webscout/Provider/OPENAI/NEMOTRON.py +0 -232
- webscout/Provider/OPENAI/Qwen3.py +0 -283
- webscout/Provider/OPENAI/api.py +0 -969
- webscout/Provider/OPENAI/c4ai.py +0 -373
- webscout/Provider/OPENAI/chatgptclone.py +0 -494
- webscout/Provider/OPENAI/copilot.py +0 -242
- webscout/Provider/OPENAI/flowith.py +0 -162
- webscout/Provider/OPENAI/freeaichat.py +0 -359
- webscout/Provider/OPENAI/mcpcore.py +0 -389
- webscout/Provider/OPENAI/multichat.py +0 -376
- webscout/Provider/OPENAI/opkfc.py +0 -496
- webscout/Provider/OPENAI/scirachat.py +0 -477
- webscout/Provider/OPENAI/standardinput.py +0 -433
- webscout/Provider/OPENAI/typegpt.py +0 -364
- webscout/Provider/OPENAI/uncovrAI.py +0 -463
- webscout/Provider/OPENAI/venice.py +0 -431
- webscout/Provider/OPENAI/yep.py +0 -382
- webscout/Provider/OpenGPT.py +0 -209
- webscout/Provider/Perplexitylabs.py +0 -415
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/StandardInput.py +0 -290
- webscout/Provider/TTI/aiarta.py +0 -365
- webscout/Provider/TTI/artbit.py +0 -0
- webscout/Provider/TTI/fastflux.py +0 -200
- webscout/Provider/TTI/piclumen.py +0 -203
- webscout/Provider/TTI/pixelmuse.py +0 -225
- webscout/Provider/TTS/gesserit.py +0 -128
- webscout/Provider/TTS/sthir.py +0 -94
- webscout/Provider/TeachAnything.py +0 -229
- webscout/Provider/UNFINISHED/puterjs.py +0 -635
- webscout/Provider/UNFINISHED/test_lmarena.py +0 -119
- webscout/Provider/Venice.py +0 -258
- webscout/Provider/VercelAI.py +0 -253
- webscout/Provider/Writecream.py +0 -246
- webscout/Provider/WritingMate.py +0 -269
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/chatglm.py +0 -215
- webscout/Provider/copilot.py +0 -425
- webscout/Provider/freeaichat.py +0 -285
- webscout/Provider/granite.py +0 -235
- webscout/Provider/hermes.py +0 -266
- webscout/Provider/koala.py +0 -170
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/multichat.py +0 -364
- webscout/Provider/scira_chat.py +0 -299
- webscout/Provider/scnet.py +0 -243
- webscout/Provider/talkai.py +0 -194
- webscout/Provider/typegpt.py +0 -289
- webscout/Provider/uncovr.py +0 -368
- webscout/Provider/yep.py +0 -389
- webscout/litagent/Readme.md +0 -276
- webscout/litprinter/__init__.py +0 -59
- webscout/swiftcli/Readme.md +0 -323
- webscout/tempid.py +0 -128
- webscout/webscout_search.py +0 -1184
- webscout/webscout_search_async.py +0 -654
- webscout/yep_search.py +0 -347
- webscout/zeroart/README.md +0 -89
- webscout-8.2.9.dist-info/METADATA +0 -1033
- webscout-8.2.9.dist-info/RECORD +0 -289
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/top_level.txt +0 -0
webscout/Provider/TTI/base.py
CHANGED
|
@@ -1,64 +1,147 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, List, Optional, Union, cast
|
|
3
|
+
|
|
4
|
+
from .utils import ImageResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseImages(ABC):
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def create(
|
|
10
|
+
self,
|
|
11
|
+
*,
|
|
12
|
+
model: str,
|
|
13
|
+
prompt: str,
|
|
14
|
+
n: int = 1,
|
|
15
|
+
size: str = "1024x1024",
|
|
16
|
+
response_format: str = "url",
|
|
17
|
+
user: Optional[str] = None,
|
|
18
|
+
style: str = "none",
|
|
19
|
+
aspect_ratio: str = "1:1",
|
|
20
|
+
timeout: Optional[int] = None,
|
|
21
|
+
image_format: str = "png",
|
|
22
|
+
seed: Optional[int] = None,
|
|
23
|
+
convert_format: bool = False,
|
|
24
|
+
**kwargs,
|
|
25
|
+
) -> ImageResponse:
|
|
26
|
+
"""
|
|
27
|
+
Abstract method to create images from a prompt.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
model: The model to use for image generation.
|
|
31
|
+
prompt: The prompt for the image.
|
|
32
|
+
n: Number of images to generate.
|
|
33
|
+
size: Image size.
|
|
34
|
+
response_format: "url" or "b64_json".
|
|
35
|
+
user: Optional user identifier.
|
|
36
|
+
style: Optional style.
|
|
37
|
+
aspect_ratio: Optional aspect ratio.
|
|
38
|
+
timeout: Request timeout in seconds.
|
|
39
|
+
image_format: "png" or "jpeg" for output format.
|
|
40
|
+
seed: Optional random seed for reproducibility.
|
|
41
|
+
**kwargs: Additional provider-specific parameters.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
ImageResponse: The generated images.
|
|
45
|
+
"""
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# class ProxyAutoMeta(ABCMeta):
|
|
50
|
+
# """Metaclass providing seamless proxy injection for providers."""
|
|
51
|
+
|
|
52
|
+
# def __call__(cls, *args, **kwargs):
|
|
53
|
+
# # Determine if automatic proxying should be disabled
|
|
54
|
+
# disable_auto_proxy = kwargs.get('disable_auto_proxy', False) or getattr(cls, 'DISABLE_AUTO_PROXY', False)
|
|
55
|
+
|
|
56
|
+
# # Proxies may be supplied explicitly
|
|
57
|
+
# proxies = kwargs.get('proxies', None)
|
|
58
|
+
|
|
59
|
+
# # Otherwise try to fetch one automatically
|
|
60
|
+
# if proxies is None and not disable_auto_proxy:
|
|
61
|
+
# try:
|
|
62
|
+
# proxies = {"http": get_auto_proxy(), "https": get_auto_proxy()}
|
|
63
|
+
# except Exception as e:
|
|
64
|
+
# print(f"Failed to fetch auto-proxy: {e}")
|
|
65
|
+
# proxies = {}
|
|
66
|
+
# elif proxies is None:
|
|
67
|
+
# proxies = {}
|
|
68
|
+
|
|
69
|
+
# # No global monkeypatching, just set proxies on the instance
|
|
70
|
+
# instance = super().__call__(*args, **kwargs)
|
|
71
|
+
# instance.proxies = proxies
|
|
72
|
+
|
|
73
|
+
# # If proxies are set, patch any existing session-like attributes
|
|
74
|
+
# if proxies:
|
|
75
|
+
# for attr in dir(instance):
|
|
76
|
+
# obj = getattr(instance, attr)
|
|
77
|
+
# if isinstance(obj, requests.Session):
|
|
78
|
+
# obj.proxies.update(proxies)
|
|
79
|
+
# if CurlSession and isinstance(obj, CurlSession):
|
|
80
|
+
# try:
|
|
81
|
+
# obj.proxies.update(proxies)
|
|
82
|
+
# except (ValueError, KeyError, AttributeError):
|
|
83
|
+
# print("Failed to update proxies for CurlSession due to an expected error.")
|
|
84
|
+
# if CurlAsyncSession and isinstance(obj, CurlAsyncSession):
|
|
85
|
+
# try:
|
|
86
|
+
# obj.proxies.update(proxies)
|
|
87
|
+
# except (ValueError, KeyError, AttributeError):
|
|
88
|
+
# print("Failed to update proxies for CurlAsyncSession due to an expected error.")
|
|
89
|
+
|
|
90
|
+
# # Helper for backward compatibility
|
|
91
|
+
# def get_proxied_session():
|
|
92
|
+
# s = requests.Session()
|
|
93
|
+
# s.proxies.update(proxies)
|
|
94
|
+
# return s
|
|
95
|
+
|
|
96
|
+
# instance.get_proxied_session = get_proxied_session
|
|
97
|
+
|
|
98
|
+
# def get_proxied_curl_session(impersonate="chrome120", **kw):
|
|
99
|
+
# if CurlSession:
|
|
100
|
+
# return CurlSession(proxies=proxies, impersonate=impersonate, **kw)
|
|
101
|
+
# raise ImportError("curl_cffi is not installed")
|
|
102
|
+
|
|
103
|
+
# instance.get_proxied_curl_session = get_proxied_curl_session
|
|
104
|
+
|
|
105
|
+
# def get_proxied_curl_async_session(impersonate="chrome120", **kw):
|
|
106
|
+
# if CurlAsyncSession:
|
|
107
|
+
# return CurlAsyncSession(proxies=proxies, impersonate=impersonate, **kw)
|
|
108
|
+
# raise ImportError("curl_cffi is not installed")
|
|
109
|
+
|
|
110
|
+
# instance.get_proxied_curl_async_session = get_proxied_curl_async_session
|
|
111
|
+
|
|
112
|
+
# return instance
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class TTICompatibleProvider(ABC):
|
|
116
|
+
"""
|
|
117
|
+
Abstract Base Class for TTI providers mimicking the OpenAI Python client structure.
|
|
118
|
+
Requires a nested 'images.create' structure.
|
|
119
|
+
All subclasses automatically get proxy support via ProxyAutoMeta.
|
|
120
|
+
|
|
121
|
+
Class Attributes:
|
|
122
|
+
required_auth (bool): Whether this provider requires authentication (API key, cookie, etc.)
|
|
123
|
+
working (bool): Whether this provider is currently working/operational
|
|
124
|
+
|
|
125
|
+
Available proxy helpers:
|
|
126
|
+
- self.get_proxied_session() - returns a requests.Session with proxies
|
|
127
|
+
- self.get_proxied_curl_session() - returns a curl_cffi.Session with proxies
|
|
128
|
+
- self.get_proxied_curl_async_session() - returns a curl_cffi.AsyncSession with proxies
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
images: BaseImages
|
|
132
|
+
required_auth: bool = False # Default: no auth required
|
|
133
|
+
working: bool = True # Default: provider is working
|
|
134
|
+
AVAILABLE_MODELS: List[str] = [] # List of available models
|
|
135
|
+
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def __init__(self, **kwargs: Any):
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
@abstractmethod
|
|
142
|
+
def models(self) -> Any:
|
|
143
|
+
"""
|
|
144
|
+
Property that returns an object with a .list() method returning available models.
|
|
145
|
+
Subclasses must implement this property.
|
|
146
|
+
"""
|
|
147
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import tempfile
|
|
6
|
+
import time
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
from requests.exceptions import RequestException
|
|
12
|
+
|
|
13
|
+
from webscout.AIbase import SimpleModelList
|
|
14
|
+
from webscout.litagent import LitAgent
|
|
15
|
+
from webscout.Provider.TTI.base import BaseImages, TTICompatibleProvider
|
|
16
|
+
from webscout.Provider.TTI.utils import ImageData, ImageResponse
|
|
17
|
+
|
|
18
|
+
# Optional Pillow import for image format conversion
|
|
19
|
+
Image: Any = None
|
|
20
|
+
PILLOW_AVAILABLE = False
|
|
21
|
+
try:
|
|
22
|
+
from PIL import Image
|
|
23
|
+
|
|
24
|
+
PILLOW_AVAILABLE = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from PIL import Image as PILImage
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _convert_image_format(img_bytes: bytes, image_format: str) -> bytes:
|
|
33
|
+
"""
|
|
34
|
+
Convert image bytes to the specified format using Pillow.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
img_bytes: Raw image bytes
|
|
38
|
+
image_format: Target format ("png" or "jpeg")
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Converted image bytes
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ImportError: If Pillow is not installed
|
|
45
|
+
"""
|
|
46
|
+
if not PILLOW_AVAILABLE or Image is None:
|
|
47
|
+
raise ImportError(
|
|
48
|
+
"Pillow (PIL) is required for image format conversion. "
|
|
49
|
+
"Install it with: pip install pillow"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
with BytesIO(img_bytes) as input_io:
|
|
53
|
+
with Image.open(input_io) as im:
|
|
54
|
+
out_io = BytesIO()
|
|
55
|
+
if image_format.lower() == "jpeg":
|
|
56
|
+
im = im.convert("RGB")
|
|
57
|
+
im.save(out_io, format="JPEG")
|
|
58
|
+
else:
|
|
59
|
+
im.save(out_io, format="PNG")
|
|
60
|
+
return out_io.getvalue()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _detect_image_format(img_bytes: bytes) -> Optional[str]:
|
|
64
|
+
"""
|
|
65
|
+
Detect image format from magic bytes.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
img_bytes: Raw image bytes
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Format string ('png', 'jpeg', 'gif', 'webp') or None if unknown
|
|
72
|
+
"""
|
|
73
|
+
if len(img_bytes) < 12:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
# PNG: 89 50 4E 47 0D 0A 1A 0A
|
|
77
|
+
if img_bytes[:8] == b"\x89PNG\r\n\x1a\n":
|
|
78
|
+
return "png"
|
|
79
|
+
|
|
80
|
+
# JPEG: FF D8 FF
|
|
81
|
+
if img_bytes[:3] == b"\xff\xd8\xff":
|
|
82
|
+
return "jpeg"
|
|
83
|
+
|
|
84
|
+
# GIF: GIF87a or GIF89a
|
|
85
|
+
if img_bytes[:6] in (b"GIF87a", b"GIF89a"):
|
|
86
|
+
return "gif"
|
|
87
|
+
|
|
88
|
+
# WebP: RIFF....WEBP
|
|
89
|
+
if img_bytes[:4] == b"RIFF" and img_bytes[8:12] == b"WEBP":
|
|
90
|
+
return "webp"
|
|
91
|
+
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Images(BaseImages):
|
|
96
|
+
def __init__(self, client: "ClaudeOnlineTTI"):
|
|
97
|
+
self._client = client
|
|
98
|
+
|
|
99
|
+
def create(
|
|
100
|
+
self,
|
|
101
|
+
*,
|
|
102
|
+
model: str,
|
|
103
|
+
prompt: str,
|
|
104
|
+
n: int = 1,
|
|
105
|
+
size: str = "1024x1024",
|
|
106
|
+
response_format: str = "url",
|
|
107
|
+
user: Optional[str] = None,
|
|
108
|
+
style: str = "none",
|
|
109
|
+
aspect_ratio: str = "1:1",
|
|
110
|
+
timeout: Optional[int] = None,
|
|
111
|
+
image_format: str = "png",
|
|
112
|
+
seed: Optional[int] = None,
|
|
113
|
+
convert_format: bool = False,
|
|
114
|
+
**kwargs,
|
|
115
|
+
) -> ImageResponse:
|
|
116
|
+
"""
|
|
117
|
+
Generate images using Claude Online's /imagine feature via Pollinations.ai.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
model: Model to use (ignored, uses Pollinations.ai)
|
|
121
|
+
prompt: The image generation prompt
|
|
122
|
+
n: Number of images to generate (max 1 for Claude Online)
|
|
123
|
+
size: Image size (supports various sizes)
|
|
124
|
+
response_format: "url" or "b64_json"
|
|
125
|
+
user: Optional user identifier
|
|
126
|
+
style: Style parameter (not used)
|
|
127
|
+
aspect_ratio: Aspect ratio (not used)
|
|
128
|
+
timeout: Request timeout in seconds (default: 60)
|
|
129
|
+
image_format: Output format "png" or "jpeg"
|
|
130
|
+
seed: Optional random seed for reproducibility
|
|
131
|
+
convert_format: If True, convert image to specified format (requires Pillow)
|
|
132
|
+
**kwargs: Additional parameters
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
ImageResponse with generated image data
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ImportError: If convert_format is True and Pillow is not installed
|
|
139
|
+
ValueError: If n > 1 or invalid response_format
|
|
140
|
+
RuntimeError: If image generation or upload fails
|
|
141
|
+
"""
|
|
142
|
+
# Use default timeout if not provided
|
|
143
|
+
effective_timeout = timeout if timeout is not None else 60
|
|
144
|
+
|
|
145
|
+
# Claude Online only supports 1 image per request
|
|
146
|
+
if n > 1:
|
|
147
|
+
raise ValueError("Claude Online only supports generating 1 image per request")
|
|
148
|
+
|
|
149
|
+
# Parse size parameter
|
|
150
|
+
width, height = self._parse_size(size)
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
# Clean the prompt (remove command words if present)
|
|
154
|
+
clean_prompt = self._clean_prompt(prompt)
|
|
155
|
+
|
|
156
|
+
# Generate image using Pollinations.ai API
|
|
157
|
+
timestamp = int(time.time() * 1000) # Use timestamp as seed for uniqueness
|
|
158
|
+
seed_value = seed if seed is not None else timestamp
|
|
159
|
+
|
|
160
|
+
# Build the Pollinations.ai URL
|
|
161
|
+
base_url = "https://image.pollinations.ai/prompt"
|
|
162
|
+
params = {"width": width, "height": height, "nologo": "true", "seed": seed_value}
|
|
163
|
+
|
|
164
|
+
image_url = f"{base_url}/{clean_prompt}"
|
|
165
|
+
query_params = "&".join([f"{k}={v}" for k, v in params.items()])
|
|
166
|
+
full_image_url = f"{image_url}?{query_params}"
|
|
167
|
+
|
|
168
|
+
# Download the image
|
|
169
|
+
response = requests.get(full_image_url, timeout=effective_timeout, stream=True)
|
|
170
|
+
response.raise_for_status()
|
|
171
|
+
|
|
172
|
+
img_bytes = response.content
|
|
173
|
+
|
|
174
|
+
# Convert image format if requested
|
|
175
|
+
if convert_format:
|
|
176
|
+
img_bytes = _convert_image_format(img_bytes, image_format)
|
|
177
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
178
|
+
else:
|
|
179
|
+
# Detect actual format from bytes
|
|
180
|
+
detected_format = _detect_image_format(img_bytes)
|
|
181
|
+
ext = detected_format if detected_format else "png"
|
|
182
|
+
if ext == "jpeg":
|
|
183
|
+
ext = "jpg"
|
|
184
|
+
|
|
185
|
+
# Handle response format
|
|
186
|
+
if response_format == "url":
|
|
187
|
+
# Upload to image hosting service
|
|
188
|
+
uploaded_url = self._upload_image(img_bytes, ext, effective_timeout)
|
|
189
|
+
if not uploaded_url:
|
|
190
|
+
raise RuntimeError("Failed to upload generated image")
|
|
191
|
+
result_data = [ImageData(url=uploaded_url)]
|
|
192
|
+
elif response_format == "b64_json":
|
|
193
|
+
b64 = base64.b64encode(img_bytes).decode("utf-8")
|
|
194
|
+
result_data = [ImageData(b64_json=b64)]
|
|
195
|
+
else:
|
|
196
|
+
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
197
|
+
|
|
198
|
+
return ImageResponse(created=int(time.time()), data=result_data)
|
|
199
|
+
|
|
200
|
+
except RequestException as e:
|
|
201
|
+
raise RuntimeError(f"Failed to generate image with Claude Online: {e}")
|
|
202
|
+
except ImportError:
|
|
203
|
+
raise
|
|
204
|
+
except Exception as e:
|
|
205
|
+
raise RuntimeError(f"Unexpected error during image generation: {e}")
|
|
206
|
+
|
|
207
|
+
def _parse_size(self, size: str) -> tuple[int, int]:
|
|
208
|
+
"""Parse size string into width and height."""
|
|
209
|
+
size = size.lower().strip()
|
|
210
|
+
|
|
211
|
+
# Handle common size formats
|
|
212
|
+
size_map = {
|
|
213
|
+
"256x256": (256, 256),
|
|
214
|
+
"512x512": (512, 512),
|
|
215
|
+
"1024x1024": (1024, 1024),
|
|
216
|
+
"1024x768": (1024, 768),
|
|
217
|
+
"768x1024": (768, 1024),
|
|
218
|
+
"1280x720": (1280, 720),
|
|
219
|
+
"720x1280": (720, 1280),
|
|
220
|
+
"1920x1080": (1920, 1080),
|
|
221
|
+
"1080x1920": (1080, 1920),
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if size in size_map:
|
|
225
|
+
return size_map[size]
|
|
226
|
+
|
|
227
|
+
# Try to parse custom size (e.g., "800x600")
|
|
228
|
+
try:
|
|
229
|
+
width, height = size.split("x")
|
|
230
|
+
return int(width), int(height)
|
|
231
|
+
except (ValueError, AttributeError):
|
|
232
|
+
# Default to 1024x1024
|
|
233
|
+
return 1024, 1024
|
|
234
|
+
|
|
235
|
+
def _clean_prompt(self, prompt: str) -> str:
|
|
236
|
+
"""Clean the prompt by removing command prefixes."""
|
|
237
|
+
# Remove common image generation command prefixes
|
|
238
|
+
prefixes_to_remove = [
|
|
239
|
+
r"^/imagine\s*",
|
|
240
|
+
r"^/image\s*",
|
|
241
|
+
r"^/picture\s*",
|
|
242
|
+
r"^/draw\s*",
|
|
243
|
+
r"^/create\s*",
|
|
244
|
+
r"^/generate\s*",
|
|
245
|
+
r"^создай изображение\s*",
|
|
246
|
+
r"^нарисуй\s*",
|
|
247
|
+
r"^сгенерируй картинку\s*",
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
clean_prompt = prompt
|
|
251
|
+
for prefix in prefixes_to_remove:
|
|
252
|
+
clean_prompt = re.sub(prefix, "", clean_prompt, flags=re.IGNORECASE)
|
|
253
|
+
|
|
254
|
+
return clean_prompt.strip()
|
|
255
|
+
|
|
256
|
+
def _upload_image(
|
|
257
|
+
self, img_bytes: bytes, ext: str, timeout: int = 30, max_retries: int = 3
|
|
258
|
+
) -> Optional[str]:
|
|
259
|
+
"""Upload image to hosting service and return URL."""
|
|
260
|
+
|
|
261
|
+
def upload_to_catbox(img_bytes: bytes, ext: str) -> Optional[str]:
|
|
262
|
+
"""Upload to catbox.moe."""
|
|
263
|
+
tmp_path = None
|
|
264
|
+
try:
|
|
265
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
266
|
+
tmp.write(img_bytes)
|
|
267
|
+
tmp.flush()
|
|
268
|
+
tmp_path = tmp.name
|
|
269
|
+
|
|
270
|
+
with open(tmp_path, "rb") as f:
|
|
271
|
+
files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
|
|
272
|
+
data = {"reqtype": "fileupload", "json": "true"}
|
|
273
|
+
headers = {"User-Agent": LitAgent().random()}
|
|
274
|
+
|
|
275
|
+
resp = requests.post(
|
|
276
|
+
"https://catbox.moe/user/api.php",
|
|
277
|
+
files=files,
|
|
278
|
+
data=data,
|
|
279
|
+
headers=headers,
|
|
280
|
+
timeout=timeout,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if resp.status_code == 200 and resp.text.strip():
|
|
284
|
+
text = resp.text.strip()
|
|
285
|
+
if text.startswith("http"):
|
|
286
|
+
return text
|
|
287
|
+
try:
|
|
288
|
+
result = resp.json()
|
|
289
|
+
if "url" in result:
|
|
290
|
+
return result["url"]
|
|
291
|
+
except json.JSONDecodeError:
|
|
292
|
+
pass
|
|
293
|
+
except Exception:
|
|
294
|
+
pass
|
|
295
|
+
finally:
|
|
296
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
297
|
+
try:
|
|
298
|
+
os.remove(tmp_path)
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
def upload_to_0x0(img_bytes: bytes, ext: str) -> Optional[str]:
|
|
304
|
+
"""Upload to 0x0.st as fallback."""
|
|
305
|
+
tmp_path = None
|
|
306
|
+
try:
|
|
307
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
308
|
+
tmp.write(img_bytes)
|
|
309
|
+
tmp.flush()
|
|
310
|
+
tmp_path = tmp.name
|
|
311
|
+
|
|
312
|
+
with open(tmp_path, "rb") as img_file:
|
|
313
|
+
files = {"file": img_file}
|
|
314
|
+
response = requests.post("https://0x0.st", files=files, timeout=timeout)
|
|
315
|
+
response.raise_for_status()
|
|
316
|
+
image_url = response.text.strip()
|
|
317
|
+
if image_url.startswith("http"):
|
|
318
|
+
return image_url
|
|
319
|
+
except Exception:
|
|
320
|
+
pass
|
|
321
|
+
finally:
|
|
322
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
323
|
+
try:
|
|
324
|
+
os.remove(tmp_path)
|
|
325
|
+
except Exception:
|
|
326
|
+
pass
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
# Try primary upload method
|
|
330
|
+
for attempt in range(max_retries):
|
|
331
|
+
uploaded_url = upload_to_catbox(img_bytes, ext)
|
|
332
|
+
if uploaded_url:
|
|
333
|
+
return uploaded_url
|
|
334
|
+
time.sleep(1 * (attempt + 1))
|
|
335
|
+
|
|
336
|
+
# Try fallback method
|
|
337
|
+
for attempt in range(max_retries):
|
|
338
|
+
uploaded_url = upload_to_0x0(img_bytes, ext)
|
|
339
|
+
if uploaded_url:
|
|
340
|
+
return uploaded_url
|
|
341
|
+
time.sleep(1 * (attempt + 1))
|
|
342
|
+
|
|
343
|
+
return None
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class ClaudeOnlineTTI(TTICompatibleProvider):
|
|
347
|
+
"""
|
|
348
|
+
Claude Online Text-to-Image Provider
|
|
349
|
+
|
|
350
|
+
Uses Claude Online's /imagine feature with Pollinations.ai backend.
|
|
351
|
+
Supports high-quality image generation with various styles and sizes.
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
# Provider status
|
|
355
|
+
required_auth: bool = False # No authentication required
|
|
356
|
+
working: bool = True # Currently working
|
|
357
|
+
|
|
358
|
+
AVAILABLE_MODELS = ["claude-imagine"]
|
|
359
|
+
|
|
360
|
+
def __init__(self):
|
|
361
|
+
self.api_endpoint = "https://image.pollinations.ai/prompt"
|
|
362
|
+
self.session = requests.Session()
|
|
363
|
+
self.user_agent = LitAgent().random()
|
|
364
|
+
self.headers = {
|
|
365
|
+
"accept": "image/*",
|
|
366
|
+
"accept-language": "en-US,en;q=0.9",
|
|
367
|
+
"user-agent": self.user_agent,
|
|
368
|
+
}
|
|
369
|
+
self.session.headers.update(self.headers)
|
|
370
|
+
self.images = Images(self)
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def models(self) -> SimpleModelList:
|
|
374
|
+
return SimpleModelList(type(self).AVAILABLE_MODELS)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
if __name__ == "__main__":
|
|
378
|
+
from rich import print
|
|
379
|
+
|
|
380
|
+
# Test the Claude Online TTI provider
|
|
381
|
+
client = ClaudeOnlineTTI()
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
response = client.images.create(
|
|
385
|
+
model="claude-imagine",
|
|
386
|
+
prompt="a beautiful sunset over mountains with vibrant colors",
|
|
387
|
+
response_format="url",
|
|
388
|
+
timeout=60,
|
|
389
|
+
)
|
|
390
|
+
print("✅ Image generation successful!")
|
|
391
|
+
print(response)
|
|
392
|
+
except Exception as e:
|
|
393
|
+
print(f"❌ Image generation failed: {e}")
|