webscout 8.3.6__py3-none-any.whl → 2025.10.11__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.
Potentially problematic release.
This version of webscout might be problematic. Click here for more details.
- webscout/AIauto.py +250 -250
- webscout/AIbase.py +379 -379
- webscout/AIutel.py +60 -58
- webscout/Bard.py +1012 -1012
- webscout/Bing_search.py +417 -417
- webscout/DWEBS.py +529 -529
- webscout/Extra/Act.md +309 -309
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/GitToolkit/gitapi/README.md +110 -110
- webscout/Extra/GitToolkit/gitapi/__init__.py +11 -11
- webscout/Extra/GitToolkit/gitapi/repository.py +195 -195
- webscout/Extra/GitToolkit/gitapi/user.py +96 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +61 -61
- webscout/Extra/YTToolkit/README.md +375 -375
- webscout/Extra/YTToolkit/YTdownloader.py +956 -956
- webscout/Extra/YTToolkit/__init__.py +2 -2
- webscout/Extra/YTToolkit/transcriber.py +475 -475
- webscout/Extra/YTToolkit/ytapi/README.md +44 -44
- webscout/Extra/YTToolkit/ytapi/__init__.py +6 -6
- webscout/Extra/YTToolkit/ytapi/channel.py +307 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +118 -118
- webscout/Extra/YTToolkit/ytapi/https.py +88 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +58 -58
- webscout/Extra/YTToolkit/ytapi/pool.py +7 -7
- webscout/Extra/YTToolkit/ytapi/query.py +39 -39
- webscout/Extra/YTToolkit/ytapi/stream.py +62 -62
- webscout/Extra/YTToolkit/ytapi/utils.py +62 -62
- webscout/Extra/YTToolkit/ytapi/video.py +232 -232
- webscout/Extra/autocoder/__init__.py +9 -9
- webscout/Extra/autocoder/autocoder.py +1105 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +332 -332
- webscout/Extra/gguf.md +429 -429
- webscout/Extra/gguf.py +1213 -1213
- webscout/Extra/tempmail/README.md +487 -487
- webscout/Extra/tempmail/__init__.py +27 -27
- webscout/Extra/tempmail/async_utils.py +140 -140
- webscout/Extra/tempmail/base.py +160 -160
- webscout/Extra/tempmail/cli.py +186 -186
- webscout/Extra/tempmail/emailnator.py +84 -84
- webscout/Extra/tempmail/mail_tm.py +360 -360
- webscout/Extra/tempmail/temp_mail_io.py +291 -291
- webscout/Extra/weather.md +281 -281
- webscout/Extra/weather.py +193 -193
- webscout/Litlogger/README.md +10 -10
- webscout/Litlogger/__init__.py +15 -15
- webscout/Litlogger/formats.py +13 -13
- webscout/Litlogger/handlers.py +121 -121
- webscout/Litlogger/levels.py +13 -13
- webscout/Litlogger/logger.py +134 -134
- webscout/Provider/AISEARCH/Perplexity.py +332 -332
- webscout/Provider/AISEARCH/README.md +279 -279
- webscout/Provider/AISEARCH/__init__.py +33 -11
- webscout/Provider/AISEARCH/felo_search.py +206 -206
- webscout/Provider/AISEARCH/genspark_search.py +323 -323
- webscout/Provider/AISEARCH/hika_search.py +185 -185
- webscout/Provider/AISEARCH/iask_search.py +410 -410
- webscout/Provider/AISEARCH/monica_search.py +219 -219
- webscout/Provider/AISEARCH/scira_search.py +316 -314
- webscout/Provider/AISEARCH/stellar_search.py +177 -177
- webscout/Provider/AISEARCH/webpilotai_search.py +255 -255
- webscout/Provider/Aitopia.py +314 -315
- webscout/Provider/Andi.py +3 -3
- webscout/Provider/Apriel.py +306 -0
- webscout/Provider/ChatGPTClone.py +236 -236
- webscout/Provider/ChatSandbox.py +343 -342
- webscout/Provider/Cloudflare.py +324 -324
- webscout/Provider/Cohere.py +208 -207
- webscout/Provider/Deepinfra.py +370 -369
- webscout/Provider/ExaAI.py +260 -260
- webscout/Provider/ExaChat.py +308 -387
- webscout/Provider/Flowith.py +221 -221
- webscout/Provider/GMI.py +293 -0
- webscout/Provider/Gemini.py +164 -162
- webscout/Provider/GeminiProxy.py +167 -166
- webscout/Provider/GithubChat.py +371 -370
- webscout/Provider/Groq.py +800 -800
- webscout/Provider/HeckAI.py +383 -379
- webscout/Provider/Jadve.py +282 -297
- webscout/Provider/K2Think.py +308 -0
- webscout/Provider/Koboldai.py +206 -384
- webscout/Provider/LambdaChat.py +423 -425
- webscout/Provider/Nemotron.py +244 -245
- webscout/Provider/Netwrck.py +248 -247
- webscout/Provider/OLLAMA.py +395 -394
- webscout/Provider/OPENAI/Cloudflare.py +394 -395
- webscout/Provider/OPENAI/FalconH1.py +452 -457
- webscout/Provider/OPENAI/FreeGemini.py +297 -299
- webscout/Provider/OPENAI/{monochat.py → K2Think.py} +432 -329
- webscout/Provider/OPENAI/NEMOTRON.py +241 -244
- webscout/Provider/OPENAI/PI.py +428 -427
- webscout/Provider/OPENAI/README.md +959 -959
- webscout/Provider/OPENAI/TogetherAI.py +345 -345
- webscout/Provider/OPENAI/TwoAI.py +466 -467
- webscout/Provider/OPENAI/__init__.py +33 -59
- webscout/Provider/OPENAI/ai4chat.py +313 -303
- webscout/Provider/OPENAI/base.py +249 -269
- webscout/Provider/OPENAI/chatglm.py +528 -0
- webscout/Provider/OPENAI/chatgpt.py +593 -588
- webscout/Provider/OPENAI/chatgptclone.py +521 -524
- webscout/Provider/OPENAI/chatsandbox.py +202 -177
- webscout/Provider/OPENAI/deepinfra.py +319 -315
- webscout/Provider/OPENAI/e2b.py +1665 -1665
- webscout/Provider/OPENAI/exaai.py +420 -420
- webscout/Provider/OPENAI/exachat.py +452 -452
- webscout/Provider/OPENAI/friendli.py +232 -232
- webscout/Provider/OPENAI/{refact.py → gmi.py} +324 -274
- webscout/Provider/OPENAI/groq.py +364 -364
- webscout/Provider/OPENAI/heckai.py +314 -311
- webscout/Provider/OPENAI/llmchatco.py +337 -337
- webscout/Provider/OPENAI/netwrck.py +355 -354
- webscout/Provider/OPENAI/oivscode.py +290 -290
- webscout/Provider/OPENAI/opkfc.py +518 -518
- webscout/Provider/OPENAI/pydantic_imports.py +1 -1
- webscout/Provider/OPENAI/scirachat.py +535 -529
- webscout/Provider/OPENAI/sonus.py +308 -308
- webscout/Provider/OPENAI/standardinput.py +442 -442
- webscout/Provider/OPENAI/textpollinations.py +340 -348
- webscout/Provider/OPENAI/toolbaz.py +419 -413
- webscout/Provider/OPENAI/typefully.py +362 -362
- webscout/Provider/OPENAI/utils.py +295 -295
- webscout/Provider/OPENAI/venice.py +436 -436
- webscout/Provider/OPENAI/wisecat.py +387 -387
- webscout/Provider/OPENAI/writecream.py +166 -166
- webscout/Provider/OPENAI/x0gpt.py +378 -378
- webscout/Provider/OPENAI/yep.py +389 -389
- webscout/Provider/OpenGPT.py +230 -230
- webscout/Provider/Openai.py +244 -496
- webscout/Provider/PI.py +405 -404
- webscout/Provider/Perplexitylabs.py +430 -431
- webscout/Provider/QwenLM.py +272 -254
- webscout/Provider/STT/__init__.py +32 -2
- webscout/Provider/{Llama3.py → Sambanova.py} +257 -258
- webscout/Provider/StandardInput.py +309 -309
- webscout/Provider/TTI/README.md +82 -82
- webscout/Provider/TTI/__init__.py +33 -12
- webscout/Provider/TTI/aiarta.py +413 -413
- webscout/Provider/TTI/base.py +136 -136
- webscout/Provider/TTI/bing.py +243 -243
- webscout/Provider/TTI/gpt1image.py +149 -149
- webscout/Provider/TTI/imagen.py +196 -196
- webscout/Provider/TTI/infip.py +211 -211
- webscout/Provider/TTI/magicstudio.py +232 -232
- webscout/Provider/TTI/monochat.py +219 -219
- webscout/Provider/TTI/piclumen.py +214 -214
- webscout/Provider/TTI/pixelmuse.py +232 -232
- webscout/Provider/TTI/pollinations.py +232 -232
- webscout/Provider/TTI/together.py +288 -288
- webscout/Provider/TTI/utils.py +12 -12
- webscout/Provider/TTI/venice.py +367 -367
- webscout/Provider/TTS/README.md +192 -192
- webscout/Provider/TTS/__init__.py +33 -10
- webscout/Provider/TTS/parler.py +110 -110
- webscout/Provider/TTS/streamElements.py +333 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TeachAnything.py +237 -236
- webscout/Provider/TextPollinationsAI.py +311 -318
- webscout/Provider/TogetherAI.py +356 -357
- webscout/Provider/TwoAI.py +313 -569
- webscout/Provider/TypliAI.py +312 -311
- webscout/Provider/UNFINISHED/ChatHub.py +208 -208
- webscout/Provider/UNFINISHED/ChutesAI.py +313 -313
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +294 -294
- webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +198 -198
- webscout/Provider/{Qodo.py → UNFINISHED/Qodo.py} +477 -477
- webscout/Provider/UNFINISHED/VercelAIGateway.py +338 -338
- webscout/Provider/{XenAI.py → UNFINISHED/XenAI.py} +324 -324
- webscout/Provider/UNFINISHED/Youchat.py +330 -330
- webscout/Provider/UNFINISHED/liner.py +334 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +262 -262
- webscout/Provider/UNFINISHED/puterjs.py +634 -634
- webscout/Provider/UNFINISHED/samurai.py +223 -223
- webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
- webscout/Provider/Venice.py +251 -250
- webscout/Provider/VercelAI.py +256 -255
- webscout/Provider/WiseCat.py +232 -231
- webscout/Provider/WrDoChat.py +367 -366
- webscout/Provider/__init__.py +33 -86
- webscout/Provider/ai4chat.py +174 -174
- webscout/Provider/akashgpt.py +331 -334
- webscout/Provider/cerebras.py +446 -340
- webscout/Provider/chatglm.py +394 -214
- webscout/Provider/cleeai.py +211 -212
- webscout/Provider/deepseek_assistant.py +1 -1
- webscout/Provider/elmo.py +282 -282
- webscout/Provider/geminiapi.py +208 -208
- webscout/Provider/granite.py +261 -261
- webscout/Provider/hermes.py +263 -265
- webscout/Provider/julius.py +223 -222
- webscout/Provider/learnfastai.py +309 -309
- webscout/Provider/llama3mitril.py +214 -214
- webscout/Provider/llmchat.py +243 -243
- webscout/Provider/llmchatco.py +290 -290
- webscout/Provider/meta.py +801 -801
- webscout/Provider/oivscode.py +309 -309
- webscout/Provider/scira_chat.py +384 -457
- webscout/Provider/searchchat.py +292 -291
- webscout/Provider/sonus.py +258 -258
- webscout/Provider/toolbaz.py +370 -364
- webscout/Provider/turboseek.py +274 -265
- webscout/Provider/typefully.py +208 -207
- webscout/Provider/x0gpt.py +1 -0
- webscout/Provider/yep.py +372 -371
- webscout/__init__.py +30 -31
- webscout/__main__.py +5 -5
- webscout/auth/api_key_manager.py +189 -189
- webscout/auth/config.py +175 -175
- webscout/auth/models.py +185 -185
- webscout/auth/routes.py +664 -664
- webscout/auth/simple_logger.py +236 -236
- webscout/cli.py +523 -523
- webscout/conversation.py +438 -438
- webscout/exceptions.py +361 -361
- webscout/litagent/Readme.md +298 -298
- webscout/litagent/__init__.py +28 -28
- webscout/litagent/agent.py +581 -581
- webscout/litagent/constants.py +59 -59
- webscout/litprinter/__init__.py +58 -58
- webscout/models.py +181 -181
- webscout/optimizers.py +419 -419
- webscout/prompt_manager.py +288 -288
- webscout/sanitize.py +1078 -1078
- webscout/scout/README.md +401 -401
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +6 -6
- webscout/scout/core/crawler.py +297 -297
- webscout/scout/core/scout.py +706 -706
- webscout/scout/core/search_result.py +95 -95
- webscout/scout/core/text_analyzer.py +62 -62
- webscout/scout/core/text_utils.py +277 -277
- webscout/scout/core/web_analyzer.py +51 -51
- webscout/scout/element.py +599 -599
- webscout/scout/parsers/__init__.py +69 -69
- webscout/scout/parsers/html5lib_parser.py +172 -172
- webscout/scout/parsers/html_parser.py +236 -236
- webscout/scout/parsers/lxml_parser.py +178 -178
- webscout/scout/utils.py +37 -37
- webscout/swiftcli/Readme.md +323 -323
- webscout/swiftcli/__init__.py +95 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +308 -308
- webscout/swiftcli/core/context.py +104 -104
- webscout/swiftcli/core/group.py +241 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +221 -221
- webscout/swiftcli/decorators/options.py +220 -220
- webscout/swiftcli/decorators/output.py +302 -302
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +135 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +59 -59
- webscout/swiftcli/utils/formatting.py +252 -252
- webscout/swiftcli/utils/parsing.py +267 -267
- webscout/update_checker.py +117 -117
- webscout/version.py +1 -1
- webscout/webscout_search.py +1183 -1183
- webscout/webscout_search_async.py +649 -649
- webscout/yep_search.py +346 -346
- webscout/zeroart/README.md +89 -89
- webscout/zeroart/__init__.py +134 -134
- webscout/zeroart/base.py +66 -66
- webscout/zeroart/effects.py +100 -100
- webscout/zeroart/fonts.py +1238 -1238
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/METADATA +937 -936
- webscout-2025.10.11.dist-info/RECORD +300 -0
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -793
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/GptOss.py +0 -207
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/Kimi.py +0 -445
- webscout/Provider/MCPCore.py +0 -322
- webscout/Provider/MiniMax.py +0 -207
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -1045
- webscout/Provider/OPENAI/MiniMax.py +0 -298
- webscout/Provider/OPENAI/Qwen3.py +0 -304
- webscout/Provider/OPENAI/autoproxy.py +0 -1067
- webscout/Provider/OPENAI/copilot.py +0 -321
- webscout/Provider/OPENAI/gptoss.py +0 -288
- webscout/Provider/OPENAI/kimi.py +0 -469
- webscout/Provider/OPENAI/mcpcore.py +0 -431
- webscout/Provider/OPENAI/multichat.py +0 -378
- webscout/Provider/OPENAI/qodo.py +0 -630
- webscout/Provider/OPENAI/xenai.py +0 -514
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/UNFINISHED/fetch_together_models.py +0 -90
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/copilot.py +0 -441
- webscout/Provider/freeaichat.py +0 -294
- webscout/Provider/koala.py +0 -182
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/monochat.py +0 -275
- webscout/Provider/multichat.py +0 -375
- webscout/Provider/scnet.py +0 -244
- webscout/Provider/talkai.py +0 -194
- webscout/tempid.py +0 -128
- webscout-8.3.6.dist-info/RECORD +0 -327
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/WHEEL +0 -0
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/entry_points.txt +0 -0
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/top_level.txt +0 -0
webscout/auth/simple_logger.py
CHANGED
|
@@ -1,236 +1,236 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Simple database logging for no-auth mode.
|
|
3
|
-
Logs API requests directly to Supabase without authentication.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
import uuid
|
|
8
|
-
import asyncio
|
|
9
|
-
from datetime import datetime, timezone
|
|
10
|
-
from typing import Optional, Dict, Any
|
|
11
|
-
import json
|
|
12
|
-
|
|
13
|
-
from webscout.Litlogger import Logger, LogLevel, LogFormat, ConsoleHandler
|
|
14
|
-
import sys
|
|
15
|
-
|
|
16
|
-
# Setup logger
|
|
17
|
-
logger = Logger(
|
|
18
|
-
name="webscout.api.simple_db",
|
|
19
|
-
level=LogLevel.INFO,
|
|
20
|
-
handlers=[ConsoleHandler(stream=sys.stdout)],
|
|
21
|
-
fmt=LogFormat.DEFAULT
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
try:
|
|
25
|
-
from supabase import create_client, Client
|
|
26
|
-
SUPABASE_AVAILABLE = True
|
|
27
|
-
except ImportError:
|
|
28
|
-
logger.warning("Supabase not available. Install with: pip install supabase")
|
|
29
|
-
SUPABASE_AVAILABLE = False
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class SimpleRequestLogger:
|
|
33
|
-
"""Simple request logger for no-auth mode."""
|
|
34
|
-
|
|
35
|
-
def __init__(self):
|
|
36
|
-
self.supabase_client: Optional[Client] = None
|
|
37
|
-
self.initialize_supabase()
|
|
38
|
-
|
|
39
|
-
def initialize_supabase(self):
|
|
40
|
-
"""Initialize Supabase client if credentials are available."""
|
|
41
|
-
if not SUPABASE_AVAILABLE:
|
|
42
|
-
logger.warning("Supabase package not installed. Request logging disabled.")
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
supabase_url = os.getenv("SUPABASE_URL")
|
|
46
|
-
supabase_key = os.getenv("SUPABASE_ANON_KEY")
|
|
47
|
-
|
|
48
|
-
if supabase_url and supabase_key:
|
|
49
|
-
try:
|
|
50
|
-
self.supabase_client = create_client(supabase_url, supabase_key)
|
|
51
|
-
logger.info("Supabase client initialized for request logging")
|
|
52
|
-
|
|
53
|
-
except Exception as e:
|
|
54
|
-
logger.error(f"Failed to initialize Supabase client: {e}")
|
|
55
|
-
self.supabase_client = None
|
|
56
|
-
else:
|
|
57
|
-
logger.info("Supabase credentials not found. Request logging disabled.")
|
|
58
|
-
|
|
59
|
-
async def log_request(
|
|
60
|
-
self,
|
|
61
|
-
request_id: str,
|
|
62
|
-
ip_address: str,
|
|
63
|
-
model: str,
|
|
64
|
-
question: str,
|
|
65
|
-
answer: str,
|
|
66
|
-
provider: Optional[str] = None,
|
|
67
|
-
request_time: Optional[datetime] = None,
|
|
68
|
-
response_time: Optional[datetime] = None,
|
|
69
|
-
processing_time_ms: Optional[float] = None,
|
|
70
|
-
tokens_used: Optional[int] = None,
|
|
71
|
-
error: Optional[str] = None,
|
|
72
|
-
user_agent: Optional[str] = None
|
|
73
|
-
) -> bool:
|
|
74
|
-
"""
|
|
75
|
-
Log API request details to Supabase.
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
request_id: Unique identifier for the request
|
|
79
|
-
ip_address: Client IP address
|
|
80
|
-
model: Model used for the request
|
|
81
|
-
question: User's question/prompt
|
|
82
|
-
answer: AI's response
|
|
83
|
-
provider: Provider used (e.g., ChatGPT, Claude, etc.)
|
|
84
|
-
request_time: When the request was received
|
|
85
|
-
response_time: When the response was sent
|
|
86
|
-
processing_time_ms: Processing time in milliseconds
|
|
87
|
-
tokens_used: Number of tokens consumed
|
|
88
|
-
error: Error message if any
|
|
89
|
-
user_agent: User agent string
|
|
90
|
-
|
|
91
|
-
Returns:
|
|
92
|
-
bool: True if logged successfully, False otherwise
|
|
93
|
-
"""
|
|
94
|
-
if not self.supabase_client:
|
|
95
|
-
# Still log to console for debugging
|
|
96
|
-
logger.info(f"Request {request_id}: {model} - {question[:100]}...")
|
|
97
|
-
return False
|
|
98
|
-
|
|
99
|
-
if not request_time:
|
|
100
|
-
request_time = datetime.now(timezone.utc)
|
|
101
|
-
|
|
102
|
-
if not response_time:
|
|
103
|
-
response_time = datetime.now(timezone.utc)
|
|
104
|
-
|
|
105
|
-
try:
|
|
106
|
-
data = {
|
|
107
|
-
"request_id": request_id,
|
|
108
|
-
"ip_address": ip_address,
|
|
109
|
-
"model": model,
|
|
110
|
-
"provider": provider or "unknown",
|
|
111
|
-
"question": question[:2000] if question else "", # Truncate long questions
|
|
112
|
-
"answer": answer[:5000] if answer else "", # Truncate long answers
|
|
113
|
-
"request_time": request_time.isoformat(),
|
|
114
|
-
"response_time": response_time.isoformat(),
|
|
115
|
-
"processing_time_ms": processing_time_ms,
|
|
116
|
-
"tokens_used": tokens_used,
|
|
117
|
-
"error": error[:1000] if error else None, # Truncate long errors
|
|
118
|
-
"user_agent": user_agent[:500] if user_agent else None,
|
|
119
|
-
"created_at": datetime.now(timezone.utc).isoformat()
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
result = self.supabase_client.table("api_requests").insert(data).execute()
|
|
123
|
-
|
|
124
|
-
if result.data:
|
|
125
|
-
logger.info(f"✅ Request {request_id} logged to database")
|
|
126
|
-
return True
|
|
127
|
-
else:
|
|
128
|
-
logger.error(f"❌ Failed to log request {request_id}: No data returned")
|
|
129
|
-
return False
|
|
130
|
-
|
|
131
|
-
except Exception as e:
|
|
132
|
-
logger.error(f"❌ Failed to log request {request_id}: {e}")
|
|
133
|
-
return False
|
|
134
|
-
|
|
135
|
-
async def get_recent_requests(self, limit: int = 10) -> Dict[str, Any]:
|
|
136
|
-
"""Get recent API requests for monitoring."""
|
|
137
|
-
if not self.supabase_client:
|
|
138
|
-
return {"error": "Database not available", "requests": []}
|
|
139
|
-
|
|
140
|
-
try:
|
|
141
|
-
result = self.supabase_client.table("api_requests")\
|
|
142
|
-
.select("request_id, ip_address, model, provider, created_at, processing_time_ms, error")\
|
|
143
|
-
.order("created_at", desc=True)\
|
|
144
|
-
.limit(limit)\
|
|
145
|
-
.execute()
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
"requests": result.data if result.data else [],
|
|
149
|
-
"count": len(result.data) if result.data else 0
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
except Exception as e:
|
|
153
|
-
logger.error(f"Failed to get recent requests: {e}")
|
|
154
|
-
return {"error": str(e), "requests": []}
|
|
155
|
-
|
|
156
|
-
async def get_stats(self) -> Dict[str, Any]:
|
|
157
|
-
"""Get basic statistics about API usage."""
|
|
158
|
-
if not self.supabase_client:
|
|
159
|
-
return {"error": "Database not available"}
|
|
160
|
-
|
|
161
|
-
try:
|
|
162
|
-
# Get total requests today
|
|
163
|
-
today = datetime.now(timezone.utc).date().isoformat()
|
|
164
|
-
|
|
165
|
-
today_requests = self.supabase_client.table("api_requests")\
|
|
166
|
-
.select("request_id", count="exact")\
|
|
167
|
-
.gte("created_at", f"{today}T00:00:00Z")\
|
|
168
|
-
.execute()
|
|
169
|
-
|
|
170
|
-
# Get requests by model (last 100)
|
|
171
|
-
model_requests = self.supabase_client.table("api_requests")\
|
|
172
|
-
.select("model")\
|
|
173
|
-
.order("created_at", desc=True)\
|
|
174
|
-
.limit(100)\
|
|
175
|
-
.execute()
|
|
176
|
-
|
|
177
|
-
model_counts = {}
|
|
178
|
-
if model_requests.data:
|
|
179
|
-
for req in model_requests.data:
|
|
180
|
-
model = req.get("model", "unknown")
|
|
181
|
-
model_counts[model] = model_counts.get(model, 0) + 1
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
"today_requests": today_requests.count if hasattr(today_requests, 'count') else 0,
|
|
185
|
-
"model_usage": model_counts,
|
|
186
|
-
"available": True
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
except Exception as e:
|
|
190
|
-
logger.error(f"Failed to get stats: {e}")
|
|
191
|
-
return {"error": str(e), "available": False}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
# Global instance
|
|
195
|
-
request_logger = SimpleRequestLogger()
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
async def log_api_request(
|
|
199
|
-
request_id: str,
|
|
200
|
-
ip_address: str,
|
|
201
|
-
model: str,
|
|
202
|
-
question: str,
|
|
203
|
-
answer: str,
|
|
204
|
-
**kwargs
|
|
205
|
-
) -> bool:
|
|
206
|
-
"""Convenience function to log API requests."""
|
|
207
|
-
return await request_logger.log_request(
|
|
208
|
-
request_id=request_id,
|
|
209
|
-
ip_address=ip_address,
|
|
210
|
-
model=model,
|
|
211
|
-
question=question,
|
|
212
|
-
answer=answer,
|
|
213
|
-
**kwargs
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def get_client_ip(request) -> str:
|
|
218
|
-
"""Extract client IP address from request."""
|
|
219
|
-
# Check for X-Forwarded-For header (common with proxies/load balancers)
|
|
220
|
-
forwarded_for = request.headers.get("X-Forwarded-For")
|
|
221
|
-
if forwarded_for:
|
|
222
|
-
# Take the first IP in the chain
|
|
223
|
-
return forwarded_for.split(",")[0].strip()
|
|
224
|
-
|
|
225
|
-
# Check for X-Real-IP header
|
|
226
|
-
real_ip = request.headers.get("X-Real-IP")
|
|
227
|
-
if real_ip:
|
|
228
|
-
return real_ip.strip()
|
|
229
|
-
|
|
230
|
-
# Fall back to direct client IP
|
|
231
|
-
return getattr(request.client, "host", "unknown")
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def generate_request_id() -> str:
|
|
235
|
-
"""Generate a unique request ID."""
|
|
236
|
-
return str(uuid.uuid4())
|
|
1
|
+
"""
|
|
2
|
+
Simple database logging for no-auth mode.
|
|
3
|
+
Logs API requests directly to Supabase without authentication.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import uuid
|
|
8
|
+
import asyncio
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Optional, Dict, Any
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
from webscout.Litlogger import Logger, LogLevel, LogFormat, ConsoleHandler
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
# Setup logger
|
|
17
|
+
logger = Logger(
|
|
18
|
+
name="webscout.api.simple_db",
|
|
19
|
+
level=LogLevel.INFO,
|
|
20
|
+
handlers=[ConsoleHandler(stream=sys.stdout)],
|
|
21
|
+
fmt=LogFormat.DEFAULT
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from supabase import create_client, Client
|
|
26
|
+
SUPABASE_AVAILABLE = True
|
|
27
|
+
except ImportError:
|
|
28
|
+
logger.warning("Supabase not available. Install with: pip install supabase")
|
|
29
|
+
SUPABASE_AVAILABLE = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SimpleRequestLogger:
|
|
33
|
+
"""Simple request logger for no-auth mode."""
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
self.supabase_client: Optional[Client] = None
|
|
37
|
+
self.initialize_supabase()
|
|
38
|
+
|
|
39
|
+
def initialize_supabase(self):
|
|
40
|
+
"""Initialize Supabase client if credentials are available."""
|
|
41
|
+
if not SUPABASE_AVAILABLE:
|
|
42
|
+
logger.warning("Supabase package not installed. Request logging disabled.")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
supabase_url = os.getenv("SUPABASE_URL")
|
|
46
|
+
supabase_key = os.getenv("SUPABASE_ANON_KEY")
|
|
47
|
+
|
|
48
|
+
if supabase_url and supabase_key:
|
|
49
|
+
try:
|
|
50
|
+
self.supabase_client = create_client(supabase_url, supabase_key)
|
|
51
|
+
logger.info("Supabase client initialized for request logging")
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.error(f"Failed to initialize Supabase client: {e}")
|
|
55
|
+
self.supabase_client = None
|
|
56
|
+
else:
|
|
57
|
+
logger.info("Supabase credentials not found. Request logging disabled.")
|
|
58
|
+
|
|
59
|
+
async def log_request(
|
|
60
|
+
self,
|
|
61
|
+
request_id: str,
|
|
62
|
+
ip_address: str,
|
|
63
|
+
model: str,
|
|
64
|
+
question: str,
|
|
65
|
+
answer: str,
|
|
66
|
+
provider: Optional[str] = None,
|
|
67
|
+
request_time: Optional[datetime] = None,
|
|
68
|
+
response_time: Optional[datetime] = None,
|
|
69
|
+
processing_time_ms: Optional[float] = None,
|
|
70
|
+
tokens_used: Optional[int] = None,
|
|
71
|
+
error: Optional[str] = None,
|
|
72
|
+
user_agent: Optional[str] = None
|
|
73
|
+
) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
Log API request details to Supabase.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
request_id: Unique identifier for the request
|
|
79
|
+
ip_address: Client IP address
|
|
80
|
+
model: Model used for the request
|
|
81
|
+
question: User's question/prompt
|
|
82
|
+
answer: AI's response
|
|
83
|
+
provider: Provider used (e.g., ChatGPT, Claude, etc.)
|
|
84
|
+
request_time: When the request was received
|
|
85
|
+
response_time: When the response was sent
|
|
86
|
+
processing_time_ms: Processing time in milliseconds
|
|
87
|
+
tokens_used: Number of tokens consumed
|
|
88
|
+
error: Error message if any
|
|
89
|
+
user_agent: User agent string
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
bool: True if logged successfully, False otherwise
|
|
93
|
+
"""
|
|
94
|
+
if not self.supabase_client:
|
|
95
|
+
# Still log to console for debugging
|
|
96
|
+
logger.info(f"Request {request_id}: {model} - {question[:100]}...")
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
if not request_time:
|
|
100
|
+
request_time = datetime.now(timezone.utc)
|
|
101
|
+
|
|
102
|
+
if not response_time:
|
|
103
|
+
response_time = datetime.now(timezone.utc)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
data = {
|
|
107
|
+
"request_id": request_id,
|
|
108
|
+
"ip_address": ip_address,
|
|
109
|
+
"model": model,
|
|
110
|
+
"provider": provider or "unknown",
|
|
111
|
+
"question": question[:2000] if question else "", # Truncate long questions
|
|
112
|
+
"answer": answer[:5000] if answer else "", # Truncate long answers
|
|
113
|
+
"request_time": request_time.isoformat(),
|
|
114
|
+
"response_time": response_time.isoformat(),
|
|
115
|
+
"processing_time_ms": processing_time_ms,
|
|
116
|
+
"tokens_used": tokens_used,
|
|
117
|
+
"error": error[:1000] if error else None, # Truncate long errors
|
|
118
|
+
"user_agent": user_agent[:500] if user_agent else None,
|
|
119
|
+
"created_at": datetime.now(timezone.utc).isoformat()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
result = self.supabase_client.table("api_requests").insert(data).execute()
|
|
123
|
+
|
|
124
|
+
if result.data:
|
|
125
|
+
logger.info(f"✅ Request {request_id} logged to database")
|
|
126
|
+
return True
|
|
127
|
+
else:
|
|
128
|
+
logger.error(f"❌ Failed to log request {request_id}: No data returned")
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error(f"❌ Failed to log request {request_id}: {e}")
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
async def get_recent_requests(self, limit: int = 10) -> Dict[str, Any]:
|
|
136
|
+
"""Get recent API requests for monitoring."""
|
|
137
|
+
if not self.supabase_client:
|
|
138
|
+
return {"error": "Database not available", "requests": []}
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
result = self.supabase_client.table("api_requests")\
|
|
142
|
+
.select("request_id, ip_address, model, provider, created_at, processing_time_ms, error")\
|
|
143
|
+
.order("created_at", desc=True)\
|
|
144
|
+
.limit(limit)\
|
|
145
|
+
.execute()
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
"requests": result.data if result.data else [],
|
|
149
|
+
"count": len(result.data) if result.data else 0
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.error(f"Failed to get recent requests: {e}")
|
|
154
|
+
return {"error": str(e), "requests": []}
|
|
155
|
+
|
|
156
|
+
async def get_stats(self) -> Dict[str, Any]:
|
|
157
|
+
"""Get basic statistics about API usage."""
|
|
158
|
+
if not self.supabase_client:
|
|
159
|
+
return {"error": "Database not available"}
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
# Get total requests today
|
|
163
|
+
today = datetime.now(timezone.utc).date().isoformat()
|
|
164
|
+
|
|
165
|
+
today_requests = self.supabase_client.table("api_requests")\
|
|
166
|
+
.select("request_id", count="exact")\
|
|
167
|
+
.gte("created_at", f"{today}T00:00:00Z")\
|
|
168
|
+
.execute()
|
|
169
|
+
|
|
170
|
+
# Get requests by model (last 100)
|
|
171
|
+
model_requests = self.supabase_client.table("api_requests")\
|
|
172
|
+
.select("model")\
|
|
173
|
+
.order("created_at", desc=True)\
|
|
174
|
+
.limit(100)\
|
|
175
|
+
.execute()
|
|
176
|
+
|
|
177
|
+
model_counts = {}
|
|
178
|
+
if model_requests.data:
|
|
179
|
+
for req in model_requests.data:
|
|
180
|
+
model = req.get("model", "unknown")
|
|
181
|
+
model_counts[model] = model_counts.get(model, 0) + 1
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
"today_requests": today_requests.count if hasattr(today_requests, 'count') else 0,
|
|
185
|
+
"model_usage": model_counts,
|
|
186
|
+
"available": True
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.error(f"Failed to get stats: {e}")
|
|
191
|
+
return {"error": str(e), "available": False}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Global instance
|
|
195
|
+
request_logger = SimpleRequestLogger()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def log_api_request(
|
|
199
|
+
request_id: str,
|
|
200
|
+
ip_address: str,
|
|
201
|
+
model: str,
|
|
202
|
+
question: str,
|
|
203
|
+
answer: str,
|
|
204
|
+
**kwargs
|
|
205
|
+
) -> bool:
|
|
206
|
+
"""Convenience function to log API requests."""
|
|
207
|
+
return await request_logger.log_request(
|
|
208
|
+
request_id=request_id,
|
|
209
|
+
ip_address=ip_address,
|
|
210
|
+
model=model,
|
|
211
|
+
question=question,
|
|
212
|
+
answer=answer,
|
|
213
|
+
**kwargs
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def get_client_ip(request) -> str:
|
|
218
|
+
"""Extract client IP address from request."""
|
|
219
|
+
# Check for X-Forwarded-For header (common with proxies/load balancers)
|
|
220
|
+
forwarded_for = request.headers.get("X-Forwarded-For")
|
|
221
|
+
if forwarded_for:
|
|
222
|
+
# Take the first IP in the chain
|
|
223
|
+
return forwarded_for.split(",")[0].strip()
|
|
224
|
+
|
|
225
|
+
# Check for X-Real-IP header
|
|
226
|
+
real_ip = request.headers.get("X-Real-IP")
|
|
227
|
+
if real_ip:
|
|
228
|
+
return real_ip.strip()
|
|
229
|
+
|
|
230
|
+
# Fall back to direct client IP
|
|
231
|
+
return getattr(request.client, "host", "unknown")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def generate_request_id() -> str:
|
|
235
|
+
"""Generate a unique request ID."""
|
|
236
|
+
return str(uuid.uuid4())
|