ursa-ai 0.9.1__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 (51) hide show
  1. ursa/__init__.py +3 -0
  2. ursa/agents/__init__.py +32 -0
  3. ursa/agents/acquisition_agents.py +812 -0
  4. ursa/agents/arxiv_agent.py +429 -0
  5. ursa/agents/base.py +728 -0
  6. ursa/agents/chat_agent.py +60 -0
  7. ursa/agents/code_review_agent.py +341 -0
  8. ursa/agents/execution_agent.py +915 -0
  9. ursa/agents/hypothesizer_agent.py +614 -0
  10. ursa/agents/lammps_agent.py +465 -0
  11. ursa/agents/mp_agent.py +204 -0
  12. ursa/agents/optimization_agent.py +410 -0
  13. ursa/agents/planning_agent.py +219 -0
  14. ursa/agents/rag_agent.py +304 -0
  15. ursa/agents/recall_agent.py +54 -0
  16. ursa/agents/websearch_agent.py +196 -0
  17. ursa/cli/__init__.py +363 -0
  18. ursa/cli/hitl.py +516 -0
  19. ursa/cli/hitl_api.py +75 -0
  20. ursa/observability/metrics_charts.py +1279 -0
  21. ursa/observability/metrics_io.py +11 -0
  22. ursa/observability/metrics_session.py +750 -0
  23. ursa/observability/pricing.json +97 -0
  24. ursa/observability/pricing.py +321 -0
  25. ursa/observability/timing.py +1466 -0
  26. ursa/prompt_library/__init__.py +0 -0
  27. ursa/prompt_library/code_review_prompts.py +51 -0
  28. ursa/prompt_library/execution_prompts.py +50 -0
  29. ursa/prompt_library/hypothesizer_prompts.py +17 -0
  30. ursa/prompt_library/literature_prompts.py +11 -0
  31. ursa/prompt_library/optimization_prompts.py +131 -0
  32. ursa/prompt_library/planning_prompts.py +79 -0
  33. ursa/prompt_library/websearch_prompts.py +131 -0
  34. ursa/tools/__init__.py +0 -0
  35. ursa/tools/feasibility_checker.py +114 -0
  36. ursa/tools/feasibility_tools.py +1075 -0
  37. ursa/tools/run_command.py +27 -0
  38. ursa/tools/write_code.py +42 -0
  39. ursa/util/__init__.py +0 -0
  40. ursa/util/diff_renderer.py +128 -0
  41. ursa/util/helperFunctions.py +142 -0
  42. ursa/util/logo_generator.py +625 -0
  43. ursa/util/memory_logger.py +183 -0
  44. ursa/util/optimization_schema.py +78 -0
  45. ursa/util/parse.py +405 -0
  46. ursa_ai-0.9.1.dist-info/METADATA +304 -0
  47. ursa_ai-0.9.1.dist-info/RECORD +51 -0
  48. ursa_ai-0.9.1.dist-info/WHEEL +5 -0
  49. ursa_ai-0.9.1.dist-info/entry_points.txt +2 -0
  50. ursa_ai-0.9.1.dist-info/licenses/LICENSE +8 -0
  51. ursa_ai-0.9.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,625 @@
1
+ # ursa/util/logo_generator.py
2
+ from __future__ import annotations
3
+
4
+ import base64
5
+ import random
6
+ import re
7
+ from concurrent.futures import ThreadPoolExecutor
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ from openai import OpenAI # pip install openai
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.text import Text
15
+
16
+ # Reuse a small thread pool so callers can "fire-and-continue" with one line.
17
+ _EXEC = ThreadPoolExecutor(max_workers=2, thread_name_prefix="logo-gen")
18
+
19
+ # Optional: a default console if one isn’t passed in
20
+ _DEFAULT_CONSOLE = Console()
21
+
22
+ # Color for slug label per style (fallback used if missing)
23
+ _STYLE_COLORS = {
24
+ "horror": "red",
25
+ "sci-fi": "cyan",
26
+ "cyberpunk": "magenta",
27
+ "comic": "yellow",
28
+ "fantasy": "green",
29
+ "anime": "deep_sky_blue1",
30
+ "mecha": "steel_blue",
31
+ "steampunk": "dark_goldenrod",
32
+ "ukiyoe": "light_sky_blue1",
33
+ "surreal": "plum1",
34
+ "noir": "grey70",
35
+ "synthwave": "hot_pink",
36
+ "mascot": "bright_magenta",
37
+ "sticker": "bright_magenta",
38
+ "random": "bright_yellow",
39
+ }
40
+
41
+ _WORKSPACE_COLOR = "bright_green" # distinct from slug, border, and prompt
42
+ _BORDER_COLOR = "bright_cyan" # distinct from slug & workspace
43
+
44
+ # ---------------------------
45
+ # Variety-driven prompt pools
46
+ # ---------------------------
47
+
48
+ # Legacy "logo" meta (kept for backwards compatibility)
49
+ META_TEMPLATE = (
50
+ "Design a logo-ready symbol for '{workspace}'. Favor a strong silhouette and clear negative space. "
51
+ "Use {render_mode} and a {composition} composition with a {palette_rule} palette. "
52
+ "Nod subtly to {style} via {style_cues}. "
53
+ "Optional mood only: {problem_text}. {glyphs_rule} {wildcard} {system_hint}"
54
+ )
55
+
56
+ RENDER_MODES = [
57
+ "flat vector shapes",
58
+ "paper cutout",
59
+ "linocut print",
60
+ "stippled ink",
61
+ "ceramic glaze texture",
62
+ "folded origami",
63
+ "wireframe mesh",
64
+ "brushed metal",
65
+ "neon tubing",
66
+ "knitted yarn",
67
+ "mosaic tiles",
68
+ "laser-cut plywood",
69
+ "light painting",
70
+ ]
71
+
72
+ COMPOSITIONS = [
73
+ "strict symmetry",
74
+ "radial burst",
75
+ "off-center tension",
76
+ "stacked vertical",
77
+ "nested negative space",
78
+ "interlocking shapes",
79
+ "spiral growth",
80
+ "tilted diagonal",
81
+ ]
82
+
83
+ PALETTE_RULES = [
84
+ "monochrome",
85
+ "duotone",
86
+ "triadic accent",
87
+ "black/white with a single shock color",
88
+ "muted naturals",
89
+ "warm metallics",
90
+ "cool grayscale with neon accent",
91
+ ]
92
+
93
+ STYLE_CUES = {
94
+ "horror": [
95
+ "elongated shadows",
96
+ "organic asymmetry",
97
+ "eroded edges",
98
+ "subtle unease",
99
+ ],
100
+ "sci-fi": [
101
+ "modular geometry",
102
+ "specular highlights",
103
+ "gridded logic",
104
+ "soft glow",
105
+ ],
106
+ "cyberpunk": [
107
+ "dense layering",
108
+ "wet sheen",
109
+ "electric micro-accents",
110
+ "overlapping signage shapes",
111
+ ],
112
+ "comic": [
113
+ "bold contour",
114
+ "snap motion shapes",
115
+ "halftone texture",
116
+ "exaggerated foreshortening",
117
+ ],
118
+ "fantasy": [
119
+ "ornamental motifs",
120
+ "heroic scale cues",
121
+ "mythic symmetry",
122
+ "carved relief feel",
123
+ ],
124
+ "anime": [
125
+ "silhouette clarity",
126
+ "clean cel edges",
127
+ "dramatic framing",
128
+ "speed lines",
129
+ ],
130
+ "mecha": [
131
+ "panel seams",
132
+ "industrial joints",
133
+ "mechanical symmetry",
134
+ "maintenance patina",
135
+ ],
136
+ "steampunk": [
137
+ "valve/gear hints",
138
+ "brass/oxidized contrast",
139
+ "pressure-gauge arcs",
140
+ "Victorian filigree shapes",
141
+ ],
142
+ "ukiyoe": [
143
+ "flat planes",
144
+ "patterned waves/sky",
145
+ "bold contour rhythm",
146
+ "asymmetric balance",
147
+ ],
148
+ "surreal": [
149
+ "scale paradox",
150
+ "unexpected juxtapositions",
151
+ "floating forms",
152
+ "uncanny calm",
153
+ ],
154
+ "noir": [
155
+ "hard light cuts",
156
+ "oblique lattice angles",
157
+ "rain sheen",
158
+ "deep shadow masses",
159
+ ],
160
+ "synthwave": [
161
+ "retro gradients",
162
+ "sunset discs",
163
+ "hazy horizon",
164
+ "wire grid hint",
165
+ ],
166
+ }
167
+
168
+ WILDCARDS = [
169
+ "Introduce an unexpected but relevant metaphor or material.",
170
+ "Consider how the mark could tessellate into a repeatable pattern.",
171
+ "Hide a secondary icon in negative space.",
172
+ "Let exactly one edge break the expected geometry.",
173
+ "Constrain yourself to three primitive shapes total.",
174
+ "Make the letterform (if any) only visible on second glance.",
175
+ ]
176
+
177
+ SYSTEM_HINTS = [
178
+ "Prefer forms that can be redrawn in ≤12 vector paths.",
179
+ "Design for recognizability at 16×16 and at poster scale.",
180
+ "Bias toward bold silhouette over surface detail.",
181
+ ]
182
+
183
+ # ---------------------------
184
+ # New: Scene-first prompt pools
185
+ # ---------------------------
186
+
187
+ STYLE_OBJECTS = {
188
+ # Concrete, unmistakable set-pieces for overt style signaling
189
+ "steampunk": [
190
+ "polished brass and riveted steel",
191
+ "visible interlocking gears with sharp teeth",
192
+ "pressure gauges with needles and glass reflections",
193
+ "pistons and exposed pipework venting steam",
194
+ "Victorian filigree panels and leather straps",
195
+ ],
196
+ # Add more styles’ objects over time as needed
197
+ }
198
+
199
+ CAMERAS = [
200
+ "low-angle hero shot",
201
+ "wide-angle 24mm",
202
+ "isometric cutaway",
203
+ "bird's-eye parallax",
204
+ ]
205
+
206
+ COMPOSITIONS_SCENE = [
207
+ "rule-of-thirds focal point with leading lines",
208
+ "hero subject centered amid busy environment",
209
+ "crowded workshop tableau",
210
+ "cutaway cross-section of a machine room",
211
+ ]
212
+
213
+ PALETTES_SCENE = [
214
+ "warm brass and leather with cool teal shadows",
215
+ "sepia smoke with scarlet accent lights",
216
+ "noir metals with warm brass pops",
217
+ ]
218
+
219
+ SCENE_WILDCARDS = [
220
+ "Let steam and dust catch light beams for depth.",
221
+ "Add fine wear—scratches, fingerprints, and soot—to metals.",
222
+ "Stage foreground pipes to create natural framing.",
223
+ ]
224
+
225
+ SCENE_TEMPLATE = (
226
+ "Create a full-frame illustration (not a logo) for '{workspace}'. "
227
+ "Use a {composition} composition and {camera} camera angle with cinematic lighting and atmospheric depth. "
228
+ "{style_strength} {style} unmistakable at first glance through: {style_objects}. "
229
+ "Include a coherent background environment; avoid borders or sticker outlines. "
230
+ "Palette: {palette_rule}. Mood (optional): {problem_text}. {wildcard}"
231
+ )
232
+
233
+ # ---------------------------
234
+ # Helpers
235
+ # ---------------------------
236
+
237
+
238
+ def _glyphs_rule(allow_text: bool) -> str:
239
+ if allow_text:
240
+ return "Letterforms are allowed; keep any words minimal and integrated into the symbol."
241
+ # Softer than “No text” → allows abstract glyphs/monograms
242
+ return "Avoid readable words; abstract glyphs/monograms are allowed if they strengthen the mark."
243
+
244
+
245
+ def _choose_style_slug(style: str | None) -> str:
246
+ """
247
+ Resolve a requested style to a known slug; if unknown or generic (e.g., 'sticker'),
248
+ choose a random one from STYLE_CUES.
249
+ """
250
+ if not style:
251
+ return random.choice(list(STYLE_CUES.keys()))
252
+ s = style.strip().lower()
253
+ if s in {"random"}:
254
+ return random.choice(list(STYLE_CUES.keys()))
255
+ return s if s in STYLE_CUES else random.choice(list(STYLE_CUES.keys()))
256
+
257
+
258
+ def _style_strength_phrase(level: str) -> str:
259
+ return {
260
+ "subtle": "Include gentle references to",
261
+ "clear": "Make the influence of",
262
+ "overt": "Make the aesthetic of",
263
+ }.get(level, "Make the aesthetic of")
264
+
265
+
266
+ SUPPORTED_SIZES = {"1024x1024", "1024x1536", "1536x1024", "auto"}
267
+
268
+
269
+ def _size_for(aspect: str, mode: str) -> str:
270
+ """
271
+ Prefer cinematic rectangles for scenes; keep square for logos.
272
+ Returns only API-supported sizes.
273
+ """
274
+ if aspect == "wide":
275
+ return "1536x1024"
276
+ if aspect == "tall":
277
+ return "1024x1536"
278
+ return "1024x1024"
279
+
280
+
281
+ def _normalize_size(size: Optional[str], aspect: str, mode: str) -> str:
282
+ """
283
+ If size is None or invalid, pick a sensible API-supported default based on aspect/mode.
284
+ """
285
+ if not size or size not in SUPPORTED_SIZES:
286
+ return _size_for(aspect, mode)
287
+ return size
288
+
289
+
290
+ # ---------------------------
291
+ # Prompt builders
292
+ # ---------------------------
293
+
294
+
295
+ def _build_logo_prompt(
296
+ *,
297
+ style_slug: str,
298
+ workspace: str,
299
+ gist: str,
300
+ allow_text: bool,
301
+ palette: str | None,
302
+ ) -> str:
303
+ render = random.choice(RENDER_MODES)
304
+ comp = random.choice(COMPOSITIONS)
305
+ palette_rule = palette if palette else random.choice(PALETTE_RULES)
306
+ cues = ", ".join(random.sample(STYLE_CUES[style_slug], k=2))
307
+ wildcard = random.choice(WILDCARDS)
308
+ system_hint = random.choice(SYSTEM_HINTS)
309
+ glyphs_rule = _glyphs_rule(allow_text)
310
+
311
+ meta = META_TEMPLATE.format(
312
+ workspace=workspace,
313
+ render_mode=render,
314
+ composition=comp,
315
+ palette_rule=palette_rule,
316
+ style=style_slug,
317
+ style_cues=cues,
318
+ problem_text=gist,
319
+ glyphs_rule=glyphs_rule,
320
+ wildcard=wildcard,
321
+ system_hint=system_hint,
322
+ )
323
+
324
+ return f"{meta}".strip()
325
+
326
+
327
+ def _build_scene_prompt(
328
+ *,
329
+ style_slug: str,
330
+ workspace: str,
331
+ gist: str,
332
+ palette: Optional[str],
333
+ style_intensity: str = "overt",
334
+ ) -> str:
335
+ comp = random.choice(COMPOSITIONS_SCENE)
336
+ camera = random.choice(CAMERAS)
337
+ palette_rule = palette or random.choice(PALETTES_SCENE)
338
+ style_strength = _style_strength_phrase(style_intensity)
339
+
340
+ # Prefer concrete objects; fall back to generic cues if necessary
341
+ objects_pool = (
342
+ STYLE_OBJECTS.get(style_slug)
343
+ or STYLE_CUES.get(style_slug)
344
+ or ["signature motifs"]
345
+ )
346
+ k = min(4, len(objects_pool))
347
+ style_objects = ", ".join(random.sample(objects_pool, k=k))
348
+ wildcard = random.choice(SCENE_WILDCARDS)
349
+
350
+ return SCENE_TEMPLATE.format(
351
+ workspace=workspace,
352
+ composition=comp,
353
+ camera=camera,
354
+ style_strength=style_strength,
355
+ style=style_slug,
356
+ style_objects=style_objects,
357
+ palette_rule=palette_rule,
358
+ problem_text=gist,
359
+ wildcard=wildcard,
360
+ ).strip()
361
+
362
+
363
+ def _render_prompt_panel(
364
+ *,
365
+ console: Optional[Console],
366
+ style_slug: str,
367
+ workspace: str,
368
+ prompt: str,
369
+ extra_title: str | None = None,
370
+ ):
371
+ c = console or _DEFAULT_CONSOLE
372
+ slug_color = _STYLE_COLORS.get(style_slug, "bright_yellow")
373
+
374
+ title_bits = [
375
+ f"[bold {slug_color}]style: {style_slug}[/bold {slug_color}]",
376
+ f"[dim]•[/dim] [bold {_WORKSPACE_COLOR}]workspace: {workspace}[/bold {_WORKSPACE_COLOR}]",
377
+ ]
378
+ if extra_title:
379
+ title_bits.append(f"[dim]•[/dim] {extra_title}")
380
+
381
+ title = " ".join(title_bits)
382
+
383
+ body = Text()
384
+ body.append(prompt, style="bright_white") # prompt text color
385
+
386
+ panel = Panel.fit(
387
+ body,
388
+ title=title,
389
+ border_style=_BORDER_COLOR,
390
+ padding=(1, 2),
391
+ )
392
+ c.print(panel)
393
+
394
+
395
+ def _craft_logo_prompt(
396
+ problem_text: str,
397
+ workspace: str,
398
+ *,
399
+ style: str = "sticker",
400
+ allow_text: bool = False,
401
+ palette: str | None = None,
402
+ mode: str = "logo", # NEW: "logo" | "scene"
403
+ style_intensity: str = "overt", # NEW: "subtle" | "clear" | "overt"
404
+ ) -> tuple[str, str]:
405
+ """
406
+ Builds either a logo-style prompt (legacy) or a scene-style prompt (new).
407
+ Retains special handling for sticker/mascot.
408
+ Returns (prompt, style_slug)
409
+ """
410
+ gist = " ".join(
411
+ line.strip()
412
+ for line in problem_text.strip().splitlines()
413
+ if line.strip()
414
+ )
415
+
416
+ # Special path: sticker/mascot request (unchanged)
417
+ if style in {"sticker", "mascot"}:
418
+ prompt = (
419
+ f"Create a die-cut sticker with a solid white background, a strong black border surrounding the white "
420
+ f"die-cut border, and no shadow. The sticker image should be a 'mascot' related to the "
421
+ f"topic: `{workspace}`."
422
+ ).strip()
423
+ return prompt, "sticker"
424
+
425
+ # Resolve style and build appropriate prompt
426
+ style_slug = _choose_style_slug(style)
427
+ if mode == "scene":
428
+ prompt = _build_scene_prompt(
429
+ style_slug=style_slug,
430
+ workspace=workspace,
431
+ gist=gist,
432
+ palette=palette,
433
+ style_intensity=style_intensity,
434
+ )
435
+ return prompt, style_slug
436
+
437
+ # Legacy logo path
438
+ prompt = _build_logo_prompt(
439
+ style_slug=style_slug,
440
+ workspace=workspace,
441
+ gist=gist,
442
+ allow_text=allow_text,
443
+ palette=palette,
444
+ )
445
+ return prompt, style_slug
446
+
447
+
448
+ def _slugify(s: str) -> str:
449
+ s = s.lower().strip().replace(" ", "-")
450
+ s = re.sub(r"[^a-z0-9\-]+", "-", s)
451
+ return re.sub(r"-{2,}", "-", s).strip("-")
452
+
453
+
454
+ def _compose_filenames(
455
+ out_dir: Path, style_slug: str, filename: str | None, n: int
456
+ ):
457
+ out_dir = Path(out_dir)
458
+ if filename:
459
+ stem = Path(filename).stem
460
+ suffix = Path(filename).suffix or ".png"
461
+ main = out_dir / f"{stem}{suffix}"
462
+ alts = [out_dir / f"{stem}_{i}{suffix}" for i in range(2, n + 1)]
463
+ else:
464
+ suffix = ".png"
465
+ base = f"{_slugify(style_slug)}_logo"
466
+ main = out_dir / f"{base}{suffix}"
467
+ alts = [out_dir / f"{base}_{i}{suffix}" for i in range(2, n + 1)]
468
+ return main, alts
469
+
470
+
471
+ # ---------------------------
472
+ # Public API
473
+ # ---------------------------
474
+
475
+
476
+ def generate_logo_sync(
477
+ *,
478
+ problem_text: str,
479
+ workspace: str,
480
+ out_dir: str | Path,
481
+ filename: str | None = None,
482
+ model: str = "gpt-image-1",
483
+ size: str | None = None,
484
+ background: str = "opaque",
485
+ quality: str = "high",
486
+ n: int = 1,
487
+ overwrite: bool = False,
488
+ style: str = "sticker",
489
+ allow_text: bool = False,
490
+ palette: str | None = None,
491
+ mode: str = "logo",
492
+ aspect: str = "square",
493
+ style_intensity: str = "overt",
494
+ console: Optional[Console] = None,
495
+ image_model_provider: str = "openai",
496
+ image_provider_kwargs: Optional[dict] = None,
497
+ ) -> Path:
498
+ """
499
+ Generate an image. Default behavior matches previous versions (logo/sticker).
500
+ To create a cinematic illustration, set mode='scene' and consider aspect='wide'.
501
+ """
502
+ out_dir = Path(out_dir)
503
+ out_dir.mkdir(parents=True, exist_ok=True)
504
+
505
+ prompt, style_slug = _craft_logo_prompt(
506
+ problem_text,
507
+ workspace,
508
+ style=style,
509
+ allow_text=allow_text,
510
+ palette=palette,
511
+ mode=mode,
512
+ style_intensity=style_intensity,
513
+ )
514
+
515
+ # Pretty console output
516
+ extra_title = f"[bold magenta]mode: {mode}[/bold magenta] [dim]•[/dim] aspect: {aspect}"
517
+ _render_prompt_panel(
518
+ console=console,
519
+ style_slug=style_slug,
520
+ workspace=workspace,
521
+ prompt=prompt,
522
+ extra_title=extra_title,
523
+ )
524
+
525
+ main_path, alt_paths = _compose_filenames(out_dir, style_slug, filename, n)
526
+ if main_path.exists() and not overwrite:
527
+ return main_path
528
+
529
+ # this is how we'll pass through a vision model and provider/url/endpoint
530
+ client_kwargs = {}
531
+ if image_provider_kwargs:
532
+ # Only pass through safe/known kwargs
533
+ for k in ("api_key", "base_url", "organization"):
534
+ if k in image_provider_kwargs and image_provider_kwargs[k]:
535
+ client_kwargs[k] = image_provider_kwargs[k]
536
+ client = OpenAI(**client_kwargs)
537
+
538
+ final_size = _normalize_size(size, aspect, mode)
539
+ # Scenes tend to look odd with transparent backgrounds; force opaque.
540
+ final_background = "opaque" if mode == "scene" else background
541
+
542
+ kwargs = dict(
543
+ model=model,
544
+ prompt=prompt,
545
+ size=final_size,
546
+ n=n,
547
+ quality=quality,
548
+ background=final_background,
549
+ )
550
+ try:
551
+ resp = client.images.generate(**kwargs)
552
+ except Exception:
553
+ # Some models ignore/forbid background=; retry without it
554
+ kwargs.pop("background", None)
555
+ resp = client.images.generate(**kwargs)
556
+
557
+ main_path.write_bytes(base64.b64decode(resp.data[0].b64_json))
558
+ for i, item in enumerate(resp.data[1:], start=0):
559
+ if i < len(alt_paths):
560
+ alt_paths[i].write_bytes(base64.b64decode(item.b64_json))
561
+ return main_path
562
+
563
+
564
+ def kickoff_logo(
565
+ *,
566
+ problem_text: str,
567
+ workspace: str,
568
+ out_dir: str | Path,
569
+ filename: str | None = None,
570
+ model: str = "gpt-image-1",
571
+ size: str | None = None, # allow None → computed from aspect/mode
572
+ background: str = "opaque",
573
+ quality: str = "high",
574
+ n: int = 4,
575
+ overwrite: bool = False,
576
+ on_done=None,
577
+ on_error=None,
578
+ style: str = "sticker",
579
+ allow_text: bool = False,
580
+ palette: str | None = None,
581
+ mode: str = "logo",
582
+ aspect: str = "square",
583
+ style_intensity: str = "overt",
584
+ console: Optional[Console] = None,
585
+ image_model: Optional[str] = None,
586
+ image_model_provider: str = "openai",
587
+ image_provider_kwargs: Optional[dict] = None,
588
+ ):
589
+ _final_model = image_model or model
590
+
591
+ def _job() -> Path:
592
+ return generate_logo_sync(
593
+ problem_text=problem_text,
594
+ workspace=workspace,
595
+ out_dir=out_dir,
596
+ filename=filename,
597
+ model=_final_model,
598
+ size=size,
599
+ background=background,
600
+ quality=quality,
601
+ n=n,
602
+ overwrite=overwrite,
603
+ style=style,
604
+ allow_text=allow_text,
605
+ palette=palette,
606
+ mode=mode,
607
+ aspect=aspect,
608
+ style_intensity=style_intensity,
609
+ console=console,
610
+ image_model_provider=image_model_provider,
611
+ image_provider_kwargs=image_provider_kwargs,
612
+ )
613
+
614
+ fut = _EXEC.submit(_job)
615
+ if on_done or on_error:
616
+
617
+ def _cb(f):
618
+ try:
619
+ p = f.result()
620
+ on_done and on_done(p)
621
+ except BaseException as e:
622
+ on_error and on_error(e)
623
+
624
+ fut.add_done_callback(_cb)
625
+ return fut