weco 0.3.6__py3-none-any.whl → 0.3.8__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.
- weco/api.py +125 -0
- weco/browser.py +29 -0
- weco/cli.py +49 -7
- weco/constants.py +1 -1
- weco/optimizer.py +510 -817
- weco/ui.py +315 -0
- weco/validation.py +112 -0
- {weco-0.3.6.dist-info → weco-0.3.8.dist-info}/METADATA +2 -2
- weco-0.3.8.dist-info/RECORD +18 -0
- {weco-0.3.6.dist-info → weco-0.3.8.dist-info}/WHEEL +1 -1
- weco-0.3.6.dist-info/RECORD +0 -15
- {weco-0.3.6.dist-info → weco-0.3.8.dist-info}/entry_points.txt +0 -0
- {weco-0.3.6.dist-info → weco-0.3.8.dist-info}/licenses/LICENSE +0 -0
- {weco-0.3.6.dist-info → weco-0.3.8.dist-info}/top_level.txt +0 -0
weco/ui.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Optimization loop UI components.
|
|
3
|
+
|
|
4
|
+
This module contains the UI protocol and implementations for displaying
|
|
5
|
+
optimization progress in the CLI.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import List, Optional, Protocol
|
|
11
|
+
|
|
12
|
+
from rich.console import Console, Group
|
|
13
|
+
from rich.live import Live
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from rich.text import Text
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OptimizationUI(Protocol):
|
|
20
|
+
"""Protocol for optimization UI event handlers."""
|
|
21
|
+
|
|
22
|
+
def on_polling(self, step: int) -> None:
|
|
23
|
+
"""Called when polling for execution tasks."""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
def on_task_claimed(self, task_id: str, plan: Optional[str]) -> None:
|
|
27
|
+
"""Called when a task is successfully claimed."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def on_executing(self, step: int) -> None:
|
|
31
|
+
"""Called when starting to execute code."""
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
def on_output(self, output: str, max_preview: int = 200) -> None:
|
|
35
|
+
"""Called with execution output."""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
def on_submitting(self) -> None:
|
|
39
|
+
"""Called when submitting result to backend."""
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
def on_metric(self, step: int, value: float) -> None:
|
|
43
|
+
"""Called when a metric value is received."""
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
def on_complete(self, total_steps: int) -> None:
|
|
47
|
+
"""Called when optimization completes successfully."""
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
def on_stop_requested(self) -> None:
|
|
51
|
+
"""Called when a stop request is received from dashboard."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
def on_interrupted(self) -> None:
|
|
55
|
+
"""Called when interrupted by user (Ctrl+C)."""
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
def on_warning(self, message: str) -> None:
|
|
59
|
+
"""Called for non-fatal warnings."""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
def on_error(self, message: str) -> None:
|
|
63
|
+
"""Called for errors."""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class UIState:
|
|
69
|
+
"""Reactive state for the live optimization UI."""
|
|
70
|
+
|
|
71
|
+
step: int = 0
|
|
72
|
+
total_steps: int = 0
|
|
73
|
+
status: str = "initializing" # polling, executing, submitting, complete, stopped, error
|
|
74
|
+
plan_preview: str = ""
|
|
75
|
+
output_preview: str = ""
|
|
76
|
+
metrics: List[tuple] = field(default_factory=list) # (step, value)
|
|
77
|
+
error: Optional[str] = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class LiveOptimizationUI:
|
|
81
|
+
"""
|
|
82
|
+
Rich Live implementation of OptimizationUI with dynamic single-panel updates.
|
|
83
|
+
|
|
84
|
+
Displays a compact, updating panel showing:
|
|
85
|
+
- Run info (ID, name, dashboard link)
|
|
86
|
+
- Current step and status with visual indicator
|
|
87
|
+
- Plan preview
|
|
88
|
+
- Output preview
|
|
89
|
+
- Metric history as sparkline
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
SPARKLINE_CHARS = "▁▂▃▄▅▆▇█"
|
|
93
|
+
SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
94
|
+
# Statuses that show the spinner animation
|
|
95
|
+
ACTIVE_STATUSES = {"initializing", "polling", "executing", "submitting"}
|
|
96
|
+
STATUS_INDICATORS = {
|
|
97
|
+
"initializing": ("⏳", "dim"),
|
|
98
|
+
"polling": ("🔄", "cyan"),
|
|
99
|
+
"executing": ("⚡", "yellow"),
|
|
100
|
+
"submitting": ("🧠", "blue"),
|
|
101
|
+
"complete": ("✅", "green"),
|
|
102
|
+
"stopped": ("⏹", "yellow"),
|
|
103
|
+
"interrupted": ("⚠", "yellow"),
|
|
104
|
+
"error": ("❌", "red"),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
console: Console,
|
|
110
|
+
run_id: str,
|
|
111
|
+
run_name: str,
|
|
112
|
+
total_steps: int,
|
|
113
|
+
dashboard_url: str,
|
|
114
|
+
model: str = "",
|
|
115
|
+
metric_name: str = "",
|
|
116
|
+
):
|
|
117
|
+
self.console = console
|
|
118
|
+
self.run_id = run_id
|
|
119
|
+
self.run_name = run_name
|
|
120
|
+
self.dashboard_url = dashboard_url
|
|
121
|
+
self.model = model
|
|
122
|
+
self.metric_name = metric_name
|
|
123
|
+
self.state = UIState(total_steps=total_steps)
|
|
124
|
+
self._live: Optional[Live] = None
|
|
125
|
+
|
|
126
|
+
def _sparkline(self, values: List[float], max_width: int) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Create a mini sparkline chart from metric values.
|
|
129
|
+
|
|
130
|
+
Automatically slides to show most recent values when they exceed max_width.
|
|
131
|
+
Shows "···" prefix when older values are hidden.
|
|
132
|
+
"""
|
|
133
|
+
if not values:
|
|
134
|
+
return ""
|
|
135
|
+
|
|
136
|
+
# Reserve space for "···" prefix if we need to truncate
|
|
137
|
+
if len(values) > max_width:
|
|
138
|
+
prefix = "··"
|
|
139
|
+
available = max_width - len(prefix)
|
|
140
|
+
vals = values[-available:] # Take most recent values that fit
|
|
141
|
+
sparkline_prefix = f"[dim]{prefix}[/]"
|
|
142
|
+
else:
|
|
143
|
+
vals = values
|
|
144
|
+
sparkline_prefix = ""
|
|
145
|
+
|
|
146
|
+
min_v, max_v = min(vals), max(vals)
|
|
147
|
+
if max_v == min_v:
|
|
148
|
+
return sparkline_prefix + self.SPARKLINE_CHARS[4] * len(vals)
|
|
149
|
+
|
|
150
|
+
chars = self.SPARKLINE_CHARS
|
|
151
|
+
sparkline = "".join(chars[int((v - min_v) / (max_v - min_v) * 7)] for v in vals)
|
|
152
|
+
return sparkline_prefix + sparkline
|
|
153
|
+
|
|
154
|
+
def _render(self) -> Group:
|
|
155
|
+
"""Render the current UI state as a Rich Panel with top margin."""
|
|
156
|
+
emoji, style = self.STATUS_INDICATORS.get(self.state.status, ("⏳", "dim"))
|
|
157
|
+
|
|
158
|
+
# Build content grid - expands to full terminal width
|
|
159
|
+
grid = Table.grid(padding=(0, 1), expand=True)
|
|
160
|
+
grid.add_column(style="dim", width=10)
|
|
161
|
+
grid.add_column(overflow="ellipsis", no_wrap=True, ratio=1)
|
|
162
|
+
|
|
163
|
+
# Run info (always shown)
|
|
164
|
+
run_display = f"[bold]{self.run_name}[/] [dim]({self.run_id})[/]"
|
|
165
|
+
grid.add_row("Run", run_display)
|
|
166
|
+
grid.add_row("Dashboard", f"[link={self.dashboard_url}]{self.dashboard_url}[/link]")
|
|
167
|
+
if self.model:
|
|
168
|
+
grid.add_row("Model", f"[cyan]{self.model}[/]")
|
|
169
|
+
if self.metric_name:
|
|
170
|
+
grid.add_row("Metric", f"[magenta]{self.metric_name}[/]")
|
|
171
|
+
grid.add_row("", "")
|
|
172
|
+
|
|
173
|
+
# Progress (always shown)
|
|
174
|
+
progress_bar = self._render_progress_bar()
|
|
175
|
+
grid.add_row("Progress", progress_bar)
|
|
176
|
+
|
|
177
|
+
# Status (always shown) - with spinner for active states
|
|
178
|
+
status_text = Text()
|
|
179
|
+
status_text.append(f"{emoji} ", style=style)
|
|
180
|
+
status_text.append(self.state.status.replace("_", " ").title(), style=f"bold {style}")
|
|
181
|
+
if self.state.status in self.ACTIVE_STATUSES:
|
|
182
|
+
# Time-based frame calculation: ~10 fps spinner animation
|
|
183
|
+
frame = int(time.time() * 10) % len(self.SPINNER_FRAMES)
|
|
184
|
+
spinner = self.SPINNER_FRAMES[frame]
|
|
185
|
+
status_text.append(f" {spinner}", style=f"bold {style}")
|
|
186
|
+
grid.add_row("Status", status_text)
|
|
187
|
+
|
|
188
|
+
# Plan (always shown, placeholder when empty)
|
|
189
|
+
if self.state.plan_preview:
|
|
190
|
+
grid.add_row("Plan", f"[dim italic]{self.state.plan_preview}[/]")
|
|
191
|
+
else:
|
|
192
|
+
grid.add_row("Plan", "[dim]—[/]")
|
|
193
|
+
|
|
194
|
+
# Output (always shown, placeholder when empty)
|
|
195
|
+
if self.state.output_preview:
|
|
196
|
+
output_text = self.state.output_preview.replace("\n", " ")
|
|
197
|
+
grid.add_row("Output", f"[dim]{output_text}[/]")
|
|
198
|
+
else:
|
|
199
|
+
grid.add_row("Output", "[dim]—[/]")
|
|
200
|
+
|
|
201
|
+
# Metrics section (always shown, 3 rows: current, best, chart)
|
|
202
|
+
if self.state.metrics:
|
|
203
|
+
values = [m[1] for m in self.state.metrics]
|
|
204
|
+
latest = self.state.metrics[-1][1]
|
|
205
|
+
best = max(values)
|
|
206
|
+
|
|
207
|
+
# Current and best on separate lines
|
|
208
|
+
grid.add_row("Current", f"[bold cyan]{latest:.6g}[/]")
|
|
209
|
+
grid.add_row("Best", f"[bold green]{best:.6g}[/]")
|
|
210
|
+
|
|
211
|
+
# Chart line - calculate available width for sparkline
|
|
212
|
+
# Console width minus: label(10) + padding(4) + panel borders(4) + panel padding(4)
|
|
213
|
+
chart_width = max(self.console.width - 22, 20)
|
|
214
|
+
sparkline = self._sparkline(values, chart_width)
|
|
215
|
+
grid.add_row("History", f"[green]{sparkline}[/]")
|
|
216
|
+
else:
|
|
217
|
+
grid.add_row("Current", "[dim]—[/]")
|
|
218
|
+
grid.add_row("Best", "[dim]—[/]")
|
|
219
|
+
grid.add_row("History", "[dim]—[/]")
|
|
220
|
+
|
|
221
|
+
# Error row (always present, empty when no error)
|
|
222
|
+
if self.state.error:
|
|
223
|
+
grid.add_row("Error", f"[bold red]{self.state.error}[/]")
|
|
224
|
+
else:
|
|
225
|
+
grid.add_row("", "") # Empty row to maintain height
|
|
226
|
+
|
|
227
|
+
panel = Panel(grid, title="[bold blue]⚡ Weco Optimization[/]", border_style="blue", padding=(1, 2), expand=True)
|
|
228
|
+
# Wrap panel with top margin for spacing
|
|
229
|
+
return Group(Text(""), panel)
|
|
230
|
+
|
|
231
|
+
def _render_progress_bar(self) -> Text:
|
|
232
|
+
"""Render a simple ASCII progress bar."""
|
|
233
|
+
total = self.state.total_steps
|
|
234
|
+
current = min(self.state.step, total) # Clamp to total to avoid >100%
|
|
235
|
+
width = 40
|
|
236
|
+
|
|
237
|
+
if total <= 0:
|
|
238
|
+
return Text(f"Step {self.state.step}", style="bold")
|
|
239
|
+
|
|
240
|
+
filled = min(int((current / total) * width), width) # Clamp filled bars
|
|
241
|
+
bar = "█" * filled + "░" * (width - filled)
|
|
242
|
+
pct = min((current / total) * 100, 100) # Clamp percentage
|
|
243
|
+
return Text(f"[{bar}] {current}/{total} ({pct:.0f}%)", style="bold")
|
|
244
|
+
|
|
245
|
+
def __rich__(self) -> Group:
|
|
246
|
+
"""Called by Rich on each refresh cycle - enables auto-animated spinner."""
|
|
247
|
+
return self._render()
|
|
248
|
+
|
|
249
|
+
def _update(self) -> None:
|
|
250
|
+
"""Trigger an immediate live update (for state changes)."""
|
|
251
|
+
if self._live:
|
|
252
|
+
self._live.refresh()
|
|
253
|
+
|
|
254
|
+
# --- Context manager for Live display ---
|
|
255
|
+
def __enter__(self) -> "LiveOptimizationUI":
|
|
256
|
+
# Pass self so Rich calls __rich__() on every auto-refresh (enables spinner animation)
|
|
257
|
+
# Use vertical_overflow="visible" to prevent clipping issues on exit
|
|
258
|
+
self._live = Live(self, console=self.console, refresh_per_second=10, transient=False, vertical_overflow="visible")
|
|
259
|
+
self._live.__enter__()
|
|
260
|
+
return self
|
|
261
|
+
|
|
262
|
+
def __exit__(self, *args) -> None:
|
|
263
|
+
if self._live:
|
|
264
|
+
self._live.__exit__(*args)
|
|
265
|
+
self._live = None
|
|
266
|
+
|
|
267
|
+
# --- OptimizationUI Protocol Implementation ---
|
|
268
|
+
def on_polling(self, step: int) -> None:
|
|
269
|
+
self.state.step = step
|
|
270
|
+
self.state.status = "polling"
|
|
271
|
+
self.state.output_preview = ""
|
|
272
|
+
self._update()
|
|
273
|
+
|
|
274
|
+
def on_task_claimed(self, task_id: str, plan: Optional[str]) -> None:
|
|
275
|
+
self.state.plan_preview = plan or ""
|
|
276
|
+
self._update()
|
|
277
|
+
|
|
278
|
+
def on_executing(self, step: int) -> None:
|
|
279
|
+
self.state.step = step
|
|
280
|
+
self.state.status = "executing"
|
|
281
|
+
self._update()
|
|
282
|
+
|
|
283
|
+
def on_output(self, output: str, max_preview: int = 200) -> None:
|
|
284
|
+
self.state.output_preview = output[:max_preview]
|
|
285
|
+
self._update()
|
|
286
|
+
|
|
287
|
+
def on_submitting(self) -> None:
|
|
288
|
+
self.state.status = "submitting"
|
|
289
|
+
self._update()
|
|
290
|
+
|
|
291
|
+
def on_metric(self, step: int, value: float) -> None:
|
|
292
|
+
self.state.metrics.append((step, value))
|
|
293
|
+
self._update()
|
|
294
|
+
|
|
295
|
+
def on_complete(self, total_steps: int) -> None:
|
|
296
|
+
self.state.step = total_steps
|
|
297
|
+
self.state.status = "complete"
|
|
298
|
+
self._update()
|
|
299
|
+
|
|
300
|
+
def on_stop_requested(self) -> None:
|
|
301
|
+
self.state.status = "stopped"
|
|
302
|
+
self._update()
|
|
303
|
+
|
|
304
|
+
def on_interrupted(self) -> None:
|
|
305
|
+
self.state.status = "interrupted"
|
|
306
|
+
self._update()
|
|
307
|
+
|
|
308
|
+
def on_warning(self, message: str) -> None:
|
|
309
|
+
# Warnings are less critical; we could add a warnings list but keeping it simple
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
def on_error(self, message: str) -> None:
|
|
313
|
+
self.state.error = message
|
|
314
|
+
self.state.status = "error"
|
|
315
|
+
self._update()
|
weco/validation.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Input validation for the Weco CLI.
|
|
2
|
+
|
|
3
|
+
Provides early validation of user inputs with helpful, actionable error messages.
|
|
4
|
+
Validation happens before expensive operations (auth, API calls) to fail fast.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pathlib
|
|
8
|
+
from difflib import get_close_matches
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ValidationError(Exception):
|
|
14
|
+
"""Raised when user input validation fails."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, message: str, suggestion: str | None = None):
|
|
17
|
+
self.message = message
|
|
18
|
+
self.suggestion = suggestion
|
|
19
|
+
super().__init__(message)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_source_file(source: str) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Validate that the source file exists and is readable.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
source: Path to the source file.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
ValidationError: If the file doesn't exist, isn't readable, or isn't a valid text file.
|
|
31
|
+
"""
|
|
32
|
+
path = pathlib.Path(source)
|
|
33
|
+
|
|
34
|
+
if not path.exists():
|
|
35
|
+
suggestion = _find_similar_files(path)
|
|
36
|
+
raise ValidationError(f"Source file '{source}' not found.", suggestion=suggestion)
|
|
37
|
+
|
|
38
|
+
if path.is_dir():
|
|
39
|
+
raise ValidationError(
|
|
40
|
+
f"'{source}' is a directory, not a file.", suggestion="Please specify a file path, e.g., 'src/model.py'"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Try reading the file to catch permission and encoding issues early
|
|
44
|
+
try:
|
|
45
|
+
path.read_text(encoding="utf-8")
|
|
46
|
+
except PermissionError:
|
|
47
|
+
raise ValidationError(f"Cannot read '{source}' — permission denied.")
|
|
48
|
+
except UnicodeDecodeError:
|
|
49
|
+
raise ValidationError(
|
|
50
|
+
f"'{source}' doesn't appear to be a valid text file.",
|
|
51
|
+
suggestion="Weco optimizes source code files (e.g., .py, .cu, .rs)",
|
|
52
|
+
)
|
|
53
|
+
except OSError as e:
|
|
54
|
+
raise ValidationError(f"Cannot read '{source}': {e}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def validate_log_directory(log_dir: str) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Validate that the log directory is writable.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
log_dir: Path to the log directory.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValidationError: If the directory can't be created or isn't writable.
|
|
66
|
+
"""
|
|
67
|
+
path = pathlib.Path(log_dir)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
# Attempt to create the directory (no-op if exists)
|
|
71
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
except PermissionError:
|
|
73
|
+
raise ValidationError(f"Cannot create log directory '{log_dir}' — permission denied.")
|
|
74
|
+
except OSError as e:
|
|
75
|
+
raise ValidationError(f"Cannot create log directory '{log_dir}': {e}")
|
|
76
|
+
|
|
77
|
+
# Check if writable by attempting to create a temp file
|
|
78
|
+
test_file = path / ".weco_write_test"
|
|
79
|
+
try:
|
|
80
|
+
test_file.touch()
|
|
81
|
+
test_file.unlink()
|
|
82
|
+
except PermissionError:
|
|
83
|
+
raise ValidationError(f"Log directory '{log_dir}' is not writable.")
|
|
84
|
+
except OSError:
|
|
85
|
+
pass # Directory exists and is likely fine
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _find_similar_files(path: pathlib.Path) -> str | None:
|
|
89
|
+
"""Find similar filenames in the same directory to suggest as alternatives."""
|
|
90
|
+
parent = path.parent if path.parent.exists() else pathlib.Path(".")
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
# Get files with the same extension, or all files if no extension
|
|
94
|
+
if path.suffix:
|
|
95
|
+
candidates = [f.name for f in parent.iterdir() if f.is_file() and f.suffix == path.suffix]
|
|
96
|
+
else:
|
|
97
|
+
candidates = [f.name for f in parent.iterdir() if f.is_file()]
|
|
98
|
+
|
|
99
|
+
matches = get_close_matches(path.name, candidates, n=3, cutoff=0.4)
|
|
100
|
+
if matches:
|
|
101
|
+
return f"Did you mean: {', '.join(matches)}?"
|
|
102
|
+
except OSError:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def print_validation_error(error: ValidationError, console: Console) -> None:
|
|
109
|
+
"""Print a validation error in a user-friendly format."""
|
|
110
|
+
console.print(f"[bold red]Error:[/] {error.message}")
|
|
111
|
+
if error.suggestion:
|
|
112
|
+
console.print(f"[dim]{error.suggestion}[/]")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weco
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: Documentation for `weco`, a CLI for using Weco AI's code optimizer.
|
|
5
5
|
Author-email: Weco AI Team <contact@weco.ai>
|
|
6
6
|
License:
|
|
@@ -357,7 +357,7 @@ weco run --model gpt-5 --source optimize.py [other options...]
|
|
|
357
357
|
- `claude-opus-4-5`, `claude-opus-4-1`, `claude-opus-4`, `claude-sonnet-4-5`, `claude-sonnet-4`, `claude-haiku-4-5`
|
|
358
358
|
|
|
359
359
|
**Google Gemini:**
|
|
360
|
-
- `gemini-3-pro-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`
|
|
360
|
+
- `gemini-3-pro-preview`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`
|
|
361
361
|
|
|
362
362
|
All models are available through Weco. If no model is specified, Weco automatically selects the best model for your optimization task.
|
|
363
363
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
weco/__init__.py,sha256=ClO0uT6GKOA0iSptvP0xbtdycf0VpoPTq37jHtvlhtw,303
|
|
2
|
+
weco/api.py,sha256=hy0D01x-AJ26DURtKEywQAS7nQgo38A-wAJfPKHGGqM,17395
|
|
3
|
+
weco/auth.py,sha256=O31Hoj-Loi8DWJJG2LfeWgUMuNqAUeGDpd2ZGjA9Ah0,9997
|
|
4
|
+
weco/browser.py,sha256=nsqQtLqbNOe9Zhu9Zogc8rMmBMyuDxuHzKZQL_w10Ps,923
|
|
5
|
+
weco/cli.py,sha256=u5DSt2YGLOha-ldaW6qg3NO2z3rNfYw37FNtaIs-Kz4,12656
|
|
6
|
+
weco/constants.py,sha256=rxL6yrpIzK8zvPTmPqOYl7LUMZ01vUJ9zUqfZD2n-0U,519
|
|
7
|
+
weco/credits.py,sha256=C08x-TRcLg3ccfKqMGNRY7zBn7t3r7LZ119bxgfztaI,7629
|
|
8
|
+
weco/optimizer.py,sha256=XygxTOTPaPx0dDVI9oWaMQHZ0-YAg1JFcMvGPcnEx-A,23990
|
|
9
|
+
weco/panels.py,sha256=POHt0MdRKDykwUJYXcry92O41lpB9gxna55wFI9abWU,16272
|
|
10
|
+
weco/ui.py,sha256=1shfWxeyfhjTzRZsuODuMx5XzeP9SngqpNpN4vIiDCI,11203
|
|
11
|
+
weco/utils.py,sha256=v_rvgw-ktRoXrpPA2copngI8QDCB8UXmbiN-wAiYvEE,9450
|
|
12
|
+
weco/validation.py,sha256=n5aDuF3BFgwVb4eZ9PuU48nogrseXYNI8S3ePqWZCoc,3736
|
|
13
|
+
weco-0.3.8.dist-info/licenses/LICENSE,sha256=9LUfoGHjLPtak2zps2kL2tm65HAZIICx_FbLaRuS4KU,11337
|
|
14
|
+
weco-0.3.8.dist-info/METADATA,sha256=a9A-tvBbsaojDEXmi1DYsoBAJALFQ6kPeIlWlf2Of0Y,29861
|
|
15
|
+
weco-0.3.8.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
16
|
+
weco-0.3.8.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
|
|
17
|
+
weco-0.3.8.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
18
|
+
weco-0.3.8.dist-info/RECORD,,
|
weco-0.3.6.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
weco/__init__.py,sha256=ClO0uT6GKOA0iSptvP0xbtdycf0VpoPTq37jHtvlhtw,303
|
|
2
|
-
weco/api.py,sha256=xVVRk1pj9jpjTphaInkkAhjqhgFP2-6zHT_V-5Du1Fc,13629
|
|
3
|
-
weco/auth.py,sha256=O31Hoj-Loi8DWJJG2LfeWgUMuNqAUeGDpd2ZGjA9Ah0,9997
|
|
4
|
-
weco/cli.py,sha256=Mtkv3rE1rQLdeoVydn30EUi1ki3Cyu45Q3cONnQH4QY,11210
|
|
5
|
-
weco/constants.py,sha256=Wt1VI76smsU0Gsg-HSB9e3V1ZjjsMYXJJg2G9Rb-nQc,519
|
|
6
|
-
weco/credits.py,sha256=C08x-TRcLg3ccfKqMGNRY7zBn7t3r7LZ119bxgfztaI,7629
|
|
7
|
-
weco/optimizer.py,sha256=2qYweESOAer26gjjhu4dg01XmuhtA2nMrJuijaxizzE,45492
|
|
8
|
-
weco/panels.py,sha256=POHt0MdRKDykwUJYXcry92O41lpB9gxna55wFI9abWU,16272
|
|
9
|
-
weco/utils.py,sha256=v_rvgw-ktRoXrpPA2copngI8QDCB8UXmbiN-wAiYvEE,9450
|
|
10
|
-
weco-0.3.6.dist-info/licenses/LICENSE,sha256=9LUfoGHjLPtak2zps2kL2tm65HAZIICx_FbLaRuS4KU,11337
|
|
11
|
-
weco-0.3.6.dist-info/METADATA,sha256=-BKVxw7j_Qe5ksblcmoVF_NVeDDIcWTg-CsUUaadAgs,29835
|
|
12
|
-
weco-0.3.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
weco-0.3.6.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
|
|
14
|
-
weco-0.3.6.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
15
|
-
weco-0.3.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|