- Moved streaming state (isGenerating, content, tokens, errors) from StepGenerate to usePostEditor hook - Added new state management to allow continuous AI generation when navigating between editor steps - Updated EditorShell to pass streaming state and setters down to StepGenerate component - Added detailed documentation explaining streaming persistence architecture and user experience - Removed local state from StepGenerate in favor of props
264 lines
6.6 KiB
Markdown
264 lines
6.6 KiB
Markdown
# Streaming Persistence Across Navigation
|
|
|
|
## Problem Solved
|
|
|
|
**Before**: When navigating away from the Generate step during AI generation, the streaming would stop and all progress would be lost.
|
|
|
|
**After**: Streaming continues in the background even when you navigate to other steps. Content is preserved and available when you return.
|
|
|
|
## How It Works
|
|
|
|
### State Management Architecture
|
|
|
|
```
|
|
usePostEditor Hook (Persistent)
|
|
↓
|
|
├─ isGenerating: boolean
|
|
├─ streamingContent: string
|
|
├─ tokenCount: number
|
|
└─ generationError: string
|
|
↓
|
|
EditorShell (Parent)
|
|
↓
|
|
StepGenerate (Child)
|
|
```
|
|
|
|
### Key Changes
|
|
|
|
#### 1. **Lifted State to Hook** (`usePostEditor.ts`)
|
|
```typescript
|
|
// Streaming state (persisted across navigation)
|
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
const [streamingContent, setStreamingContent] = useState('');
|
|
const [tokenCount, setTokenCount] = useState(0);
|
|
const [generationError, setGenerationError] = useState<string>('');
|
|
```
|
|
|
|
#### 2. **Passed Through EditorShell** (`EditorShell.tsx`)
|
|
```typescript
|
|
<StepGenerate
|
|
// ... other props
|
|
isGenerating={isGenerating}
|
|
streamingContent={streamingContent}
|
|
tokenCount={tokenCount}
|
|
generationError={generationError}
|
|
onSetIsGenerating={setIsGenerating}
|
|
onSetStreamingContent={setStreamingContent}
|
|
onSetTokenCount={setTokenCount}
|
|
onSetGenerationError={setGenerationError}
|
|
/>
|
|
```
|
|
|
|
#### 3. **Used in Component** (`StepGenerate.tsx`)
|
|
```typescript
|
|
// No longer local state - uses props from hook
|
|
const {
|
|
isGenerating,
|
|
streamingContent,
|
|
tokenCount,
|
|
generationError,
|
|
onSetIsGenerating,
|
|
onSetStreamingContent,
|
|
onSetTokenCount,
|
|
onSetGenerationError
|
|
} = props;
|
|
```
|
|
|
|
## User Experience
|
|
|
|
### Scenario 1: Navigate During Streaming
|
|
|
|
1. **Start generation** on Generate step
|
|
2. **See content streaming** in real-time
|
|
3. **Navigate to Assets step** to check something
|
|
4. **Generation continues** in background
|
|
5. **Return to Generate step**
|
|
6. **See completed content** or ongoing stream
|
|
|
|
### Scenario 2: Check Other Steps While Generating
|
|
|
|
```
|
|
Step 0: Assets ← Navigate here
|
|
Step 1: AI Prompt ← Or here
|
|
Step 2: Generate ← Streaming continues here
|
|
Step 3: Edit ← Or here
|
|
Step 4: Metadata
|
|
Step 5: Publish
|
|
```
|
|
|
|
**Result**: Generation keeps running, content preserved
|
|
|
|
### Scenario 3: Long Generation
|
|
|
|
- Start 2000-word article generation (~60 seconds)
|
|
- Navigate to Edit step to prepare
|
|
- Navigate to Metadata to plan tags
|
|
- Return to Generate step
|
|
- Content is complete and ready!
|
|
|
|
## Technical Benefits
|
|
|
|
### 1. **State Persistence**
|
|
- State lives in `usePostEditor` hook
|
|
- Hook persists across step navigation
|
|
- Only unmounts when leaving entire editor
|
|
|
|
### 2. **Background Processing**
|
|
- Streaming API continues regardless of UI
|
|
- Server-Sent Events connection stays open
|
|
- Content accumulates in hook state
|
|
|
|
### 3. **No Data Loss**
|
|
- Partial content preserved if navigation occurs
|
|
- Token count maintained
|
|
- Error state preserved
|
|
- Can resume viewing at any time
|
|
|
|
### 4. **Better UX**
|
|
- Don't have to wait on Generate step
|
|
- Can multitask while AI generates
|
|
- No accidental cancellation
|
|
- Flexible workflow
|
|
|
|
## Implementation Details
|
|
|
|
### State Flow During Streaming
|
|
|
|
```
|
|
1. User clicks "Generate Draft"
|
|
↓
|
|
2. onSetIsGenerating(true) in hook
|
|
↓
|
|
3. Stream starts, chunks arrive
|
|
↓
|
|
4. onSetStreamingContent(content + delta)
|
|
↓
|
|
5. User navigates to another step
|
|
↓
|
|
6. StepGenerate unmounts (local state lost)
|
|
BUT hook state persists!
|
|
↓
|
|
7. Stream continues, updating hook state
|
|
↓
|
|
8. User returns to Generate step
|
|
↓
|
|
9. StepGenerate remounts with current hook state
|
|
↓
|
|
10. Sees current streaming content or final result
|
|
```
|
|
|
|
### Memory Management
|
|
|
|
- **Hook state**: ~few KB for content string
|
|
- **Streaming connection**: Maintained by browser
|
|
- **Cleanup**: Automatic when leaving editor
|
|
- **No memory leaks**: State cleared on unmount
|
|
|
|
## Edge Cases Handled
|
|
|
|
### 1. **Navigation During Stream**
|
|
✅ Stream continues
|
|
✅ Content preserved
|
|
✅ Can return anytime
|
|
|
|
### 2. **Error During Stream**
|
|
✅ Error state preserved
|
|
✅ Visible when returning
|
|
✅ Can retry generation
|
|
|
|
### 3. **Multiple Generations**
|
|
✅ Previous content cleared on new generation
|
|
✅ State reset properly
|
|
✅ No conflicts
|
|
|
|
### 4. **Browser Refresh**
|
|
❌ Stream lost (expected - SSE connection closed)
|
|
✅ Last saved draft preserved in database
|
|
✅ Can regenerate if needed
|
|
|
|
## Comparison
|
|
|
|
### Before (Local State)
|
|
```typescript
|
|
// In StepGenerate.tsx
|
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
const [streamingContent, setStreamingContent] = useState('');
|
|
|
|
// ❌ Lost on navigation
|
|
// ❌ Stream stops
|
|
// ❌ Progress lost
|
|
```
|
|
|
|
### After (Hook State)
|
|
```typescript
|
|
// In usePostEditor.ts
|
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
const [streamingContent, setStreamingContent] = useState('');
|
|
|
|
// ✅ Persists across navigation
|
|
// ✅ Stream continues
|
|
// ✅ Progress preserved
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Test Case 1: Basic Persistence
|
|
1. Start generation
|
|
2. Wait 5 seconds (partial content)
|
|
3. Navigate to Assets
|
|
4. Navigate back to Generate
|
|
5. **Expected**: See partial content, stream continuing
|
|
|
|
### Test Case 2: Complete During Navigation
|
|
1. Start generation
|
|
2. Navigate away immediately
|
|
3. Wait 60 seconds
|
|
4. Navigate back to Generate
|
|
5. **Expected**: See complete content
|
|
|
|
### Test Case 3: Error Handling
|
|
1. Disconnect network
|
|
2. Start generation
|
|
3. Navigate away
|
|
4. Navigate back
|
|
5. **Expected**: See error message
|
|
|
|
## Future Enhancements
|
|
|
|
### 1. **Visual Indicator**
|
|
Show streaming status in step navigation:
|
|
```
|
|
Step 2: Generate ⚡ (Streaming...)
|
|
```
|
|
|
|
### 2. **Notification on Complete**
|
|
Toast notification when generation completes while on another step:
|
|
```
|
|
✅ Article generation complete! (2,456 tokens)
|
|
```
|
|
|
|
### 3. **Progress in Sidebar**
|
|
Show live progress in sidebar:
|
|
```
|
|
┌─────────────────────┐
|
|
│ AI Generation │
|
|
│ ▓▓▓▓▓▓▓░░░░░░░░░ │
|
|
│ 1,234 tokens │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
### 4. **Pause/Resume**
|
|
Add ability to pause streaming:
|
|
```typescript
|
|
const [isPaused, setIsPaused] = useState(false);
|
|
// Pause SSE consumption, resume later
|
|
```
|
|
|
|
## Conclusion
|
|
|
|
The streaming persistence feature provides a seamless, flexible workflow where users can multitask during long AI generations without losing progress. The implementation is clean, using React's built-in state management patterns and requiring minimal changes to the existing codebase.
|
|
|
|
**Status**: ✅ Fully implemented and tested
|
|
**Impact**: Significantly improved UX for long-running generations
|
|
**Complexity**: Low (simple state lifting pattern)
|