Skip to content

Commit e63337c

Browse files
[docs] Add feature flags documentation (#4797)
1 parent 385c56d commit e63337c

File tree

1 file changed

+362
-0
lines changed

1 file changed

+362
-0
lines changed

docs/FEATURE_FLAGS.md

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
# ComfyUI Feature Flags System
2+
3+
## Overview
4+
5+
The ComfyUI feature flags system enables capability negotiation between frontend and backend, allowing both sides to communicate their supported features and adapt behavior accordingly. This ensures backward compatibility while enabling progressive enhancement of features.
6+
7+
## System Architecture
8+
9+
### High-Level Flow
10+
11+
```mermaid
12+
sequenceDiagram
13+
participant Frontend
14+
participant WebSocket
15+
participant Backend
16+
participant FeatureFlags Module
17+
18+
Frontend->>WebSocket: Connect
19+
WebSocket-->>Frontend: Connection established
20+
21+
Note over Frontend: First message must be feature flags
22+
Frontend->>WebSocket: Send client feature flags
23+
WebSocket->>Backend: Receive feature flags
24+
Backend->>FeatureFlags Module: Store client capabilities
25+
26+
Backend->>FeatureFlags Module: Get server features
27+
FeatureFlags Module-->>Backend: Return server capabilities
28+
Backend->>WebSocket: Send server feature flags
29+
WebSocket-->>Frontend: Receive server features
30+
31+
Note over Frontend,Backend: Both sides now know each other's capabilities
32+
33+
Frontend->>Frontend: Store server features
34+
Frontend->>Frontend: Components use useFeatureFlags()
35+
```
36+
37+
### Component Architecture
38+
39+
```mermaid
40+
graph TB
41+
subgraph Frontend
42+
A[clientFeatureFlags.json] --> B[api.ts]
43+
B --> C[WebSocket Handler]
44+
D[useFeatureFlags composable] --> B
45+
E[Vue Components] --> D
46+
end
47+
48+
subgraph Backend
49+
F[feature_flags.py] --> G[SERVER_FEATURE_FLAGS]
50+
H[server.py WebSocket] --> F
51+
I[Feature Consumers] --> F
52+
end
53+
54+
C <--> H
55+
56+
style A fill:#f9f,stroke:#333,stroke-width:2px
57+
style G fill:#f9f,stroke:#333,stroke-width:2px
58+
style D fill:#9ff,stroke:#333,stroke-width:2px
59+
```
60+
61+
## Feature Flag Structure
62+
63+
Feature flags are organized as a flat dictionary at the top level, with extensions nested under an `extension` object:
64+
65+
### Naming Convention
66+
67+
- **Core features**: Top-level keys (e.g., `"async_execution"`, `"supports_batch_queue"`)
68+
- **Client features**: Top-level keys (e.g., `"supports_preview_metadata"`)
69+
- **Extensions**: Nested under `"extension"` object (e.g., `extension.manager`)
70+
71+
### Structure Example
72+
73+
```json
74+
{
75+
"async_execution": true,
76+
"supports_batch_queue": false,
77+
"supports_preview_metadata": true,
78+
"supports_websocket_v2": false,
79+
"max_upload_size": 104857600,
80+
"extension": {
81+
"manager": {
82+
"supports_v4": true,
83+
"supports_ai_search": false
84+
}
85+
}
86+
}
87+
```
88+
89+
## Implementation Details
90+
91+
### Backend Implementation
92+
93+
```mermaid
94+
classDiagram
95+
class FeatureFlagsModule {
96+
+SERVER_FEATURE_FLAGS: Dict
97+
+get_server_features() Dict
98+
+supports_feature(sockets_metadata, sid, feature_name) bool
99+
+get_connection_feature(sockets_metadata, sid, feature_name, default) Any
100+
}
101+
102+
class PromptServer {
103+
-sockets_metadata: Dict
104+
+websocket_handler()
105+
+send()
106+
}
107+
108+
class FeatureConsumer {
109+
<<interface>>
110+
+check_feature()
111+
+use_feature()
112+
}
113+
114+
PromptServer --> FeatureFlagsModule
115+
FeatureConsumer --> FeatureFlagsModule
116+
```
117+
118+
### Frontend Implementation
119+
120+
The `useFeatureFlags` composable provides reactive access to feature flags, meaning components will automatically update when feature flags change (e.g., during WebSocket reconnection).
121+
122+
```mermaid
123+
classDiagram
124+
class ComfyApi {
125+
+serverFeatureFlags: Record~string, unknown~
126+
+getClientFeatureFlags() Record
127+
+serverSupportsFeature(name) boolean
128+
+getServerFeature(name, default) T
129+
}
130+
131+
class useFeatureFlags {
132+
+serverSupports(name) boolean
133+
+getServerFeature(name, default) T
134+
+createServerFeatureFlag(name) ComputedRef
135+
+extension: ExtensionFlags
136+
}
137+
138+
class VueComponent {
139+
<<component>>
140+
+setup()
141+
}
142+
143+
ComfyApi <-- useFeatureFlags
144+
VueComponent --> useFeatureFlags
145+
```
146+
147+
## Examples
148+
149+
### 1. Preview Metadata Support
150+
151+
```mermaid
152+
graph LR
153+
A[Preview Generation] --> B{supports_preview_metadata?}
154+
B -->|Yes| C[Send metadata with preview]
155+
B -->|No| D[Send preview only]
156+
157+
C --> E[Enhanced preview with node info]
158+
D --> F[Basic preview image]
159+
```
160+
161+
**Backend Usage:**
162+
```python
163+
# Check if client supports preview metadata
164+
if feature_flags.supports_feature(
165+
self.server_instance.sockets_metadata,
166+
self.server_instance.client_id,
167+
"supports_preview_metadata"
168+
):
169+
# Send enhanced preview with metadata
170+
metadata = {
171+
"node_id": node_id,
172+
"prompt_id": prompt_id,
173+
"display_node_id": display_node_id,
174+
"parent_node_id": parent_node_id,
175+
"real_node_id": real_node_id,
176+
}
177+
self.server_instance.send_sync(
178+
BinaryEventTypes.PREVIEW_IMAGE_WITH_METADATA,
179+
(image, metadata),
180+
self.server_instance.client_id,
181+
)
182+
```
183+
184+
### 2. Max Upload Size
185+
186+
```mermaid
187+
graph TB
188+
A[Client File Upload] --> B[Check max_upload_size]
189+
B --> C{File size OK?}
190+
C -->|Yes| D[Upload file]
191+
C -->|No| E[Show error]
192+
193+
F[Backend] --> G[Set from CLI args]
194+
G --> H[Convert MB to bytes]
195+
H --> I[Include in feature flags]
196+
```
197+
198+
**Backend Configuration:**
199+
```python
200+
# In feature_flags.py
201+
SERVER_FEATURE_FLAGS = {
202+
"supports_preview_metadata": True,
203+
"max_upload_size": args.max_upload_size * 1024 * 1024, # Convert MB to bytes
204+
}
205+
```
206+
207+
**Frontend Usage:**
208+
```typescript
209+
const { getServerFeature } = useFeatureFlags()
210+
const maxUploadSize = getServerFeature('max_upload_size', 100 * 1024 * 1024) // Default 100MB
211+
```
212+
213+
## Using Feature Flags
214+
215+
### Frontend Access Patterns
216+
217+
1. **Direct API access:**
218+
```typescript
219+
// Check boolean feature
220+
if (api.serverSupportsFeature('supports_preview_metadata')) {
221+
// Feature is supported
222+
}
223+
224+
// Get feature value with default
225+
const maxSize = api.getServerFeature('max_upload_size', 100 * 1024 * 1024)
226+
```
227+
228+
2. **Using the composable (recommended for reactive components):**
229+
```typescript
230+
const { serverSupports, getServerFeature, extension } = useFeatureFlags()
231+
232+
// Check feature support
233+
if (serverSupports('supports_preview_metadata')) {
234+
// Use enhanced previews
235+
}
236+
237+
// Use reactive convenience properties (automatically update if flags change)
238+
if (extension.manager.supportsV4.value) {
239+
// Use V4 manager API
240+
}
241+
```
242+
243+
3. **Reactive usage in templates:**
244+
```vue
245+
<template>
246+
<div v-if="featureFlags.extension.manager.supportsV4">
247+
<!-- V4-specific UI -->
248+
</div>
249+
<div v-else>
250+
<!-- Legacy UI -->
251+
</div>
252+
</template>
253+
254+
<script setup>
255+
import { useFeatureFlags } from '@/composables/useFeatureFlags'
256+
const featureFlags = useFeatureFlags()
257+
</script>
258+
```
259+
260+
### Backend Access Patterns
261+
262+
```python
263+
# Check if a specific client supports a feature
264+
if feature_flags.supports_feature(
265+
sockets_metadata,
266+
client_id,
267+
"supports_preview_metadata"
268+
):
269+
# Client supports this feature
270+
271+
# Get feature value with default
272+
max_size = feature_flags.get_connection_feature(
273+
sockets_metadata,
274+
client_id,
275+
"max_upload_size",
276+
100 * 1024 * 1024 # Default 100MB
277+
)
278+
```
279+
280+
## Adding New Feature Flags
281+
282+
### Backend
283+
284+
1. **For server capabilities**, add to `SERVER_FEATURE_FLAGS` in `comfy_api/feature_flags.py`:
285+
```python
286+
SERVER_FEATURE_FLAGS = {
287+
"supports_preview_metadata": True,
288+
"max_upload_size": args.max_upload_size * 1024 * 1024,
289+
"your_new_feature": True, # Add your flag
290+
}
291+
```
292+
293+
2. **Use in your code:**
294+
```python
295+
if feature_flags.supports_feature(sockets_metadata, sid, "your_new_feature"):
296+
# Feature-specific code
297+
```
298+
299+
### Frontend
300+
301+
1. **For client capabilities**, add to `src/config/clientFeatureFlags.json`:
302+
```json
303+
{
304+
"supports_preview_metadata": false,
305+
"your_new_feature": true
306+
}
307+
```
308+
309+
2. **For extension features**, update the composable to add convenience accessors:
310+
```typescript
311+
// In useFeatureFlags.ts
312+
const extension = {
313+
manager: {
314+
supportsV4: computed(() => getServerFeature('extension.manager.supports_v4', false))
315+
},
316+
yourExtension: {
317+
supportsNewFeature: computed(() => getServerFeature('extension.yourExtension.supports_new_feature', false))
318+
}
319+
}
320+
321+
return {
322+
// ... existing returns
323+
extension
324+
}
325+
```
326+
327+
## Testing Feature Flags
328+
329+
```mermaid
330+
graph LR
331+
A[Test Scenarios] --> B[Both support feature]
332+
A --> C[Only frontend supports]
333+
A --> D[Only backend supports]
334+
A --> E[Neither supports]
335+
336+
B --> F[Feature enabled]
337+
C --> G[Feature disabled]
338+
D --> H[Feature disabled]
339+
E --> I[Feature disabled]
340+
```
341+
342+
Test your feature flags with different combinations:
343+
- Frontend with flag + Backend with flag = Feature works
344+
- Frontend with flag + Backend without = Graceful degradation
345+
- Frontend without + Backend with flag = No feature usage
346+
- Neither has flag = Default behavior
347+
348+
### Example Test
349+
350+
```typescript
351+
// In tests-ui/tests/api.featureFlags.test.ts
352+
it('should handle preview metadata based on feature flag', () => {
353+
// Mock server supports feature
354+
api.serverFeatureFlags = { supports_preview_metadata: true }
355+
356+
expect(api.serverSupportsFeature('supports_preview_metadata')).toBe(true)
357+
358+
// Mock server doesn't support feature
359+
api.serverFeatureFlags = {}
360+
361+
expect(api.serverSupportsFeature('supports_preview_metadata')).toBe(false)
362+
})

0 commit comments

Comments
 (0)