webscout 8.2.2__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.
Files changed (483) hide show
  1. webscout/AIauto.py +524 -143
  2. webscout/AIbase.py +247 -123
  3. webscout/AIutel.py +68 -132
  4. webscout/Bard.py +1072 -535
  5. webscout/Extra/GitToolkit/__init__.py +2 -2
  6. webscout/Extra/GitToolkit/gitapi/__init__.py +20 -12
  7. webscout/Extra/GitToolkit/gitapi/gist.py +142 -0
  8. webscout/Extra/GitToolkit/gitapi/organization.py +91 -0
  9. webscout/Extra/GitToolkit/gitapi/repository.py +308 -195
  10. webscout/Extra/GitToolkit/gitapi/search.py +162 -0
  11. webscout/Extra/GitToolkit/gitapi/trending.py +236 -0
  12. webscout/Extra/GitToolkit/gitapi/user.py +128 -96
  13. webscout/Extra/GitToolkit/gitapi/utils.py +82 -62
  14. webscout/Extra/YTToolkit/README.md +443 -0
  15. webscout/Extra/YTToolkit/YTdownloader.py +953 -957
  16. webscout/Extra/YTToolkit/__init__.py +3 -3
  17. webscout/Extra/YTToolkit/transcriber.py +595 -476
  18. webscout/Extra/YTToolkit/ytapi/README.md +230 -0
  19. webscout/Extra/YTToolkit/ytapi/__init__.py +22 -6
  20. webscout/Extra/YTToolkit/ytapi/captions.py +190 -0
  21. webscout/Extra/YTToolkit/ytapi/channel.py +302 -307
  22. webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
  23. webscout/Extra/YTToolkit/ytapi/extras.py +178 -45
  24. webscout/Extra/YTToolkit/ytapi/hashtag.py +120 -0
  25. webscout/Extra/YTToolkit/ytapi/https.py +89 -88
  26. webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
  27. webscout/Extra/YTToolkit/ytapi/playlist.py +59 -59
  28. webscout/Extra/YTToolkit/ytapi/pool.py +8 -8
  29. webscout/Extra/YTToolkit/ytapi/query.py +143 -40
  30. webscout/Extra/YTToolkit/ytapi/shorts.py +122 -0
  31. webscout/Extra/YTToolkit/ytapi/stream.py +68 -63
  32. webscout/Extra/YTToolkit/ytapi/suggestions.py +97 -0
  33. webscout/Extra/YTToolkit/ytapi/utils.py +66 -62
  34. webscout/Extra/YTToolkit/ytapi/video.py +189 -18
  35. webscout/Extra/__init__.py +2 -3
  36. webscout/Extra/gguf.py +1298 -682
  37. webscout/Extra/tempmail/README.md +488 -0
  38. webscout/Extra/tempmail/__init__.py +28 -28
  39. webscout/Extra/tempmail/async_utils.py +143 -141
  40. webscout/Extra/tempmail/base.py +172 -161
  41. webscout/Extra/tempmail/cli.py +191 -187
  42. webscout/Extra/tempmail/emailnator.py +88 -84
  43. webscout/Extra/tempmail/mail_tm.py +378 -361
  44. webscout/Extra/tempmail/temp_mail_io.py +304 -292
  45. webscout/Extra/weather.py +196 -194
  46. webscout/Extra/weather_ascii.py +17 -15
  47. webscout/Provider/AISEARCH/PERPLEXED_search.py +175 -0
  48. webscout/Provider/AISEARCH/Perplexity.py +237 -304
  49. webscout/Provider/AISEARCH/README.md +106 -0
  50. webscout/Provider/AISEARCH/__init__.py +16 -10
  51. webscout/Provider/AISEARCH/brave_search.py +298 -0
  52. webscout/Provider/AISEARCH/iask_search.py +130 -209
  53. webscout/Provider/AISEARCH/monica_search.py +200 -246
  54. webscout/Provider/AISEARCH/webpilotai_search.py +242 -281
  55. webscout/Provider/Algion.py +413 -0
  56. webscout/Provider/Andi.py +74 -69
  57. webscout/Provider/Apriel.py +313 -0
  58. webscout/Provider/Ayle.py +323 -0
  59. webscout/Provider/ChatSandbox.py +329 -0
  60. webscout/Provider/ClaudeOnline.py +365 -0
  61. webscout/Provider/Cohere.py +232 -208
  62. webscout/Provider/DeepAI.py +367 -0
  63. webscout/Provider/Deepinfra.py +343 -173
  64. webscout/Provider/EssentialAI.py +217 -0
  65. webscout/Provider/ExaAI.py +274 -261
  66. webscout/Provider/Gemini.py +60 -54
  67. webscout/Provider/GithubChat.py +385 -367
  68. webscout/Provider/Gradient.py +286 -0
  69. webscout/Provider/Groq.py +556 -670
  70. webscout/Provider/HadadXYZ.py +323 -0
  71. webscout/Provider/HeckAI.py +392 -233
  72. webscout/Provider/HuggingFace.py +387 -0
  73. webscout/Provider/IBM.py +340 -0
  74. webscout/Provider/Jadve.py +317 -266
  75. webscout/Provider/K2Think.py +306 -0
  76. webscout/Provider/Koboldai.py +221 -381
  77. webscout/Provider/Netwrck.py +273 -228
  78. webscout/Provider/Nvidia.py +310 -0
  79. webscout/Provider/OPENAI/DeepAI.py +489 -0
  80. webscout/Provider/OPENAI/K2Think.py +423 -0
  81. webscout/Provider/OPENAI/PI.py +463 -0
  82. webscout/Provider/OPENAI/README.md +890 -0
  83. webscout/Provider/OPENAI/TogetherAI.py +405 -0
  84. webscout/Provider/OPENAI/TwoAI.py +255 -0
  85. webscout/Provider/OPENAI/__init__.py +148 -25
  86. webscout/Provider/OPENAI/ai4chat.py +348 -0
  87. webscout/Provider/OPENAI/akashgpt.py +436 -0
  88. webscout/Provider/OPENAI/algion.py +303 -0
  89. webscout/Provider/OPENAI/ayle.py +365 -0
  90. webscout/Provider/OPENAI/base.py +253 -46
  91. webscout/Provider/OPENAI/cerebras.py +296 -0
  92. webscout/Provider/OPENAI/chatgpt.py +514 -193
  93. webscout/Provider/OPENAI/chatsandbox.py +233 -0
  94. webscout/Provider/OPENAI/deepinfra.py +403 -272
  95. webscout/Provider/OPENAI/e2b.py +2370 -1350
  96. webscout/Provider/OPENAI/elmo.py +278 -0
  97. webscout/Provider/OPENAI/exaai.py +186 -138
  98. webscout/Provider/OPENAI/freeassist.py +446 -0
  99. webscout/Provider/OPENAI/gradient.py +448 -0
  100. webscout/Provider/OPENAI/groq.py +380 -0
  101. webscout/Provider/OPENAI/hadadxyz.py +292 -0
  102. webscout/Provider/OPENAI/heckai.py +100 -104
  103. webscout/Provider/OPENAI/huggingface.py +321 -0
  104. webscout/Provider/OPENAI/ibm.py +425 -0
  105. webscout/Provider/OPENAI/llmchat.py +253 -0
  106. webscout/Provider/OPENAI/llmchatco.py +378 -327
  107. webscout/Provider/OPENAI/meta.py +541 -0
  108. webscout/Provider/OPENAI/netwrck.py +110 -84
  109. webscout/Provider/OPENAI/nvidia.py +317 -0
  110. webscout/Provider/OPENAI/oivscode.py +348 -0
  111. webscout/Provider/OPENAI/openrouter.py +328 -0
  112. webscout/Provider/OPENAI/pydantic_imports.py +1 -0
  113. webscout/Provider/OPENAI/sambanova.py +397 -0
  114. webscout/Provider/OPENAI/sonus.py +126 -115
  115. webscout/Provider/OPENAI/textpollinations.py +218 -133
  116. webscout/Provider/OPENAI/toolbaz.py +136 -166
  117. webscout/Provider/OPENAI/typefully.py +419 -0
  118. webscout/Provider/OPENAI/typliai.py +279 -0
  119. webscout/Provider/OPENAI/utils.py +314 -211
  120. webscout/Provider/OPENAI/wisecat.py +103 -125
  121. webscout/Provider/OPENAI/writecream.py +185 -156
  122. webscout/Provider/OPENAI/x0gpt.py +227 -136
  123. webscout/Provider/OPENAI/zenmux.py +380 -0
  124. webscout/Provider/OpenRouter.py +386 -0
  125. webscout/Provider/Openai.py +337 -496
  126. webscout/Provider/PI.py +443 -344
  127. webscout/Provider/QwenLM.py +346 -254
  128. webscout/Provider/STT/__init__.py +28 -0
  129. webscout/Provider/STT/base.py +303 -0
  130. webscout/Provider/STT/elevenlabs.py +264 -0
  131. webscout/Provider/Sambanova.py +317 -0
  132. webscout/Provider/TTI/README.md +69 -0
  133. webscout/Provider/TTI/__init__.py +37 -12
  134. webscout/Provider/TTI/base.py +147 -0
  135. webscout/Provider/TTI/claudeonline.py +393 -0
  136. webscout/Provider/TTI/magicstudio.py +292 -0
  137. webscout/Provider/TTI/miragic.py +180 -0
  138. webscout/Provider/TTI/pollinations.py +331 -0
  139. webscout/Provider/TTI/together.py +334 -0
  140. webscout/Provider/TTI/utils.py +14 -0
  141. webscout/Provider/TTS/README.md +186 -0
  142. webscout/Provider/TTS/__init__.py +43 -7
  143. webscout/Provider/TTS/base.py +523 -0
  144. webscout/Provider/TTS/deepgram.py +286 -156
  145. webscout/Provider/TTS/elevenlabs.py +189 -111
  146. webscout/Provider/TTS/freetts.py +218 -0
  147. webscout/Provider/TTS/murfai.py +288 -113
  148. webscout/Provider/TTS/openai_fm.py +364 -0
  149. webscout/Provider/TTS/parler.py +203 -111
  150. webscout/Provider/TTS/qwen.py +334 -0
  151. webscout/Provider/TTS/sherpa.py +286 -0
  152. webscout/Provider/TTS/speechma.py +693 -180
  153. webscout/Provider/TTS/streamElements.py +275 -333
  154. webscout/Provider/TTS/utils.py +280 -280
  155. webscout/Provider/TextPollinationsAI.py +221 -121
  156. webscout/Provider/TogetherAI.py +450 -0
  157. webscout/Provider/TwoAI.py +309 -199
  158. webscout/Provider/TypliAI.py +311 -0
  159. webscout/Provider/UNFINISHED/ChatHub.py +219 -0
  160. webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +160 -145
  161. webscout/Provider/UNFINISHED/GizAI.py +300 -0
  162. webscout/Provider/UNFINISHED/Marcus.py +218 -0
  163. webscout/Provider/UNFINISHED/Qodo.py +481 -0
  164. webscout/Provider/UNFINISHED/XenAI.py +330 -0
  165. webscout/Provider/{Youchat.py → UNFINISHED/Youchat.py} +64 -47
  166. webscout/Provider/UNFINISHED/aihumanizer.py +41 -0
  167. webscout/Provider/UNFINISHED/grammerchecker.py +37 -0
  168. webscout/Provider/UNFINISHED/liner.py +342 -0
  169. webscout/Provider/UNFINISHED/liner_api_request.py +246 -0
  170. webscout/Provider/UNFINISHED/samurai.py +231 -0
  171. webscout/Provider/WiseCat.py +256 -196
  172. webscout/Provider/WrDoChat.py +390 -0
  173. webscout/Provider/__init__.py +115 -198
  174. webscout/Provider/ai4chat.py +181 -202
  175. webscout/Provider/akashgpt.py +330 -342
  176. webscout/Provider/cerebras.py +397 -242
  177. webscout/Provider/cleeai.py +236 -213
  178. webscout/Provider/elmo.py +291 -234
  179. webscout/Provider/geminiapi.py +343 -208
  180. webscout/Provider/julius.py +245 -223
  181. webscout/Provider/learnfastai.py +333 -266
  182. webscout/Provider/llama3mitril.py +230 -180
  183. webscout/Provider/llmchat.py +308 -213
  184. webscout/Provider/llmchatco.py +321 -311
  185. webscout/Provider/meta.py +996 -794
  186. webscout/Provider/oivscode.py +332 -0
  187. webscout/Provider/searchchat.py +316 -293
  188. webscout/Provider/sonus.py +264 -208
  189. webscout/Provider/toolbaz.py +359 -320
  190. webscout/Provider/turboseek.py +332 -219
  191. webscout/Provider/typefully.py +262 -280
  192. webscout/Provider/x0gpt.py +332 -256
  193. webscout/__init__.py +31 -38
  194. webscout/__main__.py +5 -5
  195. webscout/cli.py +585 -293
  196. webscout/client.py +1497 -0
  197. webscout/conversation.py +140 -565
  198. webscout/exceptions.py +383 -339
  199. webscout/litagent/__init__.py +29 -29
  200. webscout/litagent/agent.py +492 -455
  201. webscout/litagent/constants.py +60 -60
  202. webscout/models.py +505 -181
  203. webscout/optimizers.py +32 -378
  204. webscout/prompt_manager.py +376 -274
  205. webscout/sanitize.py +1514 -0
  206. webscout/scout/README.md +452 -0
  207. webscout/scout/__init__.py +8 -8
  208. webscout/scout/core/__init__.py +7 -7
  209. webscout/scout/core/crawler.py +330 -140
  210. webscout/scout/core/scout.py +800 -568
  211. webscout/scout/core/search_result.py +51 -96
  212. webscout/scout/core/text_analyzer.py +64 -63
  213. webscout/scout/core/text_utils.py +412 -277
  214. webscout/scout/core/web_analyzer.py +54 -52
  215. webscout/scout/element.py +872 -460
  216. webscout/scout/parsers/__init__.py +70 -69
  217. webscout/scout/parsers/html5lib_parser.py +182 -172
  218. webscout/scout/parsers/html_parser.py +238 -236
  219. webscout/scout/parsers/lxml_parser.py +203 -178
  220. webscout/scout/utils.py +38 -37
  221. webscout/search/__init__.py +47 -0
  222. webscout/search/base.py +201 -0
  223. webscout/search/bing_main.py +45 -0
  224. webscout/search/brave_main.py +92 -0
  225. webscout/search/duckduckgo_main.py +57 -0
  226. webscout/search/engines/__init__.py +127 -0
  227. webscout/search/engines/bing/__init__.py +15 -0
  228. webscout/search/engines/bing/base.py +35 -0
  229. webscout/search/engines/bing/images.py +114 -0
  230. webscout/search/engines/bing/news.py +96 -0
  231. webscout/search/engines/bing/suggestions.py +36 -0
  232. webscout/search/engines/bing/text.py +109 -0
  233. webscout/search/engines/brave/__init__.py +19 -0
  234. webscout/search/engines/brave/base.py +47 -0
  235. webscout/search/engines/brave/images.py +213 -0
  236. webscout/search/engines/brave/news.py +353 -0
  237. webscout/search/engines/brave/suggestions.py +318 -0
  238. webscout/search/engines/brave/text.py +167 -0
  239. webscout/search/engines/brave/videos.py +364 -0
  240. webscout/search/engines/duckduckgo/__init__.py +25 -0
  241. webscout/search/engines/duckduckgo/answers.py +80 -0
  242. webscout/search/engines/duckduckgo/base.py +189 -0
  243. webscout/search/engines/duckduckgo/images.py +100 -0
  244. webscout/search/engines/duckduckgo/maps.py +183 -0
  245. webscout/search/engines/duckduckgo/news.py +70 -0
  246. webscout/search/engines/duckduckgo/suggestions.py +22 -0
  247. webscout/search/engines/duckduckgo/text.py +221 -0
  248. webscout/search/engines/duckduckgo/translate.py +48 -0
  249. webscout/search/engines/duckduckgo/videos.py +80 -0
  250. webscout/search/engines/duckduckgo/weather.py +84 -0
  251. webscout/search/engines/mojeek.py +61 -0
  252. webscout/search/engines/wikipedia.py +77 -0
  253. webscout/search/engines/yahoo/__init__.py +41 -0
  254. webscout/search/engines/yahoo/answers.py +19 -0
  255. webscout/search/engines/yahoo/base.py +34 -0
  256. webscout/search/engines/yahoo/images.py +323 -0
  257. webscout/search/engines/yahoo/maps.py +19 -0
  258. webscout/search/engines/yahoo/news.py +258 -0
  259. webscout/search/engines/yahoo/suggestions.py +140 -0
  260. webscout/search/engines/yahoo/text.py +273 -0
  261. webscout/search/engines/yahoo/translate.py +19 -0
  262. webscout/search/engines/yahoo/videos.py +302 -0
  263. webscout/search/engines/yahoo/weather.py +220 -0
  264. webscout/search/engines/yandex.py +67 -0
  265. webscout/search/engines/yep/__init__.py +13 -0
  266. webscout/search/engines/yep/base.py +34 -0
  267. webscout/search/engines/yep/images.py +101 -0
  268. webscout/search/engines/yep/suggestions.py +38 -0
  269. webscout/search/engines/yep/text.py +99 -0
  270. webscout/search/http_client.py +172 -0
  271. webscout/search/results.py +141 -0
  272. webscout/search/yahoo_main.py +57 -0
  273. webscout/search/yep_main.py +48 -0
  274. webscout/server/__init__.py +48 -0
  275. webscout/server/config.py +78 -0
  276. webscout/server/exceptions.py +69 -0
  277. webscout/server/providers.py +286 -0
  278. webscout/server/request_models.py +131 -0
  279. webscout/server/request_processing.py +404 -0
  280. webscout/server/routes.py +642 -0
  281. webscout/server/server.py +351 -0
  282. webscout/server/ui_templates.py +1171 -0
  283. webscout/swiftcli/__init__.py +79 -809
  284. webscout/swiftcli/core/__init__.py +7 -0
  285. webscout/swiftcli/core/cli.py +574 -0
  286. webscout/swiftcli/core/context.py +98 -0
  287. webscout/swiftcli/core/group.py +268 -0
  288. webscout/swiftcli/decorators/__init__.py +28 -0
  289. webscout/swiftcli/decorators/command.py +243 -0
  290. webscout/swiftcli/decorators/options.py +247 -0
  291. webscout/swiftcli/decorators/output.py +392 -0
  292. webscout/swiftcli/exceptions.py +21 -0
  293. webscout/swiftcli/plugins/__init__.py +9 -0
  294. webscout/swiftcli/plugins/base.py +134 -0
  295. webscout/swiftcli/plugins/manager.py +269 -0
  296. webscout/swiftcli/utils/__init__.py +58 -0
  297. webscout/swiftcli/utils/formatting.py +251 -0
  298. webscout/swiftcli/utils/parsing.py +368 -0
  299. webscout/update_checker.py +280 -136
  300. webscout/utils.py +28 -14
  301. webscout/version.py +2 -1
  302. webscout/version.py.bak +3 -0
  303. webscout/zeroart/__init__.py +218 -55
  304. webscout/zeroart/base.py +70 -60
  305. webscout/zeroart/effects.py +155 -99
  306. webscout/zeroart/fonts.py +1799 -816
  307. webscout-2026.1.19.dist-info/METADATA +638 -0
  308. webscout-2026.1.19.dist-info/RECORD +312 -0
  309. {webscout-8.2.2.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
  310. webscout-2026.1.19.dist-info/entry_points.txt +4 -0
  311. webscout-2026.1.19.dist-info/top_level.txt +1 -0
  312. inferno/__init__.py +0 -6
  313. inferno/__main__.py +0 -9
  314. inferno/cli.py +0 -6
  315. webscout/DWEBS.py +0 -477
  316. webscout/Extra/autocoder/__init__.py +0 -9
  317. webscout/Extra/autocoder/autocoder.py +0 -849
  318. webscout/Extra/autocoder/autocoder_utiles.py +0 -332
  319. webscout/LLM.py +0 -442
  320. webscout/Litlogger/__init__.py +0 -67
  321. webscout/Litlogger/core/__init__.py +0 -6
  322. webscout/Litlogger/core/level.py +0 -23
  323. webscout/Litlogger/core/logger.py +0 -165
  324. webscout/Litlogger/handlers/__init__.py +0 -12
  325. webscout/Litlogger/handlers/console.py +0 -33
  326. webscout/Litlogger/handlers/file.py +0 -143
  327. webscout/Litlogger/handlers/network.py +0 -173
  328. webscout/Litlogger/styles/__init__.py +0 -7
  329. webscout/Litlogger/styles/colors.py +0 -249
  330. webscout/Litlogger/styles/formats.py +0 -458
  331. webscout/Litlogger/styles/text.py +0 -87
  332. webscout/Litlogger/utils/__init__.py +0 -6
  333. webscout/Litlogger/utils/detectors.py +0 -153
  334. webscout/Litlogger/utils/formatters.py +0 -200
  335. webscout/Local/__init__.py +0 -12
  336. webscout/Local/__main__.py +0 -9
  337. webscout/Local/api.py +0 -576
  338. webscout/Local/cli.py +0 -516
  339. webscout/Local/config.py +0 -75
  340. webscout/Local/llm.py +0 -287
  341. webscout/Local/model_manager.py +0 -253
  342. webscout/Local/server.py +0 -721
  343. webscout/Local/utils.py +0 -93
  344. webscout/Provider/AI21.py +0 -177
  345. webscout/Provider/AISEARCH/DeepFind.py +0 -250
  346. webscout/Provider/AISEARCH/ISou.py +0 -256
  347. webscout/Provider/AISEARCH/felo_search.py +0 -228
  348. webscout/Provider/AISEARCH/genspark_search.py +0 -208
  349. webscout/Provider/AISEARCH/hika_search.py +0 -194
  350. webscout/Provider/AISEARCH/scira_search.py +0 -324
  351. webscout/Provider/Aitopia.py +0 -292
  352. webscout/Provider/AllenAI.py +0 -413
  353. webscout/Provider/Blackboxai.py +0 -229
  354. webscout/Provider/C4ai.py +0 -432
  355. webscout/Provider/ChatGPTClone.py +0 -226
  356. webscout/Provider/ChatGPTES.py +0 -237
  357. webscout/Provider/ChatGPTGratis.py +0 -194
  358. webscout/Provider/Chatify.py +0 -175
  359. webscout/Provider/Cloudflare.py +0 -273
  360. webscout/Provider/DeepSeek.py +0 -196
  361. webscout/Provider/ElectronHub.py +0 -709
  362. webscout/Provider/ExaChat.py +0 -342
  363. webscout/Provider/Free2GPT.py +0 -241
  364. webscout/Provider/GPTWeb.py +0 -193
  365. webscout/Provider/Glider.py +0 -211
  366. webscout/Provider/HF_space/__init__.py +0 -0
  367. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  368. webscout/Provider/HuggingFaceChat.py +0 -462
  369. webscout/Provider/Hunyuan.py +0 -272
  370. webscout/Provider/LambdaChat.py +0 -392
  371. webscout/Provider/Llama.py +0 -200
  372. webscout/Provider/Llama3.py +0 -204
  373. webscout/Provider/Marcus.py +0 -148
  374. webscout/Provider/OLLAMA.py +0 -396
  375. webscout/Provider/OPENAI/c4ai.py +0 -367
  376. webscout/Provider/OPENAI/chatgptclone.py +0 -460
  377. webscout/Provider/OPENAI/exachat.py +0 -433
  378. webscout/Provider/OPENAI/freeaichat.py +0 -352
  379. webscout/Provider/OPENAI/opkfc.py +0 -488
  380. webscout/Provider/OPENAI/scirachat.py +0 -463
  381. webscout/Provider/OPENAI/standardinput.py +0 -425
  382. webscout/Provider/OPENAI/typegpt.py +0 -346
  383. webscout/Provider/OPENAI/uncovrAI.py +0 -455
  384. webscout/Provider/OPENAI/venice.py +0 -413
  385. webscout/Provider/OPENAI/yep.py +0 -327
  386. webscout/Provider/OpenGPT.py +0 -199
  387. webscout/Provider/Perplexitylabs.py +0 -415
  388. webscout/Provider/Phind.py +0 -535
  389. webscout/Provider/PizzaGPT.py +0 -198
  390. webscout/Provider/Reka.py +0 -214
  391. webscout/Provider/StandardInput.py +0 -278
  392. webscout/Provider/TTI/AiForce/__init__.py +0 -22
  393. webscout/Provider/TTI/AiForce/async_aiforce.py +0 -224
  394. webscout/Provider/TTI/AiForce/sync_aiforce.py +0 -245
  395. webscout/Provider/TTI/FreeAIPlayground/__init__.py +0 -9
  396. webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +0 -181
  397. webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +0 -180
  398. webscout/Provider/TTI/ImgSys/__init__.py +0 -23
  399. webscout/Provider/TTI/ImgSys/async_imgsys.py +0 -202
  400. webscout/Provider/TTI/ImgSys/sync_imgsys.py +0 -195
  401. webscout/Provider/TTI/MagicStudio/__init__.py +0 -2
  402. webscout/Provider/TTI/MagicStudio/async_magicstudio.py +0 -111
  403. webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +0 -109
  404. webscout/Provider/TTI/Nexra/__init__.py +0 -22
  405. webscout/Provider/TTI/Nexra/async_nexra.py +0 -286
  406. webscout/Provider/TTI/Nexra/sync_nexra.py +0 -258
  407. webscout/Provider/TTI/PollinationsAI/__init__.py +0 -23
  408. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +0 -311
  409. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +0 -265
  410. webscout/Provider/TTI/aiarta/__init__.py +0 -2
  411. webscout/Provider/TTI/aiarta/async_aiarta.py +0 -482
  412. webscout/Provider/TTI/aiarta/sync_aiarta.py +0 -440
  413. webscout/Provider/TTI/artbit/__init__.py +0 -22
  414. webscout/Provider/TTI/artbit/async_artbit.py +0 -155
  415. webscout/Provider/TTI/artbit/sync_artbit.py +0 -148
  416. webscout/Provider/TTI/fastflux/__init__.py +0 -22
  417. webscout/Provider/TTI/fastflux/async_fastflux.py +0 -261
  418. webscout/Provider/TTI/fastflux/sync_fastflux.py +0 -252
  419. webscout/Provider/TTI/huggingface/__init__.py +0 -22
  420. webscout/Provider/TTI/huggingface/async_huggingface.py +0 -199
  421. webscout/Provider/TTI/huggingface/sync_huggingface.py +0 -195
  422. webscout/Provider/TTI/piclumen/__init__.py +0 -23
  423. webscout/Provider/TTI/piclumen/async_piclumen.py +0 -268
  424. webscout/Provider/TTI/piclumen/sync_piclumen.py +0 -233
  425. webscout/Provider/TTI/pixelmuse/__init__.py +0 -4
  426. webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +0 -249
  427. webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +0 -182
  428. webscout/Provider/TTI/talkai/__init__.py +0 -4
  429. webscout/Provider/TTI/talkai/async_talkai.py +0 -229
  430. webscout/Provider/TTI/talkai/sync_talkai.py +0 -207
  431. webscout/Provider/TTS/gesserit.py +0 -127
  432. webscout/Provider/TeachAnything.py +0 -187
  433. webscout/Provider/Venice.py +0 -219
  434. webscout/Provider/VercelAI.py +0 -234
  435. webscout/Provider/WebSim.py +0 -228
  436. webscout/Provider/Writecream.py +0 -211
  437. webscout/Provider/WritingMate.py +0 -197
  438. webscout/Provider/aimathgpt.py +0 -189
  439. webscout/Provider/askmyai.py +0 -158
  440. webscout/Provider/asksteve.py +0 -203
  441. webscout/Provider/bagoodex.py +0 -145
  442. webscout/Provider/chatglm.py +0 -205
  443. webscout/Provider/copilot.py +0 -428
  444. webscout/Provider/freeaichat.py +0 -271
  445. webscout/Provider/gaurish.py +0 -244
  446. webscout/Provider/geminiprorealtime.py +0 -160
  447. webscout/Provider/granite.py +0 -187
  448. webscout/Provider/hermes.py +0 -219
  449. webscout/Provider/koala.py +0 -268
  450. webscout/Provider/labyrinth.py +0 -340
  451. webscout/Provider/lepton.py +0 -194
  452. webscout/Provider/llamatutor.py +0 -192
  453. webscout/Provider/multichat.py +0 -325
  454. webscout/Provider/promptrefine.py +0 -193
  455. webscout/Provider/scira_chat.py +0 -277
  456. webscout/Provider/scnet.py +0 -187
  457. webscout/Provider/talkai.py +0 -194
  458. webscout/Provider/tutorai.py +0 -252
  459. webscout/Provider/typegpt.py +0 -232
  460. webscout/Provider/uncovr.py +0 -312
  461. webscout/Provider/yep.py +0 -376
  462. webscout/litprinter/__init__.py +0 -59
  463. webscout/scout/core.py +0 -881
  464. webscout/tempid.py +0 -128
  465. webscout/webscout_search.py +0 -1346
  466. webscout/webscout_search_async.py +0 -877
  467. webscout/yep_search.py +0 -297
  468. webscout-8.2.2.dist-info/METADATA +0 -734
  469. webscout-8.2.2.dist-info/RECORD +0 -309
  470. webscout-8.2.2.dist-info/entry_points.txt +0 -5
  471. webscout-8.2.2.dist-info/top_level.txt +0 -3
  472. webstoken/__init__.py +0 -30
  473. webstoken/classifier.py +0 -189
  474. webstoken/keywords.py +0 -216
  475. webstoken/language.py +0 -128
  476. webstoken/ner.py +0 -164
  477. webstoken/normalizer.py +0 -35
  478. webstoken/processor.py +0 -77
  479. webstoken/sentiment.py +0 -206
  480. webstoken/stemmer.py +0 -73
  481. webstoken/tagger.py +0 -60
  482. webstoken/tokenizer.py +0 -158
  483. {webscout-8.2.2.dist-info → webscout-2026.1.19.dist-info/licenses}/LICENSE.md +0 -0
webscout/Provider/PI.py CHANGED
@@ -1,344 +1,443 @@
1
- from uuid import uuid4
2
- import cloudscraper
3
- import json
4
- import re
5
- import threading
6
- import requests
7
- from webscout.AIutel import Optimizers
8
- from webscout.AIutel import Conversation
9
- from webscout.AIutel import AwesomePrompts
10
- from webscout.AIbase import Provider
11
- from typing import Dict, Union, Any, Optional
12
- from webscout.litagent import LitAgent
13
-
14
- class PiAI(Provider):
15
- """
16
- PiAI is a provider class for interacting with the Pi.ai chat API.
17
-
18
- Attributes:
19
- knowledge_cutoff (str): The knowledge cutoff date for the model
20
- AVAILABLE_VOICES (Dict[str, int]): Available voice options for audio responses
21
- AVAILABLE_MODELS (List[str]): Available model options for the API
22
- """
23
- AVAILABLE_MODELS = ["inflection_3_pi"]
24
- AVAILABLE_VOICES: Dict[str, int] = {
25
- "voice1": 1,
26
- "voice2": 2,
27
- "voice3": 3,
28
- "voice4": 4,
29
- "voice5": 5,
30
- "voice6": 6,
31
- "voice7": 7,
32
- "voice8": 8
33
- }
34
-
35
- def __init__(
36
- self,
37
- is_conversation: bool = True,
38
- max_tokens: int = 2048,
39
- timeout: int = 30,
40
- intro: str = None,
41
- filepath: str = None,
42
- update_file: bool = True,
43
- proxies: dict = {},
44
- history_offset: int = 10250,
45
- act: str = None,
46
- voice: bool = False,
47
- voice_name: str = "voice3",
48
- output_file: str = "PiAI.mp3",
49
- model: str = "inflection_3_pi",
50
- ):
51
- """
52
- Initializes PiAI with voice support.
53
-
54
- Args:
55
- voice (bool): Enable/disable voice output
56
- voice_name (str): Name of the voice to use (if None, uses default)
57
- output_file (str): Path to save voice output (default: PiAI.mp3)
58
- """
59
- # Voice settings
60
- self.voice_enabled = voice
61
- self.voice_name = voice_name
62
- self.output_file = output_file
63
-
64
- if voice and voice_name and voice_name not in self.AVAILABLE_VOICES:
65
- raise ValueError(f"Voice '{voice_name}' not available. Choose from: {list(self.AVAILABLE_VOICES.keys())}")
66
-
67
- # Initialize other attributes
68
- self.scraper = cloudscraper.create_scraper()
69
- self.primary_url = 'https://pi.ai/api/chat'
70
- self.fallback_url = 'https://pi.ai/api/v2/chat'
71
- self.url = self.primary_url
72
- self.headers = {
73
- 'Accept': 'text/event-stream',
74
- 'Accept-Encoding': 'gzip, deflate, br, zstd',
75
- 'Accept-Language': 'en-US,en;q=0.9,en-IN;q=0.8',
76
- 'Content-Type': 'application/json',
77
- 'DNT': '1',
78
- 'Origin': 'https://pi.ai',
79
- 'Referer': 'https://pi.ai/talk',
80
- 'Sec-CH-UA': '"Not)A;Brand";v="99", "Microsoft Edge";v="127", "Chromium";v="127"',
81
- 'Sec-CH-UA-Mobile': '?0',
82
- 'Sec-CH-UA-Platform': '"Windows"',
83
- 'Sec-Fetch-Dest': 'empty',
84
- 'Sec-Fetch-Mode': 'cors',
85
- 'Sec-Fetch-Site': 'same-origin',
86
- 'User-Agent': LitAgent().random(),
87
- 'X-Api-Version': '3'
88
- }
89
- self.cookies = {
90
- '__cf_bm': uuid4().hex
91
- }
92
-
93
- self.session = requests.Session()
94
- self.session.headers.update(self.headers)
95
- self.session.proxies = proxies
96
-
97
- self.is_conversation = is_conversation
98
- self.max_tokens_to_sample = max_tokens
99
- self.timeout = timeout
100
- self.last_response = {} if self.is_conversation else {'text': ""}
101
- self.conversation_id = None
102
-
103
- self.__available_optimizers = (
104
- method
105
- for method in dir(Optimizers)
106
- if callable(getattr(Optimizers, method)) and not method.startswith("__")
107
- )
108
-
109
- # Setup conversation
110
- Conversation.intro = (
111
- AwesomePrompts().get_act(
112
- act, raise_not_found=True, default=None, case_insensitive=True
113
- ) if act else intro or Conversation.intro
114
- )
115
- self.conversation = Conversation(
116
- is_conversation, self.max_tokens_to_sample, filepath, update_file
117
- )
118
- self.conversation.history_offset = history_offset
119
- self.session.proxies = proxies
120
-
121
- if self.is_conversation:
122
- self.start_conversation()
123
-
124
- def start_conversation(self) -> str:
125
- """
126
- Initializes a new conversation and returns the conversation ID.
127
- """
128
- response = self.scraper.post(
129
- "https://pi.ai/api/chat/start",
130
- headers=self.headers,
131
- cookies=self.cookies,
132
- json={},
133
- timeout=self.timeout
134
- )
135
-
136
- if not response.ok:
137
- raise Exception(f"Failed to start conversation: {response.status_code}")
138
-
139
- data = response.json()
140
- self.conversation_id = data['conversations'][0]['sid']
141
-
142
- return self.conversation_id
143
-
144
- def ask(
145
- self,
146
- prompt: str,
147
- stream: bool = False,
148
- raw: bool = False,
149
- optimizer: str = None,
150
- conversationally: bool = False,
151
- voice: bool = None,
152
- voice_name: str = None,
153
- output_file: str = None
154
- ) -> dict:
155
- """
156
- Interact with Pi.ai by sending a prompt and receiving a response.
157
-
158
- Args:
159
- prompt (str): The prompt to send
160
- stream (bool): Whether to stream the response
161
- raw (bool): Return raw response format
162
- optimizer (str): Prompt optimizer to use
163
- conversationally (bool): Use conversation context
164
- voice (bool): Override default voice setting
165
- voice_name (str): Override default voice name
166
- output_file (str): Override default output file path
167
- """
168
- # Voice configuration
169
- voice = self.voice_enabled if voice is None else voice
170
- voice_name = self.voice_name if voice_name is None else voice_name
171
- output_file = self.output_file if output_file is None else output_file
172
-
173
- if voice and voice_name and voice_name not in self.AVAILABLE_VOICES:
174
- raise ValueError(f"Voice '{voice_name}' not available. Choose from: {list(self.AVAILABLE_VOICES.keys())}")
175
-
176
- conversation_prompt = self.conversation.gen_complete_prompt(prompt)
177
- if optimizer:
178
- if optimizer in self.__available_optimizers:
179
- conversation_prompt = getattr(Optimizers, optimizer)(
180
- conversation_prompt if conversationally else prompt
181
- )
182
- else:
183
- raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
184
-
185
- data = {
186
- 'text': conversation_prompt,
187
- 'conversation': self.conversation_id
188
- }
189
-
190
- def process_stream():
191
- # Try primary URL first
192
- response = self.scraper.post(
193
- self.url,
194
- headers=self.headers,
195
- cookies=self.cookies,
196
- json=data,
197
- stream=True,
198
- timeout=self.timeout
199
- )
200
-
201
- # If primary URL fails, try fallback URL
202
- if not response.ok and self.url == self.primary_url:
203
- self.url = self.fallback_url
204
- response = self.scraper.post(
205
- self.url,
206
- headers=self.headers,
207
- cookies=self.cookies,
208
- json=data,
209
- stream=True,
210
- timeout=self.timeout
211
- )
212
-
213
- if not response.ok:
214
- raise Exception(f"API request failed: {response.status_code}")
215
-
216
- output_str = response.content.decode('utf-8')
217
- sids = re.findall(r'"sid":"(.*?)"', output_str)
218
- second_sid = sids[1] if len(sids) >= 2 else None
219
-
220
- if voice and voice_name and second_sid:
221
- threading.Thread(
222
- target=self.download_audio_threaded,
223
- args=(voice_name, second_sid, output_file)
224
- ).start()
225
-
226
- streaming_text = ""
227
- for line in response.iter_lines(decode_unicode=True):
228
- if line.startswith("data: "):
229
- try:
230
- parsed_data = json.loads(line[6:])
231
- if 'text' in parsed_data:
232
- streaming_text += parsed_data['text']
233
- resp = dict(text=streaming_text)
234
- self.last_response.update(resp)
235
- yield parsed_data if raw else resp
236
- except json.JSONDecodeError:
237
- continue
238
-
239
- self.conversation.update_chat_history(
240
- prompt, self.get_message(self.last_response)
241
- )
242
-
243
- if stream:
244
- return process_stream()
245
- else:
246
- # For non-stream, collect all responses and return the final one
247
- for res in process_stream():
248
- pass
249
- return self.last_response
250
-
251
- def chat(
252
- self,
253
- prompt: str,
254
- stream: bool = False,
255
- optimizer: str = None,
256
- conversationally: bool = False,
257
- voice: bool = None,
258
- voice_name: str = None,
259
- output_file: str = None
260
- ) -> str:
261
- """
262
- Generates a response based on the provided prompt.
263
-
264
- Args:
265
- prompt (str): The prompt to send
266
- stream (bool): Whether to stream the response
267
- optimizer (str): Prompt optimizer to use
268
- conversationally (bool): Use conversation context
269
- voice (bool): Override default voice setting
270
- voice_name (str): Override default voice name
271
- output_file (str): Override default output file path
272
- """
273
- # Use instance defaults if not specified
274
- voice = self.voice_enabled if voice is None else voice
275
- voice_name = self.voice_name if voice_name is None else voice_name
276
- output_file = self.output_file if output_file is None else output_file
277
-
278
- if voice and voice_name and voice_name not in self.AVAILABLE_VOICES:
279
- raise ValueError(f"Voice '{voice_name}' not available. Choose from: {list(self.AVAILABLE_VOICES.keys())}")
280
-
281
- if stream:
282
- def stream_generator():
283
- for response in self.ask(
284
- prompt,
285
- stream=True,
286
- optimizer=optimizer,
287
- conversationally=conversationally,
288
- voice=voice,
289
- voice_name=voice_name,
290
- output_file=output_file
291
- ):
292
- yield self.get_message(response).encode('utf-8').decode('utf-8')
293
- return stream_generator()
294
- else:
295
- response = self.ask(
296
- prompt,
297
- stream=False,
298
- optimizer=optimizer,
299
- conversationally=conversationally,
300
- voice=voice,
301
- voice_name=voice_name,
302
- output_file=output_file
303
- )
304
- return self.get_message(response)
305
-
306
- def get_message(self, response: dict) -> str:
307
- """Retrieves message only from response"""
308
- assert isinstance(response, dict), "Response should be of dict data-type only"
309
- return response["text"]
310
-
311
- def download_audio_threaded(self, voice_name: str, second_sid: str, output_file: str) -> None:
312
- """Downloads audio in a separate thread."""
313
- params = {
314
- 'mode': 'eager',
315
- 'voice': f'voice{self.AVAILABLE_VOICES[voice_name]}',
316
- 'messageSid': second_sid,
317
- }
318
-
319
- try:
320
- audio_response = self.scraper.get(
321
- 'https://pi.ai/api/chat/voice',
322
- params=params,
323
- cookies=self.cookies,
324
- headers=self.headers,
325
- timeout=self.timeout
326
- )
327
-
328
- if not audio_response.ok:
329
- return
330
-
331
- audio_response.raise_for_status()
332
-
333
- with open(output_file, "wb") as file:
334
- file.write(audio_response.content)
335
-
336
- except requests.exceptions.RequestException:
337
- pass
338
-
339
- if __name__ == '__main__':
340
- from rich import print
341
- ai = PiAI()
342
- response = ai.chat(input(">>> "), stream=True)
343
- for chunk in response:
344
- print(chunk, end="", flush=True)
1
+ import re
2
+ import threading
3
+ from typing import Any, Dict, Generator, Optional, Union, cast
4
+ from uuid import uuid4
5
+
6
+ from curl_cffi import CurlError
7
+ from curl_cffi.requests import Session
8
+
9
+ from webscout import exceptions
10
+ from webscout.AIbase import Provider, Response
11
+ from webscout.AIutel import ( # Import sanitize_stream
12
+ AwesomePrompts,
13
+ Conversation,
14
+ Optimizers,
15
+ sanitize_stream,
16
+ )
17
+ from webscout.litagent import LitAgent
18
+
19
+
20
+ class PiAI(Provider):
21
+ """
22
+ PiAI is a provider class for interacting with the Pi.ai chat API.
23
+
24
+ Attributes:
25
+ knowledge_cutoff (str): The knowledge cutoff date for the model
26
+ AVAILABLE_VOICES (Dict[str, int]): Available voice options for audio responses
27
+ AVAILABLE_MODELS (List[str]): Available model options for the API
28
+ """
29
+
30
+ required_auth = False
31
+ AVAILABLE_MODELS = ["inflection_3_pi"]
32
+ AVAILABLE_VOICES: Dict[str, int] = {
33
+ "voice1": 1,
34
+ "voice2": 2,
35
+ "voice3": 3,
36
+ "voice4": 4,
37
+ "voice5": 5,
38
+ "voice6": 6,
39
+ "voice7": 7,
40
+ "voice8": 8,
41
+ }
42
+
43
+ def __init__(
44
+ self,
45
+ is_conversation: bool = True,
46
+ max_tokens: int = 2048, # Note: max_tokens is not used by this API
47
+ timeout: int = 30,
48
+ intro: Optional[str] = None,
49
+ filepath: Optional[str] = None,
50
+ update_file: bool = True,
51
+ proxies: dict = {},
52
+ history_offset: int = 10250,
53
+ act: Optional[str] = None,
54
+ voice: bool = False,
55
+ voice_name: str = "voice3",
56
+ output_file: str = "PiAI.mp3",
57
+ model: str = "inflection_3_pi", # Note: model is not used by this API
58
+ ):
59
+ """
60
+ Initializes PiAI with voice support.
61
+
62
+ Args:
63
+ voice (bool): Enable/disable voice output
64
+ voice_name (str): Name of the voice to use (if None, uses default)
65
+ output_file (str): Path to save voice output (default: PiAI.mp3)
66
+ """
67
+ # Voice settings
68
+ self.voice_enabled = voice
69
+ self.voice_name = voice_name
70
+ self.output_file = output_file
71
+
72
+ if voice and voice_name and voice_name not in self.AVAILABLE_VOICES:
73
+ raise ValueError(
74
+ f"Voice '{voice_name}' not available. Choose from: {list(self.AVAILABLE_VOICES.keys())}"
75
+ )
76
+
77
+ # Initialize curl_cffi Session instead of cloudscraper/requests
78
+ self.session = Session()
79
+ self.primary_url = "https://pi.ai/api/chat"
80
+ self.fallback_url = "https://pi.ai/api/v2/chat"
81
+ self.url = self.primary_url
82
+ self.headers = {
83
+ "Accept": "text/event-stream",
84
+ "Accept-Encoding": "gzip, deflate, br, zstd",
85
+ "Accept-Language": "en-US,en;q=0.9,en-IN;q=0.8",
86
+ "Content-Type": "application/json",
87
+ "DNT": "1",
88
+ "Origin": "https://pi.ai",
89
+ "Referer": "https://pi.ai/talk",
90
+ "Sec-Fetch-Dest": "empty",
91
+ "Sec-Fetch-Mode": "cors",
92
+ "Sec-Fetch-Site": "same-origin",
93
+ "User-Agent": LitAgent().random(),
94
+ "X-Api-Version": "3",
95
+ }
96
+ self.cookies = {"__cf_bm": uuid4().hex}
97
+
98
+ # Update curl_cffi session headers, proxies, and cookies
99
+ self.session.headers.update(self.headers)
100
+ if proxies:
101
+ self.session.proxies.update(cast(Any, proxies)) # Assign proxies directly
102
+ # Set cookies on the session object for curl_cffi
103
+ for name, value in self.cookies.items():
104
+ self.session.cookies.set(name, value)
105
+
106
+ self.is_conversation = is_conversation
107
+ self.max_tokens_to_sample = max_tokens
108
+ self.timeout = timeout
109
+ self.last_response = {} if self.is_conversation else {"text": ""}
110
+ self.conversation_id = None
111
+
112
+ self.__available_optimizers = (
113
+ method
114
+ for method in dir(Optimizers)
115
+ if callable(getattr(Optimizers, method)) and not method.startswith("__")
116
+ )
117
+
118
+ self.conversation = Conversation(
119
+ is_conversation, self.max_tokens_to_sample, filepath, update_file
120
+ )
121
+ self.conversation.history_offset = history_offset
122
+
123
+ if act:
124
+ self.conversation.intro = (
125
+ AwesomePrompts().get_act(
126
+ cast(Union[str, int], act),
127
+ default=self.conversation.intro,
128
+ case_insensitive=True,
129
+ )
130
+ or self.conversation.intro
131
+ )
132
+ elif intro:
133
+ self.conversation.intro = intro
134
+ if proxies:
135
+ self.session.proxies.update(proxies)
136
+
137
+ if self.is_conversation:
138
+ self.start_conversation()
139
+
140
+ @staticmethod
141
+ def _pi_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
142
+ """Extracts text content from PiAI stream JSON objects."""
143
+ if isinstance(chunk, dict) and "text" in chunk and chunk["text"] is not None:
144
+ return chunk.get("text")
145
+ return None
146
+
147
+ def start_conversation(self) -> str:
148
+ """
149
+ Initializes a new conversation and returns the conversation ID.
150
+ """
151
+ try:
152
+ # Use curl_cffi session post with impersonate
153
+ # Cookies are handled by the session
154
+ response = self.session.post(
155
+ "https://pi.ai/api/chat/start",
156
+ # headers are set on the session
157
+ # cookies=self.cookies, # Handled by session
158
+ json={},
159
+ timeout=self.timeout,
160
+ # proxies are set on the session
161
+ impersonate="chrome110", # Use a common impersonation profile
162
+ )
163
+ response.raise_for_status() # Check for HTTP errors
164
+
165
+ data = response.json()
166
+ # Ensure the expected structure before accessing
167
+ if (
168
+ "conversations" in data
169
+ and data["conversations"]
170
+ and "sid" in data["conversations"][0]
171
+ ):
172
+ self.conversation_id = data["conversations"][0]["sid"]
173
+ return self.conversation_id
174
+ else:
175
+ raise exceptions.FailedToGenerateResponseError(
176
+ f"Unexpected response structure from start API: {data}"
177
+ )
178
+
179
+ except CurlError as e: # Catch CurlError
180
+ raise exceptions.FailedToGenerateResponseError(
181
+ f"Failed to start conversation (CurlError): {e}"
182
+ ) from e
183
+ except Exception as e: # Catch other potential exceptions (like HTTPError, JSONDecodeError)
184
+ # Extract error text from the response if available
185
+ err_text = (
186
+ e.response.text if hasattr(e, "response") and hasattr(e.response, "text") else ""
187
+ )
188
+ raise exceptions.FailedToGenerateResponseError(
189
+ f"Failed to start conversation ({type(e).__name__}): {e} - {err_text}"
190
+ ) from e
191
+
192
+ def ask(
193
+ self,
194
+ prompt: str,
195
+ stream: bool = False,
196
+ raw: bool = False,
197
+ optimizer: Optional[str] = None,
198
+ conversationally: bool = False,
199
+ voice: Optional[bool] = None,
200
+ voice_name: Optional[str] = None,
201
+ output_file: Optional[str] = None,
202
+ **kwargs: Any,
203
+ ) -> Response:
204
+ """
205
+ Interact with Pi.ai by sending a prompt and receiving a response.
206
+ Now supports raw streaming and non-streaming output, matching the pattern in other providers.
207
+
208
+ Args:
209
+ prompt (str): The prompt to send
210
+ stream (bool): Whether to stream the response
211
+ raw (bool): Return raw response format
212
+ optimizer (str): Prompt optimizer to use
213
+ conversationally (bool): Use conversation context
214
+ voice (bool): Override default voice setting
215
+ voice_name (str): Override default voice name
216
+ output_file (str): Override default output file path
217
+ """
218
+ # Voice configuration
219
+ voice = self.voice_enabled if voice is None else voice
220
+ voice_name = self.voice_name if voice_name is None else voice_name
221
+ output_file = self.output_file if output_file is None else output_file
222
+
223
+ if voice and voice_name and voice_name not in self.AVAILABLE_VOICES:
224
+ raise ValueError(
225
+ f"Voice '{voice_name}' not available. Choose from: {list(self.AVAILABLE_VOICES.keys())}"
226
+ )
227
+
228
+ conversation_prompt = self.conversation.gen_complete_prompt(prompt)
229
+ if optimizer:
230
+ if optimizer in self.__available_optimizers:
231
+ conversation_prompt = getattr(Optimizers, optimizer)(
232
+ conversation_prompt if conversationally else prompt
233
+ )
234
+ else:
235
+ raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
236
+
237
+ data = {"text": conversation_prompt, "conversation": self.conversation_id}
238
+
239
+ def process_stream():
240
+ try:
241
+ current_url = self.url
242
+ response = self.session.post(
243
+ current_url,
244
+ json=data,
245
+ stream=True,
246
+ timeout=self.timeout,
247
+ impersonate="chrome110",
248
+ )
249
+ if not response.ok and current_url == self.primary_url:
250
+ current_url = self.fallback_url
251
+ response = self.session.post(
252
+ current_url,
253
+ json=data,
254
+ stream=True,
255
+ timeout=self.timeout,
256
+ impersonate="chrome110",
257
+ )
258
+ response.raise_for_status()
259
+
260
+ sids = []
261
+ streaming_text = ""
262
+ full_raw_data_for_sids = ""
263
+
264
+ processed_stream = sanitize_stream(
265
+ data=response.iter_lines(),
266
+ intro_value="data: ",
267
+ to_json=True,
268
+ content_extractor=self._pi_extractor,
269
+ raw=raw,
270
+ )
271
+ for content in processed_stream:
272
+ if raw:
273
+ yield content
274
+ else:
275
+ if content and isinstance(content, str):
276
+ streaming_text += content
277
+ yield {"text": streaming_text}
278
+ # SID extraction for voice
279
+ for line_bytes in response.iter_lines():
280
+ if line_bytes:
281
+ line = line_bytes.decode("utf-8")
282
+ full_raw_data_for_sids += line + "\n"
283
+ sids = re.findall(r'"sid":"(.*?)"', full_raw_data_for_sids)
284
+ second_sid = sids[1] if len(sids) >= 2 else None
285
+ if voice and voice_name and second_sid:
286
+ threading.Thread(
287
+ target=self.download_audio_threaded,
288
+ args=(voice_name, second_sid, output_file),
289
+ ).start()
290
+ if not raw:
291
+ self.last_response = dict(text=streaming_text)
292
+ self.conversation.update_chat_history(prompt, streaming_text)
293
+ except CurlError as e:
294
+ raise exceptions.FailedToGenerateResponseError(
295
+ f"API request failed (CurlError): {e}"
296
+ ) from e
297
+ except Exception as e:
298
+ err_text = ""
299
+ if hasattr(e, "response"):
300
+ response_obj = getattr(e, "response")
301
+ if hasattr(response_obj, "text"):
302
+ err_text = getattr(response_obj, "text")
303
+ raise exceptions.FailedToGenerateResponseError(
304
+ f"API request failed ({type(e).__name__}): {e} - {err_text}"
305
+ ) from e
306
+
307
+ if stream:
308
+ return process_stream()
309
+ else:
310
+ full_response = ""
311
+ for chunk in process_stream():
312
+ if raw:
313
+ if isinstance(chunk, str):
314
+ full_response += chunk
315
+ else:
316
+ if isinstance(chunk, dict) and "text" in chunk:
317
+ full_response = chunk["text"]
318
+ if not raw:
319
+ self.last_response = {"text": full_response}
320
+ self.conversation.update_chat_history(prompt, full_response)
321
+ return self.last_response
322
+ else:
323
+ return full_response
324
+
325
+ def chat(
326
+ self,
327
+ prompt: str,
328
+ stream: bool = False,
329
+ optimizer: Optional[str] = None,
330
+ conversationally: bool = False,
331
+ **kwargs: Any,
332
+ ) -> Union[str, Generator[str, None, None]]:
333
+ """
334
+ Generates a response based on the provided prompt.
335
+
336
+ Args:
337
+ prompt (str): The prompt to send
338
+ stream (bool): Whether to stream the response
339
+ optimizer (str): Prompt optimizer to use
340
+ conversationally (bool): Use conversation context
341
+ **kwargs: Additional parameters including voice, voice_name, output_file, raw.
342
+ """
343
+ voice = kwargs.get("voice", self.voice_enabled)
344
+ voice_name = kwargs.get("voice_name", self.voice_name)
345
+ output_file = kwargs.get("output_file", self.output_file)
346
+ raw = kwargs.get("raw", False)
347
+
348
+ if voice and voice_name and voice_name not in self.AVAILABLE_VOICES:
349
+ raise ValueError(
350
+ f"Voice '{voice_name}' not available. Choose from: {list(self.AVAILABLE_VOICES.keys())}"
351
+ )
352
+
353
+ if stream:
354
+
355
+ def stream_generator():
356
+ gen = self.ask(
357
+ prompt,
358
+ stream=True,
359
+ raw=raw,
360
+ optimizer=optimizer,
361
+ conversationally=conversationally,
362
+ voice=voice,
363
+ voice_name=voice_name,
364
+ output_file=output_file,
365
+ )
366
+ for response in gen:
367
+ if raw:
368
+ yield cast(str, response)
369
+ else:
370
+ yield self.get_message(cast(Response, response))
371
+
372
+ return stream_generator()
373
+ else:
374
+ response_data = self.ask(
375
+ prompt,
376
+ stream=False,
377
+ raw=raw,
378
+ optimizer=optimizer,
379
+ conversationally=conversationally,
380
+ voice=voice,
381
+ voice_name=voice_name,
382
+ output_file=output_file,
383
+ )
384
+ if raw:
385
+ return cast(str, response_data)
386
+ else:
387
+ return self.get_message(response_data)
388
+
389
+ def get_message(self, response: Response) -> str:
390
+ """Retrieves message only from response"""
391
+ if not isinstance(response, dict):
392
+ return str(response)
393
+ return cast(Dict[str, Any], response).get("text", "")
394
+
395
+ def download_audio_threaded(self, voice_name: str, second_sid: str, output_file: str) -> None:
396
+ """Downloads audio in a separate thread."""
397
+ params = {
398
+ "mode": "eager",
399
+ "voice": f"voice{self.AVAILABLE_VOICES[voice_name]}",
400
+ "messageSid": second_sid,
401
+ }
402
+
403
+ try:
404
+ # Use curl_cffi session get with impersonate
405
+ audio_response = self.session.get(
406
+ "https://pi.ai/api/chat/voice",
407
+ params=params,
408
+ # cookies are handled by the session
409
+ # headers are set on the session
410
+ timeout=self.timeout,
411
+ # proxies are set on the session
412
+ impersonate="chrome110", # Use a common impersonation profile
413
+ )
414
+ audio_response.raise_for_status() # Check for HTTP errors
415
+
416
+ with open(output_file, "wb") as file:
417
+ file.write(audio_response.content)
418
+
419
+ except CurlError: # Catch CurlError
420
+ # Optionally log the error
421
+ pass
422
+ except Exception: # Catch other potential exceptions
423
+ # Optionally log the error
424
+ pass
425
+
426
+
427
+ if __name__ == "__main__":
428
+ # Ensure curl_cffi is installed
429
+ from rich import print
430
+
431
+ try: # Add try-except block for testing
432
+ ai = PiAI(timeout=60)
433
+ print("[bold blue]Testing Chat (Stream):[/bold blue]")
434
+ response = ai.chat("hi", stream=True, raw=False)
435
+ if hasattr(response, "__iter__") and not isinstance(response, (str, bytes)):
436
+ for chunk in response:
437
+ print(chunk, end="", flush=True)
438
+ else:
439
+ print(response)
440
+ except exceptions.FailedToGenerateResponseError as e:
441
+ print(f"\n[bold red]API Error:[/bold red] {e}")
442
+ except Exception as e:
443
+ print(f"\n[bold red]An unexpected error occurred:[/bold red] {e}")