cudag 0.3.10__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 (69) hide show
  1. cudag/__init__.py +334 -0
  2. cudag/annotation/__init__.py +77 -0
  3. cudag/annotation/codegen.py +648 -0
  4. cudag/annotation/config.py +545 -0
  5. cudag/annotation/loader.py +342 -0
  6. cudag/annotation/scaffold.py +121 -0
  7. cudag/annotation/transcription.py +296 -0
  8. cudag/cli/__init__.py +5 -0
  9. cudag/cli/main.py +315 -0
  10. cudag/cli/new.py +873 -0
  11. cudag/core/__init__.py +364 -0
  12. cudag/core/button.py +137 -0
  13. cudag/core/canvas.py +222 -0
  14. cudag/core/config.py +70 -0
  15. cudag/core/coords.py +233 -0
  16. cudag/core/data_grid.py +804 -0
  17. cudag/core/dataset.py +678 -0
  18. cudag/core/distribution.py +136 -0
  19. cudag/core/drawing.py +75 -0
  20. cudag/core/fonts.py +156 -0
  21. cudag/core/generator.py +163 -0
  22. cudag/core/grid.py +367 -0
  23. cudag/core/grounding_task.py +247 -0
  24. cudag/core/icon.py +207 -0
  25. cudag/core/iconlist_task.py +301 -0
  26. cudag/core/models.py +1251 -0
  27. cudag/core/random.py +130 -0
  28. cudag/core/renderer.py +190 -0
  29. cudag/core/screen.py +402 -0
  30. cudag/core/scroll_task.py +254 -0
  31. cudag/core/scrollable_grid.py +447 -0
  32. cudag/core/state.py +110 -0
  33. cudag/core/task.py +293 -0
  34. cudag/core/taskbar.py +350 -0
  35. cudag/core/text.py +212 -0
  36. cudag/core/utils.py +82 -0
  37. cudag/data/surnames.txt +5000 -0
  38. cudag/modal_apps/__init__.py +4 -0
  39. cudag/modal_apps/archive.py +103 -0
  40. cudag/modal_apps/extract.py +138 -0
  41. cudag/modal_apps/preprocess.py +529 -0
  42. cudag/modal_apps/upload.py +317 -0
  43. cudag/prompts/SYSTEM_PROMPT.txt +104 -0
  44. cudag/prompts/__init__.py +33 -0
  45. cudag/prompts/system.py +43 -0
  46. cudag/prompts/tools.py +382 -0
  47. cudag/py.typed +0 -0
  48. cudag/schemas/filesystem.json +90 -0
  49. cudag/schemas/test_record.schema.json +113 -0
  50. cudag/schemas/train_record.schema.json +90 -0
  51. cudag/server/__init__.py +21 -0
  52. cudag/server/app.py +232 -0
  53. cudag/server/services/__init__.py +9 -0
  54. cudag/server/services/generator.py +128 -0
  55. cudag/templates/scripts/archive.sh +35 -0
  56. cudag/templates/scripts/build.sh +13 -0
  57. cudag/templates/scripts/extract.sh +54 -0
  58. cudag/templates/scripts/generate.sh +116 -0
  59. cudag/templates/scripts/pre-commit.sh +44 -0
  60. cudag/templates/scripts/preprocess.sh +46 -0
  61. cudag/templates/scripts/upload.sh +63 -0
  62. cudag/templates/scripts/verify.py +428 -0
  63. cudag/validation/__init__.py +35 -0
  64. cudag/validation/validate.py +508 -0
  65. cudag-0.3.10.dist-info/METADATA +570 -0
  66. cudag-0.3.10.dist-info/RECORD +69 -0
  67. cudag-0.3.10.dist-info/WHEEL +4 -0
  68. cudag-0.3.10.dist-info/entry_points.txt +2 -0
  69. cudag-0.3.10.dist-info/licenses/LICENSE +66 -0
@@ -0,0 +1,447 @@
1
+ # Copyright (c) 2025 Tylt LLC. All rights reserved.
2
+ # CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
3
+ # is strictly prohibited. For licensing inquiries: hello@claimhawk.app
4
+
5
+ """Scrollable data grid abstraction for VLM training.
6
+
7
+ Provides reusable components for rendering scrollable grids with:
8
+ - Column definitions and text wrapping
9
+ - Row rendering with variable heights
10
+ - Scroll position and viewport calculations
11
+ - Scrollbar rendering
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass, field
17
+ from typing import Any, Sequence
18
+
19
+ from PIL import Image, ImageDraw, ImageFont
20
+
21
+
22
+ @dataclass
23
+ class ColumnDef:
24
+ """Definition for a grid column."""
25
+
26
+ id: str
27
+ """Column identifier (matches row dict keys)."""
28
+
29
+ label: str
30
+ """Display label for header."""
31
+
32
+ x: int
33
+ """X position of column start."""
34
+
35
+ align: str = "left"
36
+ """Text alignment: 'left' or 'right'."""
37
+
38
+
39
+ @dataclass
40
+ class ScrollableGridGeometry:
41
+ """Geometry for a scrollable grid area."""
42
+
43
+ x: int
44
+ """X position of grid top-left."""
45
+
46
+ y: int
47
+ """Y position of grid top-left."""
48
+
49
+ width: int
50
+ """Total width of grid area."""
51
+
52
+ height: int
53
+ """Total height of grid area."""
54
+
55
+ padding: int = 0
56
+ """Internal padding."""
57
+
58
+ header_height: int = 0
59
+ """Height of fixed header row."""
60
+
61
+ scrollbar_width: int = 19
62
+ """Width of scrollbar track."""
63
+
64
+ @property
65
+ def content_width(self) -> int:
66
+ """Width available for content (excluding scrollbar)."""
67
+ return self.width - self.scrollbar_width - self.padding * 2
68
+
69
+ @property
70
+ def content_height(self) -> int:
71
+ """Height available for content (excluding header)."""
72
+ return self.height - self.header_height - self.padding * 2
73
+
74
+ @property
75
+ def center(self) -> tuple[int, int]:
76
+ """Center point of the grid (for scroll actions)."""
77
+ cx = self.x + (self.width - self.scrollbar_width) // 2
78
+ cy = self.y + self.height // 2
79
+ return (cx, cy)
80
+
81
+
82
+ @dataclass
83
+ class ScrollState:
84
+ """Scroll position state."""
85
+
86
+ page: int = 1
87
+ """Current page number (1-based)."""
88
+
89
+ has_more: bool = True
90
+ """Whether more content exists below."""
91
+
92
+
93
+ @dataclass
94
+ class RowLayout:
95
+ """Layout information for a rendered row."""
96
+
97
+ height: int
98
+ """Rendered height of row."""
99
+
100
+ wrapped_text: dict[str, list[str]]
101
+ """Wrapped text lines per column."""
102
+
103
+ data: dict[str, str]
104
+ """Original row data."""
105
+
106
+
107
+ class ScrollableGrid:
108
+ """A scrollable data grid that renders rows with text wrapping.
109
+
110
+ Usage:
111
+ grid = ScrollableGrid(
112
+ geometry=ScrollableGridGeometry(x=0, y=100, width=800, height=300),
113
+ columns=[
114
+ ColumnDef(id="name", label="Name", x=0),
115
+ ColumnDef(id="value", label="Value", x=200, align="right"),
116
+ ],
117
+ font=ImageFont.truetype("arial.ttf", 12),
118
+ )
119
+
120
+ # Render rows
121
+ body_image, row_layouts = grid.render_rows(rows)
122
+
123
+ # Get visible portion
124
+ visible = grid.get_visible_slice(body_image, scroll_state)
125
+
126
+ # Compose onto base image
127
+ grid.compose_onto(base_image, visible, scroll_state, body_image.height)
128
+ """
129
+
130
+ def __init__(
131
+ self,
132
+ geometry: ScrollableGridGeometry,
133
+ columns: Sequence[ColumnDef],
134
+ font: ImageFont.FreeTypeFont,
135
+ cell_padding: int = 3,
136
+ line_color: tuple[int, int, int] = (210, 210, 210),
137
+ text_color: tuple[int, int, int] = (0, 0, 0),
138
+ bg_color: tuple[int, int, int] = (255, 255, 255),
139
+ ):
140
+ """Initialize scrollable grid.
141
+
142
+ Args:
143
+ geometry: Grid positioning and sizing.
144
+ columns: Column definitions.
145
+ font: Font for rendering text.
146
+ cell_padding: Padding inside each cell.
147
+ line_color: Color for grid lines.
148
+ text_color: Color for text.
149
+ bg_color: Background color.
150
+ """
151
+ self.geometry = geometry
152
+ self.columns = list(columns)
153
+ self.font = font
154
+ self.cell_padding = cell_padding
155
+ self.line_color = line_color
156
+ self.text_color = text_color
157
+ self.bg_color = bg_color
158
+
159
+ # Compute column widths
160
+ self._column_widths = self._compute_column_widths()
161
+ self._line_height = self._compute_line_height()
162
+
163
+ def _compute_column_widths(self) -> list[int]:
164
+ """Compute widths from column x positions."""
165
+ widths: list[int] = []
166
+ total_width = self.geometry.content_width
167
+ for i, col in enumerate(self.columns):
168
+ if i + 1 < len(self.columns):
169
+ widths.append(self.columns[i + 1].x - col.x)
170
+ else:
171
+ widths.append(total_width - col.x)
172
+ return widths
173
+
174
+ def _compute_line_height(self) -> int:
175
+ """Get line height from font metrics."""
176
+ ascent, descent = self.font.getmetrics()
177
+ return int(ascent) + int(descent)
178
+
179
+ def _wrap_text(self, text: str, max_width: int) -> list[str]:
180
+ """Wrap text to fit within max_width pixels."""
181
+ words = text.split()
182
+ if not words:
183
+ return [""]
184
+ lines: list[str] = []
185
+ current = words[0]
186
+ for word in words[1:]:
187
+ candidate = f"{current} {word}"
188
+ if self.font.getlength(candidate) <= max_width:
189
+ current = candidate
190
+ else:
191
+ lines.append(current)
192
+ current = word
193
+ lines.append(current)
194
+ return lines
195
+
196
+ def _compute_row_height(self, row: dict[str, str]) -> tuple[int, dict[str, list[str]]]:
197
+ """Calculate row height and wrapped text for all columns."""
198
+ wrapped: dict[str, list[str]] = {}
199
+ max_lines = 1
200
+ for idx, col in enumerate(self.columns):
201
+ width = self._column_widths[idx]
202
+ lines = self._wrap_text(
203
+ row.get(col.id, ""),
204
+ max_width=max(width - self.cell_padding * 2, 10),
205
+ )
206
+ wrapped[col.id] = lines
207
+ max_lines = max(max_lines, len(lines))
208
+ height = max_lines * self._line_height + self.cell_padding * 2
209
+ return height, wrapped
210
+
211
+ def render_rows(self, rows: Sequence[dict[str, str]]) -> tuple[Image.Image, list[RowLayout]]:
212
+ """Render all rows to a body image.
213
+
214
+ Args:
215
+ rows: List of row data dicts.
216
+
217
+ Returns:
218
+ (body_image, row_layouts) - The rendered image and layout info.
219
+ """
220
+ # First pass: compute layouts
221
+ row_layouts: list[RowLayout] = []
222
+ total_height = 0
223
+ for row in rows:
224
+ height, wrapped = self._compute_row_height(row)
225
+ row_layouts.append(RowLayout(height=height, wrapped_text=wrapped, data=row))
226
+ total_height += height
227
+
228
+ # Create body image
229
+ body_width = sum(self._column_widths)
230
+ body_image = Image.new("RGB", (body_width, max(total_height, 1)), color=self.bg_color)
231
+ draw = ImageDraw.Draw(body_image)
232
+
233
+ # Second pass: render
234
+ y = 0
235
+ for layout in row_layouts:
236
+ for col_idx, col in enumerate(self.columns):
237
+ x_start = col.x
238
+ x_end = col.x + self._column_widths[col_idx]
239
+ lines = layout.wrapped_text[col.id]
240
+ for line_idx, line in enumerate(lines):
241
+ if col.align == "right":
242
+ text_width = int(self.font.getlength(line))
243
+ text_x = max(x_end - self.cell_padding - text_width, x_start + self.cell_padding)
244
+ else:
245
+ text_x = x_start + self.cell_padding
246
+ text_y = y + self.cell_padding + line_idx * self._line_height
247
+ draw.text((text_x, text_y), line, font=self.font, fill=self.text_color)
248
+
249
+ y += layout.height
250
+ # Horizontal line after row
251
+ draw.line([(0, y - 1), (body_width, y - 1)], fill=self.line_color)
252
+
253
+ # Vertical column separators
254
+ for col in self.columns:
255
+ draw.line([(col.x, 0), (col.x, total_height)], fill=self.line_color)
256
+
257
+ return body_image, row_layouts
258
+
259
+ def get_scroll_offset(
260
+ self,
261
+ scroll_state: ScrollState,
262
+ content_height: int,
263
+ ) -> int:
264
+ """Calculate scroll offset from scroll state.
265
+
266
+ Args:
267
+ scroll_state: Current scroll state.
268
+ content_height: Total height of content.
269
+
270
+ Returns:
271
+ Pixel offset from top.
272
+ """
273
+ visible_height = self.geometry.content_height
274
+ max_offset = max(content_height - visible_height, 0)
275
+
276
+ if not scroll_state.has_more:
277
+ return max_offset
278
+
279
+ page_height = max(visible_height, 1)
280
+ offset = max((scroll_state.page - 1) * page_height, 0)
281
+ return min(offset, max_offset)
282
+
283
+ def get_visible_slice(
284
+ self,
285
+ body_image: Image.Image,
286
+ scroll_state: ScrollState,
287
+ ) -> Image.Image:
288
+ """Get visible portion of body image.
289
+
290
+ Args:
291
+ body_image: Full rendered body.
292
+ scroll_state: Current scroll state.
293
+
294
+ Returns:
295
+ Cropped image for visible viewport.
296
+ """
297
+ visible_height = self.geometry.content_height
298
+ start_offset = self.get_scroll_offset(scroll_state, body_image.height)
299
+
300
+ if body_image.height <= visible_height:
301
+ canvas = Image.new("RGB", (body_image.width, visible_height), color=self.bg_color)
302
+ canvas.paste(body_image, (0, 0))
303
+ return canvas
304
+
305
+ end_offset = min(start_offset + visible_height, body_image.height)
306
+ cropped = body_image.crop((0, start_offset, body_image.width, end_offset))
307
+
308
+ if cropped.height == visible_height:
309
+ return cropped
310
+
311
+ canvas = Image.new("RGB", (body_image.width, visible_height), color=self.bg_color)
312
+ canvas.paste(cropped, (0, 0))
313
+ return canvas
314
+
315
+ def get_visible_row_indices(
316
+ self,
317
+ row_layouts: Sequence[RowLayout],
318
+ scroll_state: ScrollState,
319
+ content_height: int,
320
+ ) -> list[int]:
321
+ """Get indices of rows visible in current viewport.
322
+
323
+ Args:
324
+ row_layouts: Layout info for all rows.
325
+ scroll_state: Current scroll state.
326
+ content_height: Total content height.
327
+
328
+ Returns:
329
+ List of visible row indices.
330
+ """
331
+ start_offset = self.get_scroll_offset(scroll_state, content_height)
332
+ end_offset = start_offset + self.geometry.content_height
333
+ visible: list[int] = []
334
+
335
+ y = 0
336
+ for idx, layout in enumerate(row_layouts):
337
+ row_top = y
338
+ row_bottom = y + layout.height
339
+ if row_bottom > start_offset and row_top < end_offset:
340
+ visible.append(idx)
341
+ y = row_bottom
342
+
343
+ return visible
344
+
345
+ def render_scrollbar(
346
+ self,
347
+ content_height: int,
348
+ scroll_state: ScrollState,
349
+ min_thumb: int = 30,
350
+ ) -> Image.Image:
351
+ """Render scrollbar track with thumb.
352
+
353
+ Args:
354
+ content_height: Total content height.
355
+ scroll_state: Current scroll state.
356
+ min_thumb: Minimum thumb height.
357
+
358
+ Returns:
359
+ Scrollbar image.
360
+ """
361
+ track_height = self.geometry.content_height
362
+ visible_height = track_height
363
+ width = self.geometry.scrollbar_width
364
+
365
+ # Create track
366
+ track = Image.new("RGB", (width, track_height), color=(240, 240, 240))
367
+ draw = ImageDraw.Draw(track)
368
+
369
+ if content_height <= 0 or visible_height <= 0:
370
+ return track
371
+
372
+ # Calculate thumb size and position
373
+ ratio = visible_height / content_height
374
+ thumb_height = max(min_thumb, int(track_height * ratio))
375
+ max_offset = max(content_height - visible_height, 1)
376
+ start_offset = self.get_scroll_offset(scroll_state, content_height)
377
+ travel = track_height - thumb_height
378
+ thumb_y = int((start_offset / max_offset) * travel) if travel > 0 else 0
379
+
380
+ # Draw thumb (thin dark line)
381
+ thumb_width = 2
382
+ thumb_x0 = (width - thumb_width) // 2
383
+ thumb_x1 = thumb_x0 + thumb_width - 1
384
+ draw.rectangle(
385
+ [(thumb_x0, thumb_y), (thumb_x1, thumb_y + thumb_height)],
386
+ fill=(100, 100, 100),
387
+ )
388
+
389
+ return track
390
+
391
+ def compose_onto(
392
+ self,
393
+ base_image: Image.Image,
394
+ visible_content: Image.Image,
395
+ scroll_state: ScrollState,
396
+ content_height: int,
397
+ header_image: Image.Image | None = None,
398
+ ) -> dict[str, Any]:
399
+ """Compose grid onto base image.
400
+
401
+ Args:
402
+ base_image: Base image to paste onto (modified in place).
403
+ visible_content: Visible portion of content.
404
+ scroll_state: Current scroll state.
405
+ content_height: Total content height.
406
+ header_image: Optional header row image.
407
+
408
+ Returns:
409
+ Metadata dict with scroll info.
410
+ """
411
+ geom = self.geometry
412
+
413
+ # Create grid canvas
414
+ grid_canvas = Image.new("RGB", (geom.width, geom.height), color=self.bg_color)
415
+
416
+ # Add header if provided
417
+ y_offset = geom.padding
418
+ if header_image and geom.header_height > 0:
419
+ grid_canvas.paste(header_image, (geom.padding, y_offset))
420
+ y_offset += geom.header_height
421
+
422
+ # Add visible content
423
+ grid_canvas.paste(visible_content, (geom.padding, y_offset))
424
+
425
+ # Add scrollbar
426
+ if geom.scrollbar_width > 0:
427
+ scrollbar = self.render_scrollbar(content_height, scroll_state)
428
+ grid_canvas.paste(
429
+ scrollbar,
430
+ (geom.width - geom.scrollbar_width, geom.padding),
431
+ )
432
+
433
+ # Paste onto base
434
+ base_image.paste(grid_canvas, (geom.x, geom.y))
435
+
436
+ # Return metadata
437
+ start_offset = self.get_scroll_offset(scroll_state, content_height)
438
+ max_offset = max(content_height - geom.content_height, 0)
439
+
440
+ return {
441
+ "grid_center": geom.center,
442
+ "scroll_offset": start_offset,
443
+ "visible_height": geom.content_height,
444
+ "content_height": content_height,
445
+ "has_more_above": start_offset > 0,
446
+ "has_more_below": start_offset < max_offset,
447
+ }
cudag/core/state.py ADDED
@@ -0,0 +1,110 @@
1
+ # Copyright (c) 2025 Tylt LLC. All rights reserved.
2
+ # CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
3
+ # is strictly prohibited. For licensing inquiries: hello@claimhawk.app
4
+
5
+ """Base state class for screen state data.
6
+
7
+ State represents the dynamic data that populates a screen at render time.
8
+ Each generator project defines its own State class with relevant fields.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from typing import Any, ClassVar
15
+
16
+
17
+ @dataclass
18
+ class BaseState:
19
+ """Base class for screen state.
20
+
21
+ Subclass this to define the dynamic data for your screen.
22
+
23
+ Example:
24
+ @dataclass
25
+ class CalendarState(BaseState):
26
+ year: int
27
+ month: int
28
+ selected_day: int
29
+ current_day: int
30
+ target_day: int
31
+
32
+ @property
33
+ def month_name(self) -> str:
34
+ import calendar
35
+ return calendar.month_name[self.month]
36
+ """
37
+
38
+ # Subclasses can define validation rules
39
+ _validators: ClassVar[list[str]] = []
40
+
41
+ def validate(self) -> list[str]:
42
+ """Validate the state and return list of errors.
43
+
44
+ Override in subclass to add custom validation.
45
+
46
+ Returns:
47
+ List of error messages (empty if valid)
48
+ """
49
+ return []
50
+
51
+ def to_dict(self) -> dict[str, Any]:
52
+ """Convert state to dictionary.
53
+
54
+ Useful for serialization and metadata.
55
+ """
56
+ return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
57
+
58
+ @classmethod
59
+ def from_dict(cls, data: dict[str, Any]) -> BaseState:
60
+ """Create state from dictionary."""
61
+ return cls(**data)
62
+
63
+
64
+ @dataclass
65
+ class ScrollState(BaseState):
66
+ """Common scroll state for scrollable regions.
67
+
68
+ Reusable across different generators that have scrolling.
69
+ """
70
+
71
+ scroll_position: int = 0
72
+ """Current scroll offset in pixels."""
73
+
74
+ has_more_above: bool = False
75
+ """Whether there's content above the visible area."""
76
+
77
+ has_more_below: bool = True
78
+ """Whether there's content below the visible area."""
79
+
80
+ page_size: int = 100
81
+ """Visible area height in pixels."""
82
+
83
+ content_height: int = 0
84
+ """Total content height in pixels."""
85
+
86
+ @property
87
+ def max_scroll(self) -> int:
88
+ """Maximum scroll position."""
89
+ return max(0, self.content_height - self.page_size)
90
+
91
+ @property
92
+ def at_top(self) -> bool:
93
+ """Whether scrolled to top."""
94
+ return self.scroll_position <= 0
95
+
96
+ @property
97
+ def at_bottom(self) -> bool:
98
+ """Whether scrolled to bottom."""
99
+ return self.scroll_position >= self.max_scroll
100
+
101
+ def scroll_by(self, pixels: int) -> ScrollState:
102
+ """Return new state scrolled by given pixels."""
103
+ new_pos = max(0, min(self.scroll_position + pixels, self.max_scroll))
104
+ return ScrollState(
105
+ scroll_position=new_pos,
106
+ has_more_above=new_pos > 0,
107
+ has_more_below=new_pos < self.max_scroll,
108
+ page_size=self.page_size,
109
+ content_height=self.content_height,
110
+ )