webscout 6.2b0__py3-none-any.whl → 6.3__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/AIbase.py +309 -239
- webscout/Agents/functioncall.py +248 -198
- webscout/DWEBS.py +322 -178
- webscout/Extra/gguf.py +250 -60
- webscout/Extra/weather.py +172 -67
- webscout/LLM.py +279 -100
- webscout/Local/formats.py +4 -2
- webscout/Provider/Amigo.py +19 -10
- webscout/Provider/Andi.py +0 -33
- webscout/Provider/Blackboxai.py +4 -204
- webscout/Provider/Llama3.py +1 -1
- webscout/Provider/Marcus.py +137 -0
- webscout/Provider/TTI/__init__.py +2 -1
- webscout/Provider/TTI/talkai.py +116 -0
- webscout/Provider/__init__.py +10 -3
- webscout/Provider/askmyai.py +158 -0
- webscout/Provider/cerebras.py +71 -58
- webscout/Provider/geminiapi.py +208 -198
- webscout/Provider/llama3mitril.py +181 -0
- webscout/Provider/llmchat.py +203 -0
- webscout/Provider/talkai.py +196 -0
- webscout/Provider/twitterclone.py +7 -6
- webscout/cli.py +354 -346
- webscout/version.py +1 -1
- webscout-6.3.dist-info/LICENSE.md +211 -0
- {webscout-6.2b0.dist-info → webscout-6.3.dist-info}/METADATA +11 -13
- {webscout-6.2b0.dist-info → webscout-6.3.dist-info}/RECORD +31 -25
- webscout-6.2b0.dist-info/LICENSE.md +0 -50
- /webscout/Provider/TTI/{AIuncensored.py → AIuncensoredimage.py} +0 -0
- {webscout-6.2b0.dist-info → webscout-6.3.dist-info}/WHEEL +0 -0
- {webscout-6.2b0.dist-info → webscout-6.3.dist-info}/entry_points.txt +0 -0
- {webscout-6.2b0.dist-info → webscout-6.3.dist-info}/top_level.txt +0 -0
webscout/Extra/gguf.py
CHANGED
|
@@ -1,47 +1,221 @@
|
|
|
1
1
|
# webscout/Extra/gguf.py
|
|
2
2
|
import subprocess
|
|
3
3
|
import os
|
|
4
|
+
import sys
|
|
5
|
+
import logging
|
|
6
|
+
import shutil
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Optional, Dict, Any
|
|
4
9
|
from pyfiglet import figlet_format
|
|
5
10
|
from rich.console import Console
|
|
6
|
-
|
|
11
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
|
|
12
|
+
from rich.logging import RichHandler
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
# Set up logging with Rich
|
|
17
|
+
logging.basicConfig(
|
|
18
|
+
level=logging.INFO,
|
|
19
|
+
format="%(message)s",
|
|
20
|
+
datefmt="[%X]",
|
|
21
|
+
handlers=[RichHandler(rich_tracebacks=True)]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
log = logging.getLogger("rich")
|
|
7
25
|
console = Console()
|
|
8
26
|
|
|
9
|
-
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
Args:
|
|
13
|
-
model_id (str): The Hugging Face model ID (e.g., 'google/flan-t5-xl').
|
|
14
|
-
username (str, optional): Your Hugging Face username. Required for uploads.
|
|
15
|
-
token (str, optional): Your Hugging Face API token. Required for uploads.
|
|
16
|
-
quantization_methods (str, optional): Comma-separated quantization methods.
|
|
17
|
-
Defaults to "q4_k_m,q5_k_m".
|
|
18
|
-
|
|
19
|
-
Raises:
|
|
20
|
-
ValueError: If an invalid quantization method is provided.
|
|
21
|
-
"""
|
|
27
|
+
class ConversionError(Exception):
|
|
28
|
+
"""Custom exception for conversion errors"""
|
|
29
|
+
pass
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
+
class ModelConverter:
|
|
32
|
+
"""Handles the conversion of Hugging Face models to GGUF format."""
|
|
33
|
+
|
|
34
|
+
VALID_METHODS = {
|
|
35
|
+
"q2_k": "2-bit quantization",
|
|
36
|
+
"q3_k_l": "3-bit quantization (large)",
|
|
37
|
+
"q3_k_m": "3-bit quantization (medium)",
|
|
38
|
+
"q3_k_s": "3-bit quantization (small)",
|
|
39
|
+
"q4_0": "4-bit quantization (version 0)",
|
|
40
|
+
"q4_1": "4-bit quantization (version 1)",
|
|
41
|
+
"q4_k_m": "4-bit quantization (medium)",
|
|
42
|
+
"q4_k_s": "4-bit quantization (small)",
|
|
43
|
+
"q5_0": "5-bit quantization (version 0)",
|
|
44
|
+
"q5_1": "5-bit quantization (version 1)",
|
|
45
|
+
"q5_k_m": "5-bit quantization (medium)",
|
|
46
|
+
"q5_k_s": "5-bit quantization (small)",
|
|
47
|
+
"q6_k": "6-bit quantization",
|
|
48
|
+
"q8_0": "8-bit quantization"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def __init__(self, model_id: str, username: Optional[str] = None,
|
|
52
|
+
token: Optional[str] = None, quantization_methods: str = "q4_k_m,q5_k_m"):
|
|
53
|
+
self.model_id = model_id
|
|
54
|
+
self.username = username
|
|
55
|
+
self.token = token
|
|
56
|
+
self.quantization_methods = quantization_methods.split(',')
|
|
57
|
+
self.model_name = model_id.split('/')[-1]
|
|
58
|
+
self.workspace = Path(os.getcwd())
|
|
59
|
+
|
|
60
|
+
def validate_inputs(self) -> None:
|
|
61
|
+
"""Validates all input parameters."""
|
|
62
|
+
if not '/' in self.model_id:
|
|
63
|
+
raise ValueError("Invalid model ID format. Expected format: 'organization/model-name'")
|
|
64
|
+
|
|
65
|
+
invalid_methods = [m for m in self.quantization_methods if m not in self.VALID_METHODS]
|
|
66
|
+
if invalid_methods:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"Invalid quantization methods: {', '.join(invalid_methods)}.\n"
|
|
69
|
+
f"Valid methods are: {', '.join(self.VALID_METHODS.keys())}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if bool(self.username) != bool(self.token):
|
|
73
|
+
raise ValueError("Both username and token must be provided for upload, or neither.")
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def check_dependencies() -> Dict[str, bool]:
|
|
77
|
+
"""Check if all required dependencies are installed."""
|
|
78
|
+
dependencies = {
|
|
79
|
+
'git': 'Git version control',
|
|
80
|
+
'pip3': 'Python package installer',
|
|
81
|
+
'huggingface-cli': 'Hugging Face CLI',
|
|
82
|
+
'nvcc': 'NVIDIA CUDA Compiler (optional)'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
status = {}
|
|
86
|
+
for cmd, desc in dependencies.items():
|
|
87
|
+
status[cmd] = subprocess.run(['which', cmd], capture_output=True, text=True).returncode == 0
|
|
88
|
+
|
|
89
|
+
return status
|
|
90
|
+
|
|
91
|
+
def setup_llama_cpp(self) -> None:
|
|
92
|
+
"""Sets up and builds llama.cpp repository."""
|
|
93
|
+
llama_path = self.workspace / "llama.cpp"
|
|
94
|
+
|
|
95
|
+
with console.status("[bold green]Setting up llama.cpp...") as status:
|
|
96
|
+
if not llama_path.exists():
|
|
97
|
+
log.info("Cloning llama.cpp repository...")
|
|
98
|
+
subprocess.run(['git', 'clone', 'https://github.com/ggerganov/llama.cpp'], check=True)
|
|
99
|
+
|
|
100
|
+
os.chdir(llama_path)
|
|
101
|
+
log.info("Installing requirements...")
|
|
102
|
+
subprocess.run(['pip3', 'install', '-r', 'requirements.txt'], check=True)
|
|
103
|
+
|
|
104
|
+
has_cuda = subprocess.run(['nvcc', '--version'], capture_output=True).returncode == 0
|
|
105
|
+
|
|
106
|
+
log.info("Building llama.cpp...")
|
|
107
|
+
subprocess.run(['make', 'clean'], check=True)
|
|
108
|
+
if has_cuda:
|
|
109
|
+
status.update("[bold green]Building with CUDA support...")
|
|
110
|
+
subprocess.run(['make', 'LLAMA_CUBLAS=1'], check=True)
|
|
111
|
+
else:
|
|
112
|
+
status.update("[bold yellow]Building without CUDA support...")
|
|
113
|
+
subprocess.run(['make'], check=True)
|
|
114
|
+
|
|
115
|
+
os.chdir(self.workspace)
|
|
116
|
+
|
|
117
|
+
def display_config(self) -> None:
|
|
118
|
+
"""Displays the current configuration in a formatted table."""
|
|
119
|
+
table = Table(title="Configuration", show_header=True, header_style="bold magenta")
|
|
120
|
+
table.add_column("Setting", style="cyan")
|
|
121
|
+
table.add_column("Value", style="green")
|
|
122
|
+
|
|
123
|
+
table.add_row("Model ID", self.model_id)
|
|
124
|
+
table.add_row("Model Name", self.model_name)
|
|
125
|
+
table.add_row("Username", self.username or "Not provided")
|
|
126
|
+
table.add_row("Token", "****" if self.token else "Not provided")
|
|
127
|
+
table.add_row("Quantization Methods", "\n".join(
|
|
128
|
+
f"{method} ({self.VALID_METHODS[method]})"
|
|
129
|
+
for method in self.quantization_methods
|
|
130
|
+
))
|
|
131
|
+
|
|
132
|
+
console.print(Panel(table))
|
|
31
133
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
134
|
+
def convert(self) -> None:
|
|
135
|
+
"""Performs the model conversion process."""
|
|
136
|
+
try:
|
|
137
|
+
# Display banner and configuration
|
|
138
|
+
console.print(f"[bold green]{figlet_format('GGUF Converter')}[/]\n", justify="center")
|
|
139
|
+
self.display_config()
|
|
140
|
+
|
|
141
|
+
# Validate inputs
|
|
142
|
+
self.validate_inputs()
|
|
143
|
+
|
|
144
|
+
# Check dependencies
|
|
145
|
+
deps = self.check_dependencies()
|
|
146
|
+
missing = [name for name, installed in deps.items() if not installed and name != 'nvcc']
|
|
147
|
+
if missing:
|
|
148
|
+
raise ConversionError(f"Missing required dependencies: {', '.join(missing)}")
|
|
149
|
+
|
|
150
|
+
# Setup llama.cpp
|
|
151
|
+
self.setup_llama_cpp()
|
|
152
|
+
|
|
153
|
+
# Create and execute conversion script
|
|
154
|
+
script_path = self.workspace / "gguf.sh"
|
|
155
|
+
if not script_path.exists():
|
|
156
|
+
self._create_conversion_script(script_path)
|
|
157
|
+
|
|
158
|
+
# Prepare command
|
|
159
|
+
command = ["bash", str(script_path), "-m", self.model_id]
|
|
160
|
+
if self.username and self.token:
|
|
161
|
+
command.extend(["-u", self.username, "-t", self.token])
|
|
162
|
+
command.extend(["-q", ",".join(self.quantization_methods)])
|
|
163
|
+
|
|
164
|
+
# Execute conversion with progress tracking
|
|
165
|
+
with Progress(
|
|
166
|
+
SpinnerColumn(),
|
|
167
|
+
TextColumn("[progress.description]{task.description}"),
|
|
168
|
+
BarColumn(),
|
|
169
|
+
TaskProgressColumn(),
|
|
170
|
+
console=console
|
|
171
|
+
) as progress:
|
|
172
|
+
task = progress.add_task("Converting model...", total=None)
|
|
173
|
+
|
|
174
|
+
process = subprocess.Popen(
|
|
175
|
+
command,
|
|
176
|
+
stdout=subprocess.PIPE,
|
|
177
|
+
stderr=subprocess.PIPE,
|
|
178
|
+
text=True,
|
|
179
|
+
bufsize=1,
|
|
180
|
+
universal_newlines=True
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
while True:
|
|
184
|
+
output = process.stdout.readline()
|
|
185
|
+
if output == '' and process.poll() is not None:
|
|
186
|
+
break
|
|
187
|
+
if output:
|
|
188
|
+
progress.update(task, description=output.strip())
|
|
189
|
+
log.info(output.strip())
|
|
190
|
+
|
|
191
|
+
stderr = process.stderr.read()
|
|
192
|
+
if stderr:
|
|
193
|
+
log.warning(stderr)
|
|
194
|
+
|
|
195
|
+
if process.returncode != 0:
|
|
196
|
+
raise ConversionError(f"Conversion failed with return code {process.returncode}")
|
|
197
|
+
|
|
198
|
+
progress.update(task, completed=True)
|
|
199
|
+
|
|
200
|
+
# Display success message
|
|
201
|
+
console.print(Panel.fit(
|
|
202
|
+
"[bold green]✓[/] Conversion completed successfully!\n\n"
|
|
203
|
+
f"[cyan]Output files can be found in: {self.workspace / self.model_name}[/]",
|
|
204
|
+
title="Success",
|
|
205
|
+
border_style="green"
|
|
206
|
+
))
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
console.print(Panel.fit(
|
|
210
|
+
f"[bold red]✗[/] {str(e)}",
|
|
211
|
+
title="Error",
|
|
212
|
+
border_style="red"
|
|
213
|
+
))
|
|
214
|
+
raise
|
|
37
215
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# Create autollama.sh with the content provided
|
|
42
|
-
with open(script_path, "w") as f:
|
|
43
|
-
f.write("""
|
|
44
|
-
cat << "EOF"
|
|
216
|
+
def _create_conversion_script(self, script_path: Path) -> None:
|
|
217
|
+
"""Creates the conversion shell script."""
|
|
218
|
+
script_content = """cat << "EOF"
|
|
45
219
|
Made with love in India
|
|
46
220
|
EOF
|
|
47
221
|
|
|
@@ -143,7 +317,6 @@ fi
|
|
|
143
317
|
|
|
144
318
|
|
|
145
319
|
|
|
146
|
-
|
|
147
320
|
# Download model
|
|
148
321
|
#todo : shall we put condition to check if model has been already downloaded? similar to autogguf?
|
|
149
322
|
echo "Downloading the model..."
|
|
@@ -195,32 +368,49 @@ else
|
|
|
195
368
|
fi
|
|
196
369
|
|
|
197
370
|
echo "Script completed."
|
|
198
|
-
"""
|
|
199
|
-
|
|
200
|
-
|
|
371
|
+
"""
|
|
372
|
+
script_path.write_text(script_content)
|
|
373
|
+
script_path.chmod(0o755)
|
|
374
|
+
|
|
375
|
+
def convert(
|
|
376
|
+
model_id: str,
|
|
377
|
+
username: Optional[str] = None,
|
|
378
|
+
token: Optional[str] = None,
|
|
379
|
+
quantization_methods: str = "q4_k_m,q5_k_m"
|
|
380
|
+
) -> None:
|
|
381
|
+
"""Converts and quantizes a Hugging Face model to GGUF format.
|
|
201
382
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if token:
|
|
209
|
-
command.extend(["-t", token])
|
|
210
|
-
|
|
211
|
-
if quantization_methods:
|
|
212
|
-
command.extend(["-q", quantization_methods])
|
|
213
|
-
|
|
214
|
-
# Execute the command
|
|
215
|
-
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
383
|
+
Args:
|
|
384
|
+
model_id (str): The Hugging Face model ID (e.g., 'google/flan-t5-xl').
|
|
385
|
+
username (str, optional): Your Hugging Face username. Required for uploads.
|
|
386
|
+
token (str, optional): Your Hugging Face API token. Required for uploads.
|
|
387
|
+
quantization_methods (str, optional): Comma-separated quantization methods.
|
|
388
|
+
Defaults to "q4_k_m,q5_k_m".
|
|
216
389
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
390
|
+
Raises:
|
|
391
|
+
ConversionError: If any step in the conversion process fails.
|
|
392
|
+
ValueError: If invalid parameters are provided.
|
|
393
|
+
"""
|
|
394
|
+
converter = ModelConverter(model_id, username, token, quantization_methods)
|
|
395
|
+
converter.convert()
|
|
220
396
|
|
|
221
|
-
|
|
222
|
-
|
|
397
|
+
if __name__ == "__main__":
|
|
398
|
+
import argparse
|
|
223
399
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
400
|
+
parser = argparse.ArgumentParser(description="Convert Hugging Face models to GGUF format")
|
|
401
|
+
parser.add_argument("model_id", help="The Hugging Face model ID (e.g., 'google/flan-t5-xl')")
|
|
402
|
+
parser.add_argument("-u", "--username", help="Your Hugging Face username")
|
|
403
|
+
parser.add_argument("-t", "--token", help="Your Hugging Face API token")
|
|
404
|
+
parser.add_argument(
|
|
405
|
+
"-q", "--quantization-methods",
|
|
406
|
+
default="q4_k_m,q5_k_m",
|
|
407
|
+
help="Comma-separated quantization methods"
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
args = parser.parse_args()
|
|
411
|
+
convert(
|
|
412
|
+
model_id=args.model_id,
|
|
413
|
+
username=args.username,
|
|
414
|
+
token=args.token,
|
|
415
|
+
quantization_methods=args.quantization_methods
|
|
416
|
+
)
|
webscout/Extra/weather.py
CHANGED
|
@@ -1,67 +1,172 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.layout import Layout
|
|
8
|
+
from rich.align import Align
|
|
9
|
+
from rich import box
|
|
10
|
+
from rich.live import Live
|
|
11
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
12
|
+
from rich.style import Style
|
|
13
|
+
from rich.text import Text
|
|
14
|
+
from rich.columns import Columns
|
|
15
|
+
|
|
16
|
+
# Initialize Rich console with force terminal
|
|
17
|
+
console = Console(force_terminal=True)
|
|
18
|
+
|
|
19
|
+
def get_weather_emoji(condition: str) -> str:
|
|
20
|
+
"""Get appropriate emoji for weather condition"""
|
|
21
|
+
conditions = {
|
|
22
|
+
'sunny': '*', 'clear': '*',
|
|
23
|
+
'partly cloudy': '~', 'cloudy': '=',
|
|
24
|
+
'rain': 'v', 'light rain': '.',
|
|
25
|
+
'heavy rain': 'V', 'thunderstorm': 'V',
|
|
26
|
+
'snow': '*', 'light snow': '*',
|
|
27
|
+
'mist': '-', 'fog': '-',
|
|
28
|
+
'overcast': '='
|
|
29
|
+
}
|
|
30
|
+
condition = condition.lower()
|
|
31
|
+
for key, symbol in conditions.items():
|
|
32
|
+
if key in condition:
|
|
33
|
+
return symbol
|
|
34
|
+
return '~'
|
|
35
|
+
|
|
36
|
+
def get_wind_arrow(degrees: int) -> str:
|
|
37
|
+
"""Convert wind degrees to arrow"""
|
|
38
|
+
arrows = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
|
|
39
|
+
index = round(degrees / 45) % 8
|
|
40
|
+
return arrows[index]
|
|
41
|
+
|
|
42
|
+
def format_temp(temp: str, scale='C') -> Text:
|
|
43
|
+
"""Format temperature with color based on value"""
|
|
44
|
+
try:
|
|
45
|
+
value = float(temp)
|
|
46
|
+
if scale == 'C':
|
|
47
|
+
if value <= 0:
|
|
48
|
+
return Text(f"{temp}°{scale}", style="bold blue")
|
|
49
|
+
elif value >= 30:
|
|
50
|
+
return Text(f"{temp}°{scale}", style="bold red")
|
|
51
|
+
else:
|
|
52
|
+
return Text(f"{temp}°{scale}", style="bold green")
|
|
53
|
+
except ValueError:
|
|
54
|
+
pass
|
|
55
|
+
return Text(f"{temp}°{scale}")
|
|
56
|
+
|
|
57
|
+
def create_current_weather_panel(data):
|
|
58
|
+
"""Create panel for current weather"""
|
|
59
|
+
current = data['current_condition'][0]
|
|
60
|
+
location = data['nearest_area'][0]
|
|
61
|
+
location_name = f"{location['areaName'][0]['value']}, {location['country'][0]['value']}"
|
|
62
|
+
|
|
63
|
+
weather_desc = current['weatherDesc'][0]['value']
|
|
64
|
+
symbol = get_weather_emoji(weather_desc)
|
|
65
|
+
|
|
66
|
+
# Create weather info table
|
|
67
|
+
table = Table(show_header=False, box=box.ROUNDED, expand=True)
|
|
68
|
+
table.add_column("Label", style="cyan")
|
|
69
|
+
table.add_column("Value", justify="right")
|
|
70
|
+
|
|
71
|
+
table.add_row("Location", f"@ {location_name}")
|
|
72
|
+
table.add_row("Condition", f"{symbol} {weather_desc}")
|
|
73
|
+
table.add_row("Temperature",
|
|
74
|
+
f"{format_temp(current['temp_C'])} / {format_temp(current['temp_F'], 'F')}")
|
|
75
|
+
table.add_row("Feels Like",
|
|
76
|
+
f"{format_temp(current['FeelsLikeC'])} / {format_temp(current['FeelsLikeF'], 'F')}")
|
|
77
|
+
table.add_row("Humidity", f"~ {current['humidity']}%")
|
|
78
|
+
|
|
79
|
+
wind_dir = get_wind_arrow(int(current['winddirDegree']))
|
|
80
|
+
table.add_row("Wind",
|
|
81
|
+
f"> {current['windspeedKmph']} km/h {wind_dir} ({current['winddir16Point']})")
|
|
82
|
+
table.add_row("Visibility", f"O {current['visibility']} km")
|
|
83
|
+
table.add_row("Pressure", f"# {current['pressure']} mb")
|
|
84
|
+
|
|
85
|
+
return Panel(table, title="[bold]Current Weather[/]", border_style="blue")
|
|
86
|
+
|
|
87
|
+
def create_forecast_panel(data):
|
|
88
|
+
"""Create panel for weather forecast"""
|
|
89
|
+
table = Table(show_header=True, box=box.ROUNDED, expand=True)
|
|
90
|
+
table.add_column("Date", style="cyan")
|
|
91
|
+
table.add_column("Condition")
|
|
92
|
+
table.add_column("Temp (°C)")
|
|
93
|
+
table.add_column("Rain")
|
|
94
|
+
table.add_column("Wind")
|
|
95
|
+
|
|
96
|
+
for day in data['weather'][:3]:
|
|
97
|
+
date = datetime.strptime(day['date'], '%Y-%m-%d').strftime('%a, %b %d')
|
|
98
|
+
# Get mid-day conditions (noon)
|
|
99
|
+
noon = day['hourly'][4]
|
|
100
|
+
condition = noon['weatherDesc'][0]['value']
|
|
101
|
+
symbol = get_weather_emoji(condition)
|
|
102
|
+
temp_range = f"{day['mintempC']}° - {day['maxtempC']}°"
|
|
103
|
+
rain_chance = f"v {noon['chanceofrain']}%"
|
|
104
|
+
wind = f"> {noon['windspeedKmph']} km/h"
|
|
105
|
+
|
|
106
|
+
table.add_row(
|
|
107
|
+
date,
|
|
108
|
+
f"{symbol} {condition}",
|
|
109
|
+
temp_range,
|
|
110
|
+
rain_chance,
|
|
111
|
+
wind
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return Panel(table, title="[bold]3-Day Forecast[/]", border_style="blue")
|
|
115
|
+
|
|
116
|
+
def get_weather(location: str):
|
|
117
|
+
"""Get weather data with progress indicator"""
|
|
118
|
+
with Progress(
|
|
119
|
+
SpinnerColumn(),
|
|
120
|
+
TextColumn("[progress.description]{task.description}"),
|
|
121
|
+
console=console,
|
|
122
|
+
) as progress:
|
|
123
|
+
progress.add_task(description="Fetching weather data...", total=None)
|
|
124
|
+
try:
|
|
125
|
+
response = requests.get(f"https://wttr.in/{location}?format=j1", timeout=10)
|
|
126
|
+
response.raise_for_status()
|
|
127
|
+
return response.json()
|
|
128
|
+
except Exception as e:
|
|
129
|
+
console.print(f"[red]Error fetching weather data: {str(e)}[/]")
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def display_weather(data):
|
|
133
|
+
"""Display weather information in a beautiful layout"""
|
|
134
|
+
if not data:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
# Create layout
|
|
138
|
+
layout = Layout()
|
|
139
|
+
layout.split_column(
|
|
140
|
+
Layout(name="current", size=15),
|
|
141
|
+
Layout(name="forecast", size=10)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Update layout sections
|
|
145
|
+
layout["current"].update(create_current_weather_panel(data))
|
|
146
|
+
layout["forecast"].update(create_forecast_panel(data))
|
|
147
|
+
|
|
148
|
+
# Print layout with a title
|
|
149
|
+
console.print("\n")
|
|
150
|
+
console.print(Align.center("[bold blue]Weather Report[/]"))
|
|
151
|
+
console.print("\n")
|
|
152
|
+
console.print(layout)
|
|
153
|
+
console.print("\n")
|
|
154
|
+
|
|
155
|
+
def main():
|
|
156
|
+
"""Main function to run the weather app"""
|
|
157
|
+
try:
|
|
158
|
+
console.clear()
|
|
159
|
+
console.print("\n[bold cyan]* Weather Information[/]\n")
|
|
160
|
+
location = console.input("[cyan]Enter location: [/]")
|
|
161
|
+
|
|
162
|
+
weather_data = get_weather(location)
|
|
163
|
+
if weather_data:
|
|
164
|
+
display_weather(weather_data)
|
|
165
|
+
|
|
166
|
+
except KeyboardInterrupt:
|
|
167
|
+
console.print("\n[yellow]Operation cancelled by user[/]")
|
|
168
|
+
except Exception as e:
|
|
169
|
+
console.print(f"\n[bold red]Unexpected error:[/] {str(e)}")
|
|
170
|
+
|
|
171
|
+
if __name__ == "__main__":
|
|
172
|
+
main()
|