@agentman/chat-widget
Version:
Agentman Chat Widget for easy integration with web applications
692 lines (525 loc) ⢠17.5 kB
Markdown
# ChatWidget.ts Refactoring Plan
## Current State Analysis
**File:** `assistantWidget/ChatWidget.ts`
**Lines:** 2,688 lines
**Properties/Methods:** 108 total
### Problem Statement
ChatWidget.ts has grown too large and violates the Single Responsibility Principle. Despite previous refactoring efforts, it still contains ~2,688 lines of mixed concerns including:
- Widget orchestration (appropriate)
- UI component creation (should be extracted)
- Animation logic (should be extracted)
- State management for various UI modes (should be extracted)
- Agent initialization (should be extracted)
- Layout/positioning logic (should be extracted)
### Biggest Offenders
| Method | Lines | Purpose | Should Move To |
|--------|-------|---------|----------------|
| `showWelcomeCard()` | 246 | Create floating welcome card with animations | WelcomeCardComponent |
| `showInputBar()` | 205 | Create floating input bar with typewriter effects | InputBarComponent |
| `showFloatingPrompts()` | 158 | Create floating prompt bubbles | FloatingPromptsComponent |
| `createWidget()` | 92 | Widget initialization | Keep (orchestration) |
| `handleToggleListView()` | 89 | Conversation list toggle | ConversationManager |
| `emitToggleEvent()` | 86 | Toggle button handler | Keep (orchestration) |
| `dismissWelcomeCard()` | 83 | Welcome card dismissal + animation | WelcomeCardComponent |
**Total in top 7 methods:** 959 lines (36% of file!)
## What We've Already Extracted ā
Previous refactoring efforts have successfully extracted:
1. **InputBarFullController** (created today)
- Sheet mode expand/collapse logic
- Focus management
- State machine for transitions
- Accessibility features
2. **ViewManager**
- Main widget UI orchestration
- Toggle button creation
- View transitions (welcome ā conversation)
3. **ConversationManager**
- Conversation list management
- Conversation switching
4. **Handlers**
- MessageHandler - Message rendering and streaming
- FileHandler - File upload and attachment management
- ErrorHandler - Error handling and display
- ConversationOrchestrator - Conversation flow coordination
5. **Services**
- ApiService - API communication
- MessageService - Message processing
- AgentService - Agent-related operations
## What Still Needs Extraction ā
### 1. Closed View UI Components (~550 lines)
**Problem:** Three massive methods create DOM for "widget closed" modes:
- `showWelcomeCard()` - 246 lines
- `showInputBar()` - 205 lines
- `showFloatingPrompts()` - 158 lines
Plus supporting methods:
- `dismissWelcomeCard()` - 83 lines
- `hideFloatingPrompts()` - ~30 lines
- `hideInputBar()` - ~20 lines
- `startTypewriterEffect()` - ~40 lines
- `startTypewriterEffectCycling()` - ~50 lines
**State being managed:**
```typescript
private welcomeCardStateMap = new WeakMap<HTMLElement, WelcomeCardState>();
private isWelcomeCardAnimating: boolean = false;
private activeWelcomeCard: HTMLElement | null = null;
private inputBarCleanup: (() => void)[] = [];
private inputBarTypewriterCleanup: (() => void) | null = null;
```
### 2. Agent Initialization (~150 lines)
**Problem:** Agent capability detection mixed with widget logic:
- `initializeAgent()` - ~70 lines
- `processAgentCapabilities()` - ~40 lines
- Supporting logic - ~40 lines
**State being managed:**
```typescript
private supportedMimeTypes: string[] = [];
private supportsAttachments: boolean = false;
private agentCapabilities: any = null;
private isInitializingAgent: boolean = false;
```
### 3. Conversation Restoration (~100 lines)
**Problem:** Persistence logic embedded in widget:
- `restoreCurrentConversation()` - ~46 lines
- `setupConversationManagement()` - ~55 lines
### 4. Layout & Positioning (~150 lines)
**Problem:** Responsive layout logic:
- `updateResponsiveLayout()` - ~51 lines
- `applyPositionToWidget()` - ~73 lines
- `applyWelcomeCardPosition()` - ~31 lines
**State being managed:**
```typescript
private resizeTimeout: number | null = null;
```
### 5. Miscellaneous (~100 lines)
- Conversation list view toggle logic
- Theme update coordination
- Various cleanup handlers
---
## Refactoring Plan
### Phase 1: Extract ClosedViewManager šÆ **HIGHEST PRIORITY**
**Impact:** Remove ~550 lines from ChatWidget.ts
#### 1.1 Create ClosedViewManager
**File:** `assistantWidget/controllers/ClosedViewManager.ts`
**Responsibilities:**
- Determine which closed view mode to show
- Orchestrate creation of welcome card, input bar, or floating prompts
- Handle dismissal and cleanup
- Manage show/hide animations
**Public API:**
```typescript
export class ClosedViewManager {
constructor(
config: ChatConfig,
theme: ChatTheme,
assets: ChatAssets,
onPromptClick: (prompt: string) => void,
onOpen: () => void
)
// Show appropriate closed view based on config
show(): void
// Hide all closed views
hide(): void
// Clean up and remove from DOM
destroy(): void
// Check if any closed view is currently visible
isVisible(): boolean
}
```
#### 1.2 Create WelcomeCardComponent
**File:** `assistantWidget/components/WelcomeCardComponent.ts`
**Responsibilities:**
- Create welcome card DOM structure
- Handle card animations (show/dismiss)
- Manage card positioning
- Handle user interactions (prompts, minimize)
**Public API:**
```typescript
export class WelcomeCardComponent {
constructor(
config: ChatConfig,
theme: ChatTheme,
assets: ChatAssets,
prompts: string[],
onPromptClick: (prompt: string) => void,
onMinimize: () => void
)
// Create and show the card
show(): void
// Animate card dismissal
dismiss(): Promise<void>
// Remove from DOM
destroy(): void
// Get card position for widget opening
getPosition(): { x: number; y: number }
}
```
#### 1.3 Create InputBarComponent
**File:** `assistantWidget/components/InputBarComponent.ts`
**Responsibilities:**
- Create input bar DOM structure
- Handle typewriter effect animations
- Manage prompt cycling
- Handle user interactions
**Public API:**
```typescript
export class InputBarComponent {
constructor(
config: ChatConfig,
theme: ChatTheme,
assets: ChatAssets,
prompts: string[],
onPromptClick: (prompt: string) => void,
onOpen: () => void
)
show(): void
hide(): void
destroy(): void
stopTypewriter(): void
}
```
#### 1.4 Create FloatingPromptsComponent
**File:** `assistantWidget/components/FloatingPromptsComponent.ts`
**Responsibilities:**
- Create floating prompts DOM structure
- Handle dismissal behavior
- Manage prompt click events
**Public API:**
```typescript
export class FloatingPromptsComponent {
constructor(
config: ChatConfig,
theme: ChatTheme,
prompts: string[],
onPromptClick: (prompt: string) => void
)
show(): void
dismiss(): void
destroy(): void
}
```
#### 1.5 Update ChatWidget.ts
**Remove:**
- `showWelcomeCard()` (246 lines)
- `showInputBar()` (205 lines)
- `showFloatingPrompts()` (158 lines)
- `dismissWelcomeCard()` (83 lines)
- `hideInputBar()` (~20 lines)
- `hideFloatingPrompts()` (~30 lines)
- `startTypewriterEffect()` (~40 lines)
- `startTypewriterEffectCycling()` (~50 lines)
- All related state properties
**Add:**
```typescript
private closedViewManager: ClosedViewManager | null = null;
// In createWidget():
if (this.config.variant === 'corner' && !this.config.initiallyOpen) {
this.closedViewManager = new ClosedViewManager(
this.config,
this.theme,
this.assets,
(prompt) => this.handlePromptClick(prompt),
() => this.handleOpen()
);
const delay = this.config.agentClosedView === 'input-bar' ||
this.isInputBarFullMode ? 0 : 5000;
setTimeout(() => {
this.closedViewManager?.show();
}, delay);
}
// In destroy():
this.closedViewManager?.destroy();
```
**Result:** ChatWidget.ts drops from 2,688 ā **~2,100 lines** (22% reduction)
---
### Phase 2: Extract AgentInitializer
**Impact:** Remove ~150 lines from ChatWidget.ts
#### 2.1 Create AgentInitializer
**File:** `assistantWidget/handlers/AgentInitializer.ts`
**Responsibilities:**
- Initialize agent and fetch capabilities
- Process agent metadata
- Extract supported MIME types
- Determine attachment support
**Public API:**
```typescript
export class AgentInitializer {
constructor(
apiService: ApiService,
agentService: AgentService,
config: ChatConfig
)
async initialize(): Promise<AgentCapabilities>
getCapabilities(): AgentCapabilities
getSupportedMimeTypes(): string[]
supportsAttachments(): boolean
}
export interface AgentCapabilities {
supportedMimeTypes: string[];
supportsAttachments: boolean;
metadata: any;
}
```
#### 2.2 Update ChatWidget.ts
**Remove:**
- `initializeAgent()` (~70 lines)
- `processAgentCapabilities()` (~40 lines)
- Related state properties
**Add:**
```typescript
private agentInitializer: AgentInitializer;
// In constructor:
this.agentInitializer = new AgentInitializer(
this.apiService,
this.agentService,
this.config
);
// In createWidget():
this.agentInitializer.initialize().then(capabilities => {
this.updateFileHandlerCapabilities(capabilities);
});
```
**Result:** ChatWidget.ts drops from ~2,100 ā **~1,950 lines**
---
### Phase 3: Extract ConversationRestorer
**Impact:** Remove ~100 lines from ChatWidget.ts
#### 3.1 Create ConversationRestorer
**File:** `assistantWidget/handlers/ConversationRestorer.ts`
**Responsibilities:**
- Restore conversation from persistence
- Setup conversation management
- Handle conversation switching
**Public API:**
```typescript
export class ConversationRestorer {
constructor(
persistenceManager: PersistenceManager,
conversationManager: ConversationManager,
messageHandler: MessageHandler
)
async restoreCurrentConversation(): Promise<void>
setupConversationManagement(): void
}
```
#### 3.2 Update ChatWidget.ts
**Remove:**
- `restoreCurrentConversation()` (~46 lines)
- `setupConversationManagement()` (~55 lines)
**Add:**
```typescript
private conversationRestorer: ConversationRestorer | null = null;
// In createWidget():
if (this.persistenceManager) {
this.conversationRestorer = new ConversationRestorer(
this.persistenceManager,
this.conversationManager,
this.messageHandler
);
this.conversationRestorer.setupConversationManagement();
}
```
**Result:** ChatWidget.ts drops from ~1,950 ā **~1,850 lines**
---
### Phase 4: Extract LayoutManager
**Impact:** Remove ~150 lines from ChatWidget.ts
#### 4.1 Create LayoutManager
**File:** `assistantWidget/managers/LayoutManager.ts`
**Responsibilities:**
- Handle responsive layout updates
- Manage widget positioning
- Handle welcome card positioning
- Debounce resize events
**Public API:**
```typescript
export class LayoutManager {
constructor(
element: HTMLElement,
config: ChatConfig,
variant: string
)
updateResponsiveLayout(): void
applyPositionToWidget(): void
applyWelcomeCardPosition(card: HTMLElement): void
handleResize(): void
destroy(): void
}
```
#### 4.2 Update ChatWidget.ts
**Remove:**
- `updateResponsiveLayout()` (~51 lines)
- `applyPositionToWidget()` (~73 lines)
- `applyWelcomeCardPosition()` (~31 lines)
- `resizeTimeout` property
**Add:**
```typescript
private layoutManager: LayoutManager;
// In constructor:
this.layoutManager = new LayoutManager(
this.element,
this.config,
this.config.variant
);
// In setupEventListeners():
window.addEventListener('resize', () => this.layoutManager.handleResize());
```
**Result:** ChatWidget.ts drops from ~1,850 ā **~1,700 lines**
---
### Phase 5: Clean Up & Optimize
**Impact:** Remove ~900 lines from ChatWidget.ts
#### Move Remaining Logic to Existing Managers
1. **Conversation List Toggle ā ConversationManager**
- Move `handleToggleListView()` (89 lines)
2. **Theme Management ā StyleManager**
- Consolidate theme update logic
3. **UI Updates ā ViewManager/UIManager**
- Move remaining UI coordination
4. **Event Handling Cleanup**
- Remove redundant event handlers
- Consolidate event subscriptions
**Result:** ChatWidget.ts drops from ~1,700 ā **~800 lines**
---
## Final Architecture
### ChatWidget.ts (~800 lines)
**Responsibilities (ONLY):**
- ā
Initialize configuration and managers
- ā
Orchestrate component lifecycle
- ā
Coordinate between managers/controllers
- ā
Provide public API methods
- ā
Handle EventBus subscriptions
- ā
Manage widget destroy/cleanup
**What it does NOT do:**
- ā Create DOM directly
- ā Manage animations
- ā Handle positioning/layout
- ā Process agent capabilities
- ā Restore conversations
- ā Create floating UI components
### New Component Tree
```
ChatWidget (800 lines - orchestrator only)
ā
āāā Configuration
ā āāā ConfigManager
ā āāā StateManager
ā
āāā Managers
ā āāā StyleManager
ā āāā PersistenceManager
ā āāā LayoutManager ā NEW
ā
āāā Controllers
ā āāā ClosedViewManager ā NEW
ā ā āāā WelcomeCardComponent ā NEW
ā ā āāā InputBarComponent ā NEW
ā ā āāā FloatingPromptsComponent ā NEW
ā āāā InputBarFullController ā
DONE
ā āāā SheetStateMachine ā
DONE
ā
āāā Components
ā āāā ViewManager
ā āāā ConversationManager
ā āāā WelcomeScreen
ā āāā ConversationView
ā
āāā Handlers
ā āāā AgentInitializer ā NEW
ā āāā ConversationRestorer ā NEW
ā āāā MessageHandler
ā āāā FileHandler
ā āāā ErrorHandler
ā āāā ConversationOrchestrator
ā
āāā Services
āāā ApiService
āāā MessageService
āāā AgentService
```
---
## Implementation Timeline
### Phase 1: ClosedViewManager (Highest Priority) ā±ļø 2-3 hours
- **Day 1 Morning:** Create ClosedViewManager + WelcomeCardComponent
- **Day 1 Afternoon:** Create InputBarComponent + FloatingPromptsComponent
- **Day 1 Evening:** Integrate with ChatWidget.ts, test all modes
**Deliverable:** 550 lines removed, all closed view modes working
### Phase 2: AgentInitializer ā±ļø 1 hour
- **Day 2 Morning:** Create AgentInitializer, integrate, test
**Deliverable:** 150 lines removed, agent initialization isolated
### Phase 3: ConversationRestorer ā±ļø 1 hour
- **Day 2 Afternoon:** Create ConversationRestorer, integrate, test
**Deliverable:** 100 lines removed, conversation restoration isolated
### Phase 4: LayoutManager ā±ļø 1 hour
- **Day 2 Afternoon:** Create LayoutManager, integrate, test
**Deliverable:** 150 lines removed, layout logic isolated
### Phase 5: Clean Up & Optimize ā±ļø 1 hour
- **Day 2 Evening:** Move remaining logic, final cleanup
**Deliverable:** 900 lines removed, ChatWidget.ts at ~800 lines
**Total Time:** 4-6 hours of focused refactoring
---
## Testing Strategy
### Per-Phase Testing
After each phase, verify:
1. **Visual Testing:**
- All UI modes render correctly
- Animations work smoothly
- Responsive behavior intact
2. **Functional Testing:**
- User interactions work (clicks, typing, etc.)
- State transitions correct
- Event handlers firing properly
3. **Integration Testing:**
- Components communicate correctly
- EventBus messages flowing
- No memory leaks
### Full Regression Testing
After all phases:
1. Test all variants (corner, centered, inline, input-bar-sheet)
2. Test all closed view modes (welcome-card, input-bar, floating-prompts, toggle-only)
3. Test conversation flows
4. Test file uploads
5. Test mobile responsive behavior
6. Test accessibility features
---
## Success Metrics
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| ChatWidget.ts lines | 2,688 | ~800 | -70% |
| Largest method | 246 lines | <50 lines | -80% |
| Total methods | 108 | ~40 | -63% |
| Testability | Low | High | ā
|
| Maintainability | Low | High | ā
|
| Code reusability | Low | High | ā
|
---
## Risk Assessment
### Low Risk ā
- **Phase 2-4:** Small, well-contained extractions
- Clear interfaces, minimal dependencies
### Medium Risk ā ļø
- **Phase 1:** Large extraction affecting multiple modes
- Requires careful testing of all closed view modes
- Animation timing must be preserved
### Mitigation Strategy
1. **Start with Phase 1** - Biggest impact, sets pattern
2. **Test thoroughly** after each phase
3. **Keep git commits clean** - One phase per commit
4. **Maintain backward compatibility** - Don't break public API
---
## Next Steps
1. ā
Review this plan with team
2. ā³ Get approval to proceed
3. ā³ Start Phase 1: ClosedViewManager extraction
4. ā³ Iterate through phases 2-5
5. ā³ Comprehensive testing
6. ā³ Deploy and monitor
---
## Questions to Answer Before Starting
1. **Should we maintain backward compatibility with existing public API?**
- Answer: Yes, only internal refactoring
2. **Do we need to support both old and new architecture during transition?**
- Answer: No, one clean cut per phase
3. **What's the testing requirements before each phase can be considered done?**
- Answer: Visual + functional + integration tests pass
4. **Should we create this as a new major version or maintain current version?**
- Answer: Maintain current version, internal refactoring only
---
*Document created: 2025-01-09*
*Last updated: 2025-01-09*
*Status: š” Awaiting Approval*