File size: 10,803 Bytes
5b6e956
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# validation.py

## Purpose
Input validation utilities for Nano Banana Streamlit. Validates user inputs, parameters, and system state to ensure data integrity and provide clear error messages.

## Responsibilities
- Validate generation parameters (temperature, aspect ratio, backend)
- Validate text inputs (prompts, character names)
- Validate images (format, dimensions, file size)
- Validate complete generation requests
- Check backend availability
- Provide clear, user-friendly error messages

## Dependencies

### Imports
- `pathlib.Path` - File path operations
- `PIL.Image` - Image validation
- `config.settings.Settings` - Validation constraints
- `utils.logging_utils.get_logger` - Logging validation errors

### Used By
- All UI pages - Validate user inputs before submission
- All services - Validate parameters before generation
- Backend clients - Validate configuration
- `models/generation_request.py` - Validate request objects

## Public Interface

All validation functions return `Tuple[bool, Optional[str]]`:
- `(True, None)` if valid
- `(False, error_message)` if invalid

### Parameter Validation

#### `validate_temperature(temperature: float) -> Tuple[bool, Optional[str]]`
Validates temperature is in valid range [0.0, 1.0].

**Example:**
```python
valid, error = validate_temperature(0.5)
if not valid:
    st.error(error)
```

#### `validate_aspect_ratio(aspect_ratio: str) -> Tuple[bool, Optional[str]]`
Validates aspect ratio is in Settings.ASPECT_RATIOS.

Accepts both display names ("16:9 (1344x768)") and values ("16:9").

**Example:**
```python
valid, error = validate_aspect_ratio("16:9")
if valid:
    # Use aspect ratio
    pass
```

#### `validate_backend(backend: str) -> Tuple[bool, Optional[str]]`
Validates backend is in Settings.AVAILABLE_BACKENDS.

**Example:**
```python
valid, error = validate_backend("Gemini API (Cloud)")
```

#### `validate_prompt(prompt: str, min_length: int = 1, max_length: int = 5000) -> Tuple[bool, Optional[str]]`
Validates text prompt length.

**Parameters:**
- `prompt`: Text to validate
- `min_length`: Minimum length (default: 1)
- `max_length`: Maximum length (default: 5000)

**Example:**
```python
valid, error = validate_prompt(user_input, min_length=10)
if not valid:
    st.warning(error)
```

#### `validate_character_name(name: str) -> Tuple[bool, Optional[str]]`
Validates character name (1-100 characters).

**Example:**
```python
valid, error = validate_character_name(character_name)
if not valid:
    st.error(error)
    return
```

### Image Validation

#### `validate_image(image: Image.Image) -> Tuple[bool, Optional[str]]`
Validates PIL Image object.

**Checks:**
- Is valid Image instance
- Dimensions > 0
- Dimensions < 8192x8192 (reasonable limit)
- Mode is supported (RGB, RGBA, L, P)

**Example:**
```python
valid, error = validate_image(uploaded_image)
if not valid:
    st.error(f"Invalid image: {error}")
```

#### `validate_image_file(file_path: Path) -> Tuple[bool, Optional[str]]`
Validates image file on disk.

**Checks:**
- File exists
- Is a file (not directory)
- Has valid extension (.png, .jpg, .jpeg, .webp, .bmp)
- Can be opened as image
- Passes validate_image() checks

**Example:**
```python
valid, error = validate_image_file(Path("character.png"))
if valid:
    image = Image.open("character.png")
```

#### `validate_image_upload_size(file_size_bytes: int) -> Tuple[bool, Optional[str]]`
Validates uploaded file size against Settings.MAX_IMAGE_UPLOAD_SIZE.

**Example:**
```python
if uploaded_file:
    valid, error = validate_image_upload_size(uploaded_file.size)
    if not valid:
        st.error(error)
```

### Request Validation

#### `validate_generation_request(...) -> Tuple[bool, Optional[str]]`
Validates complete generation request.

**Parameters:**
- `prompt`: Text prompt
- `backend`: Backend name
- `aspect_ratio`: Aspect ratio
- `temperature`: Temperature value
- `input_images`: Optional list of input images

**Validates:**
- All individual parameters
- Input images (if provided, max 3)

**Example:**
```python
valid, error = validate_generation_request(
    prompt=prompt,
    backend=backend,
    aspect_ratio=aspect_ratio,
    temperature=temperature,
    input_images=[img1, img2]
)

if not valid:
    st.error(error)
    return

# Proceed with generation
result = generate(...)
```

#### `validate_character_forge_request(...) -> Tuple[bool, Optional[str]]`
Validates Character Forge-specific request.

**Parameters:**
- `character_name`: Character name
- `initial_image`: Initial image (Face Only / Full Body modes)
- `face_image`: Face image (Face+Body Separate)
- `body_image`: Body image (Face+Body Separate)
- `image_type`: Input mode type
- `backend`: Backend name

**Validates:**
- Character name
- Backend
- Correct images for selected mode

**Example:**
```python
valid, error = validate_character_forge_request(
    character_name="Hero",
    initial_image=None,
    face_image=face_img,
    body_image=body_img,
    image_type="Face + Body (Separate)",
    backend="Gemini API (Cloud)"
)

if not valid:
    st.error(error)
    return
```

### Backend Availability

#### `validate_backend_available(backend: str, api_key: Optional[str] = None) -> Tuple[bool, Optional[str]]`
Check if backend is available and configured.

**For Gemini:**
- Checks if API key is provided

**For OmniGen2:**
- Makes HTTP request to /health endpoint
- Checks server is responding and healthy

**Example:**
```python
valid, error = validate_backend_available(
    backend=st.session_state.backend,
    api_key=st.session_state.gemini_api_key
)

if not valid:
    st.warning(error)
    st.stop()
```

### Helper Functions

#### `raise_if_invalid(is_valid: bool, error_message: Optional[str], exception_type=ValueError)`
Convert validation result to exception.

**Example:**
```python
valid, error = validate_temperature(temp)
raise_if_invalid(valid, error, ValueError)
# Raises ValueError if invalid
```

#### `log_validation_error(validation_result: Tuple[bool, Optional[str]], context: str = "")`
Log validation error if validation failed.

**Example:**
```python
result = validate_prompt(prompt)
log_validation_error(result, context="user_input")
# Logs: "Validation failed [user_input]: Prompt must be at least 1 character(s)"
```

## Usage Examples

### Page Input Validation
```python
import streamlit as st
from utils.validation import (
    validate_prompt,
    validate_backend_available,
    validate_generation_request
)

# Get user inputs
prompt = st.text_area("Prompt")
backend = st.session_state.backend

if st.button("Generate"):
    # Validate prompt
    valid, error = validate_prompt(prompt, min_length=5)
    if not valid:
        st.error(error)
        st.stop()

    # Check backend available
    valid, error = validate_backend_available(backend, api_key)
    if not valid:
        st.warning(error)
        st.stop()

    # Validate complete request
    valid, error = validate_generation_request(
        prompt=prompt,
        backend=backend,
        aspect_ratio=aspect_ratio,
        temperature=temperature
    )
    if not valid:
        st.error(error)
        st.stop()

    # All valid - proceed
    result = generate_image(...)
```

### Service Parameter Validation
```python
from utils.validation import validate_generation_request, raise_if_invalid

class GenerationService:
    def generate(self, prompt, backend, aspect_ratio, temperature, ...):
        # Validate inputs
        valid, error = validate_generation_request(
            prompt, backend, aspect_ratio, temperature
        )
        raise_if_invalid(valid, error, ValueError)

        # Proceed with generation
        ...
```

### Backend Status Check
```python
import streamlit as st
from utils.validation import validate_backend_available

def render_backend_status(backend, api_key):
    valid, error = validate_backend_available(backend, api_key)

    if valid:
        st.success(f"βœ… {backend}: Ready")
    else:
        st.error(f"❌ {backend}: {error}")
```

## Error Messages

All error messages are user-friendly and actionable:

**Good Examples:**
- ❌ "Prompt must be at least 5 character(s)" (specific, clear)
- ❌ "File too large: 25.3MB (max: 20MB)" (includes values)
- ❌ "OmniGen2 server not responding. Start it with: omnigen2_plugin/server.bat start" (includes solution)

**Not Used:**
- ❌ "Invalid input" (too vague)
- ❌ "Error" (no information)
- ❌ "NoneType has no attribute..." (technical, not user-friendly)

## Validation Strategy

### When to Validate

1. **Before submission** (UI layer)
   - Validate on button click
   - Show errors immediately
   - Prevent submission if invalid

2. **In service layer** (redundant validation)
   - Validate again for safety
   - Raise exceptions if invalid
   - Protects against programmatic calls

3. **Backend availability** (startup + on demand)
   - Check on app startup
   - Check when user switches backend
   - Check before expensive operations

### What NOT to Validate

- Don't validate Streamlit widget outputs (they enforce types)
- Don't validate internal function calls between modules
- Don't validate data from trusted sources (Settings constants)

## Known Limitations
- Backend availability check makes network request (slow)
- Image validation loads entire image into memory
- No async validation support
- No batch validation support
- No custom validation rules (extension mechanism)

## Future Improvements
- Add async validation for slow checks
- Add batch validation functions
- Add validation caching (avoid redundant checks)
- Add custom validation rule registration
- Add validation result serialization
- Add more granular image checks (color space, DPI, etc.)
- Add prompt content validation (detect harmful content)

## Testing
- Test all parameter validators with valid/invalid inputs
- Test boundary conditions (min/max values)
- Test image validators with various formats
- Test backend availability with server running/stopped
- Test request validators with complete/incomplete data
- Test error message clarity and helpfulness

## Related Files
- `config/settings.py` - Validation constraints
- `utils/logging_utils.py` - Error logging
- All UI pages - Input validation
- All services - Parameter validation
- `models/generation_request.py` - Request validation

## Performance Considerations
- validate_backend_available() makes network request (~100ms)
- validate_image() loads image into memory
- validate_image_file() opens file (I/O)
- All other validators are fast (<1ms)

## Change History
- 2025-10-23: Initial creation for Streamlit migration
  - Comprehensive parameter validation
  - Image validation utilities
  - Request validation functions
  - Backend availability checks
  - User-friendly error messages
  - Helper functions for error handling