caption-flow 0.1.0__py3-none-any.whl → 0.2.0__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.
caption_flow/__init__.py CHANGED
@@ -3,7 +3,8 @@
3
3
  __version__ = "0.1.0"
4
4
 
5
5
  from .orchestrator import Orchestrator
6
- from .worker import Worker
6
+ from .workers.data import DataWorker
7
+ from .workers.caption import CaptionWorker
7
8
  from .monitor import Monitor
8
9
 
9
- __all__ = ["Orchestrator", "Worker", "Monitor"]
10
+ __all__ = ["Orchestrator", "DataWorker", "CaptionWorker", "Monitor"]
caption_flow/cli.py CHANGED
@@ -15,7 +15,6 @@ from rich.logging import RichHandler
15
15
  from datetime import datetime
16
16
 
17
17
  from .orchestrator import Orchestrator
18
- from .worker import Worker
19
18
  from .monitor import Monitor
20
19
  from .utils.certificates import CertificateManager
21
20
 
@@ -168,8 +167,6 @@ def orchestrator(ctx, config: Optional[str], **kwargs):
168
167
  else:
169
168
  config_data = base_config
170
169
 
171
- console.print(f"Config contents: {config_data}")
172
-
173
170
  # Apply CLI overrides
174
171
  if kwargs.get("port"):
175
172
  config_data["port"] = kwargs["port"]
@@ -242,11 +239,11 @@ def worker(ctx, config: Optional[str], **kwargs):
242
239
 
243
240
  # Choose worker type
244
241
  if kwargs.get("vllm") or config_data.get("vllm"):
245
- from .worker_vllm import VLLMWorker
242
+ from .workers.caption import CaptionWorker
246
243
 
247
- worker_instance = VLLMWorker(config_data)
244
+ worker_instance = CaptionWorker(config_data)
248
245
  else:
249
- worker_instance = Worker(config_data)
246
+ raise ValueError(f"Not sure how to handle worker for {config_data.get('type')} type setup.")
250
247
 
251
248
  try:
252
249
  asyncio.run(worker_instance.start())
@@ -262,84 +259,92 @@ def worker(ctx, config: Optional[str], **kwargs):
262
259
  @click.option("--no-verify-ssl", is_flag=True, help="Skip SSL verification")
263
260
  @click.option("--debug", is_flag=True, help="Enable debug output")
264
261
  @click.pass_context
265
- def monitor(ctx, config: Optional[str], server: Optional[str], token: Optional[str],
266
- no_verify_ssl: bool, debug: bool):
262
+ def monitor(
263
+ ctx,
264
+ config: Optional[str],
265
+ server: Optional[str],
266
+ token: Optional[str],
267
+ no_verify_ssl: bool,
268
+ debug: bool,
269
+ ):
267
270
  """Start the monitoring TUI."""
268
-
271
+
269
272
  # Enable debug logging if requested
270
273
  if debug:
271
274
  setup_logging(verbose=True)
272
275
  console.print("[yellow]Debug mode enabled[/yellow]")
273
-
276
+
274
277
  # Load configuration
275
- base_config = ConfigManager.find_config('monitor', config)
276
-
278
+ base_config = ConfigManager.find_config("monitor", config)
279
+
277
280
  if not base_config:
278
281
  # Try to find monitor config in orchestrator config as fallback
279
- orch_config = ConfigManager.find_config('orchestrator')
280
- if orch_config and 'monitor' in orch_config:
281
- base_config = {'monitor': orch_config['monitor']}
282
+ orch_config = ConfigManager.find_config("orchestrator")
283
+ if orch_config and "monitor" in orch_config:
284
+ base_config = {"monitor": orch_config["monitor"]}
282
285
  console.print("[dim]Using monitor config from orchestrator.yaml[/dim]")
283
286
  else:
284
287
  base_config = {}
285
288
  if not server or not token:
286
289
  console.print("[yellow]No monitor config found, using CLI args[/yellow]")
287
-
290
+
288
291
  # Handle different config structures
289
292
  # Case 1: Config has top-level 'monitor' section
290
- if 'monitor' in base_config:
291
- config_data = base_config['monitor']
293
+ if "monitor" in base_config:
294
+ config_data = base_config["monitor"]
292
295
  # Case 2: Config IS the monitor config (no wrapper)
293
296
  else:
294
297
  config_data = base_config
295
-
298
+
296
299
  # Apply CLI overrides (CLI always wins)
297
300
  if server:
298
- config_data['server'] = server
301
+ config_data["server"] = server
299
302
  if token:
300
- config_data['token'] = token
303
+ config_data["token"] = token
301
304
  if no_verify_ssl:
302
- config_data['verify_ssl'] = False
303
-
305
+ config_data["verify_ssl"] = False
306
+
304
307
  # Debug output
305
308
  if debug:
306
309
  console.print("\n[cyan]Final monitor configuration:[/cyan]")
307
310
  console.print(f" Server: {config_data.get('server', 'NOT SET')}")
308
- console.print(f" Token: {'***' + config_data.get('token', '')[-4:] if config_data.get('token') else 'NOT SET'}")
311
+ console.print(
312
+ f" Token: {'***' + config_data.get('token', '')[-4:] if config_data.get('token') else 'NOT SET'}"
313
+ )
309
314
  console.print(f" Verify SSL: {config_data.get('verify_ssl', True)}")
310
315
  console.print()
311
-
316
+
312
317
  # Validate required fields
313
- if not config_data.get('server'):
318
+ if not config_data.get("server"):
314
319
  console.print("[red]Error: --server required (or set 'server' in monitor.yaml)[/red]")
315
320
  console.print("\n[dim]Example monitor.yaml:[/dim]")
316
321
  console.print("server: wss://localhost:8765")
317
322
  console.print("token: your-token-here")
318
323
  sys.exit(1)
319
-
320
- if not config_data.get('token'):
324
+
325
+ if not config_data.get("token"):
321
326
  console.print("[red]Error: --token required (or set 'token' in monitor.yaml)[/red]")
322
327
  console.print("\n[dim]Example monitor.yaml:[/dim]")
323
328
  console.print("server: wss://localhost:8765")
324
329
  console.print("token: your-token-here")
325
330
  sys.exit(1)
326
-
331
+
327
332
  # Set defaults for optional settings
328
- config_data.setdefault('refresh_interval', 1.0)
329
- config_data.setdefault('show_inactive_workers', False)
330
- config_data.setdefault('max_log_lines', 100)
331
-
333
+ config_data.setdefault("refresh_interval", 1.0)
334
+ config_data.setdefault("show_inactive_workers", False)
335
+ config_data.setdefault("max_log_lines", 100)
336
+
332
337
  # Create and start monitor
333
338
  try:
334
339
  monitor_instance = Monitor(config_data)
335
-
340
+
336
341
  if debug:
337
342
  console.print("[green]Starting monitor...[/green]")
338
343
  console.print(f"[dim]Connecting to: {config_data['server']}[/dim]")
339
344
  sys.exit(1)
340
-
345
+
341
346
  asyncio.run(monitor_instance.start())
342
-
347
+
343
348
  except KeyboardInterrupt:
344
349
  console.print("\n[yellow]Closing monitor...[/yellow]")
345
350
  except ConnectionRefusedError:
@@ -350,9 +355,11 @@ def monitor(ctx, config: Optional[str], server: Optional[str], token: Optional[s
350
355
  console.print(f"\n[red]Error starting monitor: {e}[/red]")
351
356
  if debug:
352
357
  import traceback
358
+
353
359
  traceback.print_exc()
354
360
  sys.exit(1)
355
361
 
362
+
356
363
  @main.command()
357
364
  @click.option("--config", type=click.Path(exists=True), help="Configuration file")
358
365
  @click.option("--server", help="Orchestrator WebSocket URL")
@@ -376,11 +383,21 @@ def reload_config(
376
383
  if not server or not token:
377
384
  base_config = ConfigManager.find_config("orchestrator", config) or {}
378
385
  admin_config = base_config.get("admin", {})
386
+ admin_tokens = base_config.get("orchestrator", {}).get("auth", {}).get("admin_tokens", [])
387
+ has_admin_tokens = False
388
+ if len(admin_tokens) > 0:
389
+ has_admin_tokens = True
390
+ first_admin_token = admin_tokens[0].get("token", None)
391
+ # Do not print sensitive admin token to console.
379
392
 
380
393
  if not server:
381
- server = admin_config.get("server")
394
+ server = admin_config.get("server", "ws://localhost:8765")
382
395
  if not token:
383
- token = admin_config.get("token")
396
+ token = admin_config.get("token", None)
397
+ if token is None and has_admin_tokens:
398
+ # grab the first one, we'll just assume we're localhost.
399
+ console.print("Using first admin token.")
400
+ token = first_admin_token
384
401
 
385
402
  if not server:
386
403
  console.print("[red]Error: --server required (or set in config)[/red]")
@@ -447,7 +464,7 @@ def reload_config(
447
464
  return True
448
465
  else:
449
466
  error = reload_response.get("error", "Unknown error")
450
- console.print(f"[red]Reload failed: {error}[/red]")
467
+ console.print(f"[red]Reload failed: {error} ({reload_response=})[/red]")
451
468
  return False
452
469
 
453
470
  except Exception as e:
caption_flow/models.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """Data models for CaptionFlow."""
2
2
 
3
- from dataclasses import dataclass
3
+ from dataclasses import dataclass, field
4
4
  from datetime import datetime
5
5
  from enum import Enum
6
- from typing import Optional
6
+ from typing import Any, Dict, List, Optional
7
7
 
8
8
 
9
9
  class JobStatus(Enum):
@@ -51,9 +51,10 @@ class Caption:
51
51
  timestamp: datetime
52
52
  caption_count: int = 1 # Number of captions generated for this item
53
53
  caption: Optional[str] = None
54
- captions: Optional[list] = None
54
+ captions: Optional[List[str]] = None
55
+ outputs: Dict[str, List[str]] = field(default_factory=dict)
55
56
  quality_score: Optional[float] = None
56
- quality_scores: Optional[list] = None
57
+ quality_scores: Optional[List[float]] = None
57
58
 
58
59
  # Image metadata
59
60
  image_width: Optional[int] = None
@@ -66,6 +67,7 @@ class Caption:
66
67
  total_captions: Optional[int] = None # Total captions for this image
67
68
  processing_time_ms: Optional[float] = None
68
69
  chunk_id: Optional[str] = None
70
+ metadata: Dict[str, Any] = field(default_factory=dict)
69
71
 
70
72
  def __post_init__(self):
71
73
  if self.caption is None and self.captions is None:
caption_flow/monitor.py CHANGED
@@ -166,6 +166,8 @@ class Monitor:
166
166
  # Filter out rate stats (already shown in rates panel)
167
167
  for key, value in self.stats.items():
168
168
  if key not in ["current_rate", "average_rate", "expected_rate"]:
169
+ if isinstance(value, dict):
170
+ value = json.dumps(value, indent=2)
169
171
  stats_table.add_row(key.replace("_", " ").title(), str(value))
170
172
 
171
173
  layout["stats"].update(Panel(stats_table, title="System Statistics", border_style="green"))
@@ -178,9 +180,18 @@ class Monitor:
178
180
  leaderboard_table.add_column("Trust", style="green")
179
181
 
180
182
  for i, contributor in enumerate(self.leaderboard[:10], 1):
183
+ # Format name with active worker count
184
+ name = contributor.get("name", "Unknown")
185
+ active_workers = contributor.get("active_workers", 0)
186
+
187
+ if active_workers > 0:
188
+ name_display = f"{name} [bright_green](x{active_workers})[/bright_green]"
189
+ else:
190
+ name_display = f"{name} [dim](offline)[/dim]"
191
+
181
192
  leaderboard_table.add_row(
182
193
  str(i),
183
- contributor.get("name", "Unknown"),
194
+ name_display,
184
195
  str(contributor.get("total_captions", 0)),
185
196
  "⭐" * contributor.get("trust_level", 0),
186
197
  )
@@ -188,7 +199,6 @@ class Monitor:
188
199
  layout["leaderboard"].update(
189
200
  Panel(leaderboard_table, title="Top Contributors", border_style="yellow")
190
201
  )
191
-
192
202
  # Activity panel
193
203
  activity_text = Text()
194
204
  for activity in self.recent_activity[-10:]: