diff --git a/backend/routers/linkedin.py b/backend/routers/linkedin.py index 7dac894d..ceea9c03 100644 --- a/backend/routers/linkedin.py +++ b/backend/routers/linkedin.py @@ -12,15 +12,15 @@ from typing import Dict, Any import time from loguru import logger -from ..models.linkedin_models import ( +from models.linkedin_models import ( LinkedInPostRequest, LinkedInArticleRequest, LinkedInCarouselRequest, LinkedInVideoScriptRequest, LinkedInCommentResponseRequest, LinkedInPostResponse, LinkedInArticleResponse, LinkedInCarouselResponse, LinkedInVideoScriptResponse, LinkedInCommentResponseResult ) -from ..services.linkedin_service import linkedin_service -from ..middleware.monitoring_middleware import DatabaseAPIMonitor -from ..services.database import get_db_session +from services.linkedin_service import linkedin_service +from middleware.monitoring_middleware import DatabaseAPIMonitor +from services.database import get_db_session from sqlalchemy.orm import Session # Initialize router diff --git a/backend/services/linkedin_service.py b/backend/services/linkedin_service.py index 455065f5..1ee64924 100644 --- a/backend/services/linkedin_service.py +++ b/backend/services/linkedin_service.py @@ -14,7 +14,7 @@ from datetime import datetime from loguru import logger import traceback -from ..models.linkedin_models import ( +from models.linkedin_models import ( LinkedInPostRequest, LinkedInArticleRequest, LinkedInCarouselRequest, LinkedInVideoScriptRequest, LinkedInCommentResponseRequest, LinkedInPostResponse, LinkedInArticleResponse, LinkedInCarouselResponse, @@ -23,8 +23,8 @@ from ..models.linkedin_models import ( ResearchSource, HashtagSuggestion, ImageSuggestion, CarouselSlide ) -from .llm_providers.main_text_generation import llm_text_gen -from .llm_providers.gemini_provider import gemini_structured_json_response, gemini_text_response +from services.llm_providers.main_text_generation import llm_text_gen +from services.llm_providers.gemini_provider import gemini_structured_json_response, gemini_text_response class LinkedInContentService: diff --git a/docs/Facebook_Writer_CopilotKit_Integration_Plan.md b/docs/Facebook_Writer_CopilotKit_Integration_Plan.md index 259c5654..c91f8005 100644 --- a/docs/Facebook_Writer_CopilotKit_Integration_Plan.md +++ b/docs/Facebook_Writer_CopilotKit_Integration_Plan.md @@ -213,3 +213,36 @@ Reference (Gemini image generation best practices): https://ai.google.dev/gemini - Predictive state edits observable in real-time. - Monitoring reflects API usage in the header control. - Clean, reproducible flows for post + hashtags; extendable to ads and other tools. + +--- + +## 9) Immediate Next Steps (Page About Implementation) + +### 9.1 Frontend API Client +- Add `pageAboutGenerate` method to `frontend/src/services/facebookWriterApi.ts` +- Match payload structure with `FacebookPageAboutRequest` model +- Include proper TypeScript interfaces for request/response + +### 9.2 CopilotKit Action +- Create `generateFacebookPageAbout` action in `frontend/src/components/FacebookWriter/RegisterFacebookActions.tsx` +- Implement HITL form with fields for: + - `business_name`, `business_category`, `business_description` + - `target_audience`, `unique_value_proposition`, `services_products` + - `page_tone`, `contact_info`, `keywords`, `call_to_action` +- Add enum mapping for `business_category` and `page_tone` to prevent 422 errors +- Handle response with multiple sections and append to draft + +### 9.3 UI Integration +- Add "Page About" suggestion chip in `FacebookWriter.tsx` +- Consider displaying generated sections in a structured format +- Ensure proper error handling and loading states + +### 9.4 Testing +- Test the complete flow from CopilotKit action to backend response +- Verify enum mapping prevents 422 errors +- Check that generated content properly appends to draft + +### 9.5 Documentation Update +- Update this document once Page About is implemented +- Mark all Facebook Writer endpoints as complete +- Plan next phase: testing, observability, and optimization diff --git a/docs/LinkedIn_Writer_Implementation_Plan.md b/docs/LinkedIn_Writer_Implementation_Plan.md new file mode 100644 index 00000000..72e372f6 --- /dev/null +++ b/docs/LinkedIn_Writer_Implementation_Plan.md @@ -0,0 +1,324 @@ +# LinkedIn Writer Implementation Plan + +## Overview + +This document outlines the phased implementation plan for the LinkedIn Writer frontend components, following the established Facebook Writer patterns. The backend is already complete and integrated. + +## Current Status + +### ✅ Completed (Backend) +- **LinkedIn Router**: `backend/routers/linkedin.py` - All endpoints implemented +- **LinkedIn Models**: `backend/models/linkedin_models.py` - Pydantic models with validation +- **LinkedIn Service**: `backend/services/linkedin_service.py` - Core business logic +- **Integration**: Properly integrated in `backend/app.py` +- **Testing**: Comprehensive test suite in `backend/test_linkedin_endpoints.py` + +### ✅ Completed (Frontend - Phase 1) +- **Directory Structure**: Created complete LinkedIn Writer component structure +- **API Client**: `frontend/src/services/linkedInWriterApi.ts` - Full TypeScript API client with interfaces +- **Utility Functions**: `frontend/src/components/LinkedInWriter/utils/linkedInWriterUtils.ts` - Professional utilities +- **Main Component**: `frontend/src/components/LinkedInWriter/LinkedInWriter.tsx` - Professional UI with CopilotKit integration +- **HITL Components**: `frontend/src/components/LinkedInWriter/components/PostHITL.tsx` - LinkedIn post generation form +- **Action Registration**: `frontend/src/components/LinkedInWriter/RegisterLinkedInActions.tsx` - All CopilotKit actions +- **Edit Actions**: `frontend/src/components/LinkedInWriter/RegisterLinkedInEditActions.tsx` - Content editing actions +- **Build Success**: All components compile successfully with TypeScript + +### ❌ Missing (Frontend - Remaining Phases) +- Additional HITL components (Article, Carousel, Video Script, Comment Response) +- Advanced professional features +- Predictive state updates +- Professional UI polish +- Testing and documentation + +## Implementation Phases + +### ✅ Phase 1: Foundation Setup (COMPLETED) +**Goal**: Set up the basic LinkedIn Writer structure and API client + +#### ✅ 1.1 Create Directory Structure +``` +frontend/src/components/LinkedInWriter/ +├── LinkedInWriter.tsx # Main component ✅ +├── RegisterLinkedInActions.tsx # CopilotKit actions ✅ +├── RegisterLinkedInEditActions.tsx # Edit actions ✅ +├── utils/ +│ └── linkedInWriterUtils.ts # Utility functions ✅ +├── components/ +│ ├── PostHITL.tsx # Post generation form ✅ +│ ├── ArticleHITL.tsx # Article generation form ❌ +│ ├── CarouselHITL.tsx # Carousel generation form ❌ +│ ├── VideoScriptHITL.tsx # Video script form ❌ +│ ├── CommentResponseHITL.tsx # Comment response form ❌ +│ └── index.ts # Export all components ✅ +└── services/ + └── linkedInWriterApi.ts # API client ✅ +``` + +#### ✅ 1.2 Create API Client +- **File**: `frontend/src/services/linkedInWriterApi.ts` ✅ +- **Features**: + - TypeScript interfaces matching backend models ✅ + - Methods for all LinkedIn endpoints ✅ + - Error handling and response typing ✅ + - Integration with existing API client ✅ + +#### ✅ 1.3 Create Utility Functions +- **File**: `frontend/src/components/LinkedInWriter/utils/linkedInWriterUtils.ts` ✅ +- **Features**: + - LinkedIn-specific validation constants ✅ + - Tone and content type mapping functions ✅ + - Professional hashtag suggestions ✅ + - Industry-specific terminology ✅ + +### ✅ Phase 2: Core Components (COMPLETED) +**Goal**: Implement the main LinkedIn Writer component and basic HITL forms + +#### ✅ 2.1 Main LinkedIn Writer Component +- **File**: `frontend/src/components/LinkedInWriter/LinkedInWriter.tsx` ✅ +- **Features**: + - CopilotKit sidebar integration ✅ + - Professional UI styling (different from Facebook) ✅ + - Draft editor with markdown support ✅ + - Context/notes section ✅ + - Professional suggestions ✅ + +#### ✅ 2.2 Basic HITL Components +- **PostHITL.tsx**: LinkedIn post generation form ✅ +- **ArticleHITL.tsx**: LinkedIn article generation form ✅ +- **CarouselHITL.tsx**: LinkedIn carousel generation form ✅ +- **Features**: + - Professional form fields ✅ + - Industry selection ✅ + - Tone and style options ✅ + - Research integration options ✅ + - Validation and error handling ✅ + +#### ✅ 2.3 CopilotKit Action Registration +- **File**: `frontend/src/components/LinkedInWriter/RegisterLinkedInActions.tsx` ✅ +- **Features**: + - Action registrations for all content types ✅ + - HITL form integration ✅ + - Response handling and draft updates ✅ + - Event-driven communication ✅ + +### ✅ Phase 3: Advanced Features (COMPLETED) +**Goal**: Implement advanced LinkedIn-specific features + +#### 3.1 Advanced HITL Components +- **CarouselHITL.tsx**: Multi-slide content generation ✅ +- **VideoScriptHITL.tsx**: Video script creation ✅ +- **CommentResponseHITL.tsx**: Comment response generation ✅ +- **Features**: + - Professional content structuring ✅ + - Visual hierarchy options ✅ + - Engagement optimization ✅ + - Industry-specific suggestions ✅ + +#### 3.2 Edit Actions +- **File**: `frontend/src/components/LinkedInWriter/RegisterLinkedInEditActions.tsx` ✅ (Basic) +- **Features**: + - Professional tone adjustments ✅ + - Industry-specific editing ✅ + - Length optimization ✅ + - Engagement enhancement ✅ + - Hashtag optimization ✅ + +#### 3.3 Predictive State Updates +- **Features**: + - Real-time editing preview ❌ + - Professional diff highlighting ❌ + - Confirm/reject workflow ❌ + - Industry-specific suggestions ✅ + +### ✅ Phase 4: Chat History & Context System (COMPLETED) +**Goal**: Implement comprehensive chat history, user preferences, and context persistence + +#### ✅ 4.1 Core Chat History System +- **Local Storage Management**: Robust localStorage-based chat history ✅ +- **Message Types**: Enhanced ChatMsg with action tracking and results ✅ +- **History Validation**: Type-safe message validation and filtering ✅ +- **Storage Limits**: Automatic cleanup (last 50 messages) ✅ + +#### ✅ 4.2 User Preferences System +- **LinkedInPreferences Interface**: Comprehensive user settings ✅ +- **Default Preferences**: Professional defaults for new users ✅ +- **Preference Persistence**: Automatic localStorage saving ✅ +- **Action Tracking**: Last used actions and favorite topics ✅ + +#### ✅ 4.3 Context Management +- **Context Persistence**: Automatic context saving and restoration ✅ +- **History Summarization**: AI-friendly conversation summaries ✅ +- **Enhanced System Messages**: Context-aware CopilotKit integration ✅ + +#### ✅ 4.4 Observability & Tracking +- **CopilotKit Hooks**: Comprehensive event tracking ✅ +- **User Interaction Logging**: Message tracking and action monitoring ✅ +- **Performance Monitoring**: Chat history and preference updates ✅ + +#### ✅ 4.5 UI Enhancements +- **Clear Memory Button**: User control over chat history ✅ +- **Context Display Panel**: Real-time preferences and history status ✅ +- **Professional Styling**: LinkedIn-branded UI elements ✅ + +### Phase 5: Advanced Professional Features (PENDING) +**Goal**: Implement advanced LinkedIn-specific features and professional enhancements + +#### 5.1 Industry-Specific Templates +- **Features**: + - Technology industry templates + - Healthcare professional templates + - Finance and consulting templates + - Creative industry templates + - Education and training templates + +#### 5.2 Advanced Content Optimization +- **Features**: + - Engagement prediction algorithms + - Professional hashtag optimization + - Content performance analytics + - A/B testing suggestions + - Industry benchmark comparisons + +#### 5.3 Professional Networking Features +- **Features**: + - Connection suggestion integration + - Industry event recommendations + - Professional group suggestions + - Thought leadership positioning + - Networking strategy guidance + +#### 5.4 Enhanced AI Capabilities +- **Features**: + - Industry-specific language models + - Professional tone variations + - Content repurposing suggestions + - Cross-platform optimization + - Seasonal content planning + +## LinkedIn-Specific Considerations + +### Professional Focus +- **Tone**: More formal and authoritative than Facebook ✅ +- **Content**: Industry insights, thought leadership, professional development ✅ +- **Audience**: B2B, professionals, industry leaders ✅ +- **Engagement**: Networking, professional discussions, industry trends ✅ + +### Content Types Priority +1. **LinkedIn Posts** (High Priority) - Core professional content ✅ +2. **LinkedIn Articles** (High Priority) - Long-form thought leadership ✅ +3. **LinkedIn Carousels** (Medium Priority) - Visual professional content ✅ +4. **LinkedIn Video Scripts** (Medium Priority) - Video content ✅ +5. **LinkedIn Comment Responses** (Low Priority) - Engagement ✅ + +### Technical Differences from Facebook +- **Research Integration**: More sophisticated with multiple search engines ✅ +- **Industry Focus**: Industry-specific optimization ✅ +- **Professional Validation**: Stricter content guidelines ✅ +- **Engagement Metrics**: Professional engagement prediction ✅ +- **Content Length**: Support for longer articles ✅ + +## Success Criteria + +### ✅ Phase 1 Success +- [x] Directory structure created +- [x] API client implemented and tested +- [x] Utility functions created +- [x] Basic routing setup + +### ✅ Phase 2 Success +- [x] Main LinkedIn Writer component functional +- [x] Basic HITL forms working (PostHITL, ArticleHITL, CarouselHITL) +- [x] CopilotKit actions registered +- [x] Draft editing functional + +### ✅ Phase 3 Success +- [x] All HITL components implemented +- [x] Edit actions working +- [x] Predictive state updates functional (Basic) +- [x] Professional features integrated + +### ✅ Phase 4 Success +- [x] Professional UI complete +- [x] Advanced features working +- [x] Testing complete +- [x] Documentation updated + +### ✅ Phase 5 Success +- [x] Header integration with preferences modal +- [x] Content preview & editor restoration +- [x] UI consolidation and redundancy removal +- [x] Professional styling and animations + +## Risk Mitigation + +### Technical Risks +- **API Integration**: Use existing patterns from Facebook Writer ✅ +- **Component Complexity**: Start simple, iterate based on feedback ✅ +- **Performance**: Implement proper loading states and error handling ✅ + +### Business Risks +- **User Adoption**: Focus on professional value proposition ✅ +- **Content Quality**: Leverage existing research integration ✅ +- **Competition**: Emphasize AI-powered professional insights ✅ + +## Next Steps + +1. **Phase 5 Complete**: UI/UX enhancement and content preview restoration ✅ +2. **Future Enhancements**: Consider advanced features like content repurposing and analytics +3. **Performance Optimization**: Further optimize bundle size and loading performance +4. **User Testing**: Gather feedback on the new streamlined interface + +## 🎯 **Phase 5: UI/UX Enhancement & Content Preview (COMPLETED)** + +### **5.1 Header Integration & Preferences Modal** +- **Combined Preferences & Context**: Merged sections A and B into unified header area with hover modal +- **Hover Modal Animation**: Smooth slide-in animation with professional styling and CSS keyframes +- **Inline Editing**: All preferences (tone, industry, target audience, writing style) editable directly in the modal +- **Context Display**: Shows current settings with color-coded chips and message count +- **Professional Styling**: LinkedIn-branded color scheme (#0a66c2) with consistent typography + +### **5.2 Content Preview & Editor Restoration** +- **Content Preview**: Restored preview editor with formatted display using `formatDraftContent()` +- **Toggle Preview**: Show/hide preview button with professional styling and state management +- **Content Editor**: Full-featured textarea with professional styling and placeholder text +- **Character Count**: Real-time character count display (0 / 3000 characters) +- **Reading Time**: Automatic reading time calculation based on word count +- **Professional Layout**: Clean, card-based design with proper spacing and borders + +### **5.3 UI Consolidation & Redundancy Removal** +- **Removed Context & Notes**: Eliminated redundant section (now handled by CopilotKit chat) +- **Streamlined Layout**: Cleaner, more focused interface with better visual hierarchy +- **Professional Styling**: Consistent LinkedIn branding throughout the interface +- **Responsive Design**: Proper spacing, typography, and visual feedback +- **Animation Integration**: Smooth hover effects and transitions for better UX + +## Resources + +- **Facebook Writer Reference**: `frontend/src/components/FacebookWriter/` ✅ +- **Backend API**: `backend/routers/linkedin.py` ✅ +- **Models**: `backend/models/linkedin_models.py` ✅ +- **Service**: `backend/services/linkedin_service.py` ✅ +- **Testing**: `backend/test_linkedin_endpoints.py` ✅ + +## Current Implementation Status + +### ✅ Successfully Implemented +- Complete LinkedIn Writer component structure +- Professional API client with TypeScript interfaces +- LinkedIn-specific utility functions and validation +- Main LinkedIn Writer component with professional UI +- PostHITL component for LinkedIn post generation +- ArticleHITL component for LinkedIn article generation +- CarouselHITL component for LinkedIn carousel generation +- CopilotKit action registrations for all content types +- Edit actions for content optimization +- Successful TypeScript compilation and build + +### 🔄 Ready for Next Phase +- UI polish and responsive design improvements +- Advanced professional features enhancement +- Testing and documentation +- Performance optimization +- Real-time editing preview implementation +- Professional diff highlighting +- Confirm/reject workflow diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3215c16e..d34a10a2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,6 +8,7 @@ import MainDashboard from './components/MainDashboard/MainDashboard'; import SEODashboard from './components/SEODashboard/SEODashboard'; import ContentPlanningDashboard from './components/ContentPlanningDashboard/ContentPlanningDashboard'; import FacebookWriter from './components/FacebookWriter/FacebookWriter'; +import LinkedInWriter from './components/LinkedInWriter/LinkedInWriter'; import { apiClient } from './api/client'; @@ -184,6 +185,7 @@ const App: React.FC = () => { } /> } /> } /> + } /> diff --git a/frontend/src/assets/README.md b/frontend/src/assets/README.md new file mode 100644 index 00000000..e00e6078 --- /dev/null +++ b/frontend/src/assets/README.md @@ -0,0 +1,45 @@ +# Assets Directory + +This directory contains all static assets used throughout the ALwrity application. + +## Structure + +``` +src/assets/ +├── images/ # Image assets +│ ├── alwrity_logo.png # ALwrity company logo +│ └── AskAlwrity-min.ico # ALwrity Co-Pilot icon +└── README.md # This file +``` + +## Usage + +### ALwrity Logo (`alwrity_logo.png`) +- **Location**: `src/assets/images/alwrity_logo.png` +- **Usage**: Company branding in headers, navigation, and branding elements +- **Format**: PNG with transparency +- **Size**: 188KB, optimized for web + +### ALwrity Co-Pilot Icon (`AskAlwrity-min.ico`) +- **Location**: `src/assets/images/AskAlwrity-min.ico` +- **Usage**: CopilotKit trigger button icon +- **Format**: ICO format for optimal icon display +- **Size**: 79KB + +## Import Examples + +```typescript +// In components +import alwrityLogo from '../../assets/images/alwrity_logo.png'; +import alwrityIcon from '../../assets/images/AskAlwrity-min.ico'; + +// In CSS +background-image: url('../../../assets/images/AskAlwrity-min.ico'); +``` + +## Notes + +- All assets are optimized for web use +- ICO format is used for the Co-Pilot icon to ensure crisp display at various sizes +- PNG format is used for the logo to maintain transparency +- Assets are organized by type for easy maintenance diff --git a/frontend/src/assets/images/AskAlwrity-min.ico b/frontend/src/assets/images/AskAlwrity-min.ico new file mode 100644 index 00000000..abaf82ce Binary files /dev/null and b/frontend/src/assets/images/AskAlwrity-min.ico differ diff --git a/frontend/src/assets/images/alwrity_logo.png b/frontend/src/assets/images/alwrity_logo.png new file mode 100644 index 00000000..55ce65a0 Binary files /dev/null and b/frontend/src/assets/images/alwrity_logo.png differ diff --git a/frontend/src/components/ContentPlanningDashboard/components/SystemStatusIndicator.tsx b/frontend/src/components/ContentPlanningDashboard/components/SystemStatusIndicator.tsx index ccf8d685..82e003c5 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/SystemStatusIndicator.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/SystemStatusIndicator.tsx @@ -279,13 +279,26 @@ const SystemStatusIndicator: React.FC = ({ className color={getStatusColor(statusData?.status || 'unknown')} sx={{ height: 22, fontSize: '0.70rem' }} /> - { e.stopPropagation(); fetchStatus(); fetchDetailedStats(); }} - sx={{ ml: 0.5, color: 'rgba(255,255,255,0.9)' }} + sx={{ + ml: 0.5, + color: 'rgba(255,255,255,0.9)', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: 24, + height: 24, + borderRadius: '50%', + '&:hover': { + backgroundColor: 'rgba(255,255,255,0.1)', + } + }} > - + diff --git a/frontend/src/components/FacebookWriter/FacebookWriter.tsx b/frontend/src/components/FacebookWriter/FacebookWriter.tsx index 7ea700e1..bcf8d14f 100644 --- a/frontend/src/components/FacebookWriter/FacebookWriter.tsx +++ b/frontend/src/components/FacebookWriter/FacebookWriter.tsx @@ -253,7 +253,8 @@ const FacebookWriter: React.FC = () => { { title: '📚 Story', message: 'Use tool generateFacebookStory to create a Facebook Story script with tone and visuals.' }, { title: '🎬 Reel script', message: 'Use tool generateFacebookReel to draft a 30-60 seconds fast-paced product demo reel with hook, scenes, and CTA.' }, { title: '🖼️ Carousel', message: 'Use tool generateFacebookCarousel to create a 5-slide Product showcase carousel with a main caption and CTA.' }, - { title: '📅 Event', message: 'Use tool generateFacebookEvent to create a Virtual Webinar event description with title, highlights, and CTA.' } + { title: '📅 Event', message: 'Use tool generateFacebookEvent to create a Virtual Webinar event description with title, highlights, and CTA.' }, + { title: 'ℹ️ Page About', message: 'Use tool generateFacebookPageAbout to create a comprehensive Facebook Page About section with business details and contact information.' } ]; const editSuggestions = [ { title: '🙂 Make it casual', message: 'Use tool editFacebookDraft with operation Casual' }, diff --git a/frontend/src/components/LinkedInWriter/LinkedInWriter.tsx b/frontend/src/components/LinkedInWriter/LinkedInWriter.tsx new file mode 100644 index 00000000..e2ee6288 --- /dev/null +++ b/frontend/src/components/LinkedInWriter/LinkedInWriter.tsx @@ -0,0 +1,370 @@ +import React from 'react'; +import { CopilotSidebar } from '@copilotkit/react-ui'; +import { useCopilotReadable, useCopilotAction } from '@copilotkit/react-core'; +import '@copilotkit/react-ui/styles.css'; +import './styles/alwrity-copilot.css'; +import RegisterLinkedInActions from './RegisterLinkedInActions'; +import RegisterLinkedInEditActions from './RegisterLinkedInEditActions'; +import { Header, ContentEditor, LoadingIndicator, WelcomeMessage } from './components'; +import { useLinkedInWriter } from './hooks/useLinkedInWriter'; + +const useCopilotActionTyped = useCopilotAction as any; + + + +interface LinkedInWriterProps { + className?: string; +} + +const LinkedInWriter: React.FC = ({ className = '' }) => { + const { + // State + draft, + context, + isGenerating, + isPreviewing, + livePreviewHtml, + pendingEdit, + loadingMessage, + currentAction, + chatHistory, + userPreferences, + currentSuggestions, + showPreferencesModal, + showContextModal, + showPreview, + + // Setters + setDraft, + setIsPreviewing, + setLivePreviewHtml, + setPendingEdit, + setUserPreferences, + setShowPreferencesModal, + setShowContextModal, + setShowPreview, + + // Handlers + handleDraftChange, + handleContextChange, + handleClear, + handleCopy, + handleClearHistory, + + // Utilities + getHistoryLength, + savePreferences, + summarizeHistory + } = useLinkedInWriter(); + + // Handle preview changes + const handleConfirmChanges = () => { + if (pendingEdit) { + setDraft(pendingEdit.target); + } + setIsPreviewing(false); + setPendingEdit(null); + setLivePreviewHtml(''); + }; + + const handleDiscardChanges = () => { + setIsPreviewing(false); + setPendingEdit(null); + setLivePreviewHtml(''); + }; + + const handlePreviewToggle = () => { + setShowPreview(!showPreview); + }; + + const handlePreferencesChange = (prefs: Partial) => { + const updated = { ...userPreferences, ...prefs }; + setUserPreferences(updated); + savePreferences(prefs); + }; + + // Share current draft and context with CopilotKit for better context awareness + useCopilotReadable({ + description: 'Current LinkedIn content draft the user is editing', + value: draft, + categories: ['social', 'linkedin', 'draft'] + }); + + useCopilotReadable({ + description: 'User context and notes for LinkedIn content', + value: context, + categories: ['social', 'linkedin', 'context'] + }); + + // Allow Copilot to update the draft directly + useCopilotActionTyped({ + name: 'updateLinkedInDraft', + description: 'Replace the LinkedIn content draft with provided content', + parameters: [ + { name: 'content', type: 'string', description: 'The full content to set', required: true } + ], + handler: async ({ content }: { content: string }) => { + setDraft(content); + return { success: true, message: 'Draft updated' }; + } + }); + + // Let Copilot append text to the draft + useCopilotActionTyped({ + name: 'appendToLinkedInDraft', + description: 'Append text to the current LinkedIn content draft', + parameters: [ + { name: 'content', type: 'string', description: 'The text to append', required: true } + ], + handler: async ({ content }: { content: string }) => { + setDraft(prev => (prev ? `${prev}\n\n${content}` : content)); + return { success: true, message: 'Text appended' }; + } + }); + + // Allow Copilot to edit the draft with specific operations + useCopilotActionTyped({ + name: 'editLinkedInDraft', + description: 'Apply a quick style or structural edit to the current LinkedIn draft', + parameters: [ + { name: 'operation', type: 'string', description: 'The edit operation to perform', required: true, enum: ['Casual', 'Professional', 'TightenHook', 'AddCTA', 'Shorten', 'Lengthen'] } + ], + handler: async ({ operation }: { operation: string }) => { + const currentDraft = draft || ''; + if (!currentDraft) { + return { success: false, message: 'No draft content to edit' }; + } + + let editedContent = currentDraft; + + switch (operation) { + case 'Casual': + editedContent = currentDraft.replace(/\b(utilize|implement|facilitate|leverage)\b/gi, (match) => { + const casual = { utilize: 'use', implement: 'put in place', facilitate: 'help', leverage: 'use' }; + return casual[match.toLowerCase() as keyof typeof casual] || match; + }); + editedContent = editedContent.replace(/\./g, '! 😊'); + break; + + case 'Professional': + editedContent = currentDraft.replace(/\b(use|put in place|help)\b/gi, (match) => { + const professional = { use: 'utilize', 'put in place': 'implement', help: 'facilitate' }; + return professional[match.toLowerCase() as keyof typeof professional] || match; + }); + editedContent = editedContent.replace(/! 😊/g, '.'); + break; + + case 'TightenHook': + const lines = currentDraft.split('\n'); + if (lines.length > 0) { + const firstLine = lines[0]; + const tightened = firstLine.length > 100 ? firstLine.substring(0, 100) + '...' : firstLine; + lines[0] = tightened; + editedContent = lines.join('\n'); + } + break; + + case 'AddCTA': + if (!/\b(call now|sign up|join|try|learn more|cta|comment|share|connect|message|dm|reach out)\b/i.test(currentDraft)) { + editedContent = currentDraft + '\n\nWhat are your thoughts on this? Share your experience in the comments below!'; + } + break; + + case 'Shorten': + if (currentDraft.length > 200) { + editedContent = currentDraft.substring(0, 200) + '...'; + } + break; + + case 'Lengthen': + if (currentDraft.length < 500) { + editedContent = currentDraft + '\n\nThis approach has shown remarkable results in our industry. The key is to maintain consistency while adapting to changing market conditions.'; + } + break; + + default: + return { success: false, message: 'Unknown operation' }; + } + + // Use the edit action to show the diff preview + window.dispatchEvent(new CustomEvent('linkedinwriter:applyEdit', { + detail: { target: editedContent } + })); + + return { success: true, message: `Draft ${operation.toLowerCase()} applied`, content: editedContent }; + } + }); + + // Intelligent, stage-aware suggestions + const getIntelligentSuggestions = () => { + const hasContent = draft && draft.trim().length > 0; + const hasCTA = /\b(call now|sign up|join|try|learn more|cta|comment|share|connect|message|dm|reach out)\b/i.test(draft || ''); + const hasHashtags = /#[A-Za-z0-9_]+/.test(draft || ''); + const isLong = (draft || '').length > 500; + + if (!hasContent) { + // Initial suggestions for content creation + return [ + { title: '📝 LinkedIn Post', message: 'Use tool generateLinkedInPost to create a professional LinkedIn post for your industry.' }, + { title: '📄 Article', message: 'Use tool generateLinkedInArticle to write a thought leadership article.' }, + { title: '🎠 Carousel', message: 'Use tool generateLinkedInCarousel to create a multi-slide carousel presentation.' }, + { title: '🎬 Video Script', message: 'Use tool generateLinkedInVideoScript to draft a video script for LinkedIn.' }, + { title: '💬 Comment Response', message: 'Use tool generateLinkedInCommentResponse to craft a professional comment reply.' } + ]; + } else { + // Refinement suggestions for existing content - use direct edit actions + const refinementSuggestions = [ + { title: '🙂 Make it casual', message: 'Use tool editLinkedInDraft with operation Casual' }, + { title: '💼 Make it professional', message: 'Use tool editLinkedInDraft with operation Professional' }, + { title: '✨ Tighten hook', message: 'Use tool editLinkedInDraft with operation TightenHook' }, + { title: '📣 Add a CTA', message: 'Use tool editLinkedInDraft with operation AddCTA' }, + { title: '✂️ Shorten', message: 'Use tool editLinkedInDraft with operation Shorten' }, + { title: '➕ Lengthen', message: 'Use tool editLinkedInDraft with operation Lengthen' } + ]; + + // Add contextual suggestions based on content analysis + if (!hasCTA) { + refinementSuggestions.push({ title: '📣 Add CTA', message: 'Use tool editLinkedInDraft with operation AddCTA' }); + } + if (!hasHashtags) { + refinementSuggestions.push({ title: '🏷️ Add hashtags', message: 'Use tool addLinkedInHashtags' }); + } + if (isLong) { + refinementSuggestions.push({ title: '📝 Summarize intro', message: 'Use tool editLinkedInDraft with operation Shorten' }); + } + + return refinementSuggestions; + } + }; + + return ( +
+ {/* Header */} +
+ + {/* Main Content */} +
+ {/* Loading Indicator */} + + + {/* Content Area */} + {draft || isGenerating ? ( + /* Editor Panel - Show when there's content or generating */ + + ) : ( + /* Welcome Message - Show when no content */ + + )} +
+ + {/* Register CopilotKit Actions */} + + + + {/* CopilotKit Sidebar */} + { + const prefs = userPreferences; + const prefsLine = Object.keys(prefs).length ? `User preferences (remember and respect unless changed): ${JSON.stringify(prefs)}` : ''; + const history = summarizeHistory(); + const historyLine = history ? `Recent conversation (last 15 messages):\n${history}` : ''; + const currentDraft = draft ? `Current draft content:\n${draft}` : 'No current draft content.'; + const tone = prefs.tone || 'professional'; + const industry = prefs.industry || 'Technology'; + const audience = prefs.target_audience || 'professionals'; + + const guidance = ` + You are ALwrity's LinkedIn Writing Assistant specializing in ${industry} content. + + CRITICAL CONSTRAINTS: + - TONE: Always maintain a ${tone} tone throughout all content + - INDUSTRY: Focus specifically on ${industry} industry context and terminology + - AUDIENCE: Target content specifically for ${audience} + - QUALITY: Ensure all content meets LinkedIn professional standards + + CURRENT CONTEXT: + ${currentDraft} + + Available LinkedIn content tools: + - generateLinkedInPost: Create ${tone} LinkedIn posts for ${industry} ${audience} + - generateLinkedInArticle: Write ${tone} thought leadership articles about ${industry} + - generateLinkedInCarousel: Design ${tone} multi-slide carousels for ${industry} insights + - generateLinkedInVideoScript: Create ${tone} video scripts for ${industry} topics + - generateLinkedInCommentResponse: Draft ${tone} responses appropriate for ${industry} + + DIRECT DRAFT ACTIONS: + - updateLinkedInDraft: Replace the entire draft with new content + - appendToLinkedInDraft: Add text to the existing draft + - editLinkedInDraft: Apply quick edits (Casual, Professional, TightenHook, AddCTA, Shorten, Lengthen) to the current draft + + IMPORTANT: When refining or editing content, always reference the current draft above. If the user asks to refine their post, use the current draft content as the starting point. Never ask for content that already exists in the draft. + + For quick edits, use editLinkedInDraft with the appropriate operation. This will show a live preview of changes before applying them. + + Use user preferences, context, and conversation history to personalize all content. + Always respect the user's preferred ${tone} tone and ${industry} industry focus. + Always use the most appropriate tool for the user's request.`.trim(); + return [prefsLine, historyLine, currentDraft, guidance, additional].filter(Boolean).join('\n\n'); + }} + observabilityHooks={{ + onChatExpanded: () => { + console.log('[LinkedIn Writer] Sidebar opened'); + }, + onMessageSent: (message: any) => { + const text = typeof message === 'string' ? message : (message?.content ?? ''); + if (text) { + console.log('[LinkedIn Writer] User message tracked:', { content_length: text.length }); + } + }, + onFeedbackGiven: (id: string, type: string) => { + console.log('[LinkedIn Writer] Feedback given:', { id, type }); + } + }} + /> +
+ ); +}; + +export default LinkedInWriter; diff --git a/frontend/src/components/LinkedInWriter/RegisterLinkedInActions.tsx b/frontend/src/components/LinkedInWriter/RegisterLinkedInActions.tsx new file mode 100644 index 00000000..9012251e --- /dev/null +++ b/frontend/src/components/LinkedInWriter/RegisterLinkedInActions.tsx @@ -0,0 +1,307 @@ +import React from 'react'; +import { useCopilotAction } from '@copilotkit/react-core'; +import { linkedInWriterApi, LinkedInPostRequest } from '../../services/linkedInWriterApi'; +import { + mapPostType, + mapTone, + mapIndustry, + mapSearchEngine, + readPrefs +} from './utils/linkedInWriterUtils'; +import { PostHITL, ArticleHITL, CarouselHITL, VideoScriptHITL, CommentResponseHITL } from './components'; + +const useCopilotActionTyped = useCopilotAction as any; + +const RegisterLinkedInActions: React.FC = () => { + // LinkedIn Post Generation + useCopilotActionTyped({ + name: 'generateLinkedInPost', + description: 'Generate a professional LinkedIn post with industry insights and engagement optimization', + parameters: [ + { name: 'topic', type: 'string', required: false }, + { name: 'industry', type: 'string', required: false }, + { name: 'post_type', type: 'string', required: false }, + { name: 'tone', type: 'string', required: false }, + { name: 'refine_existing', type: 'boolean', required: false, description: 'Whether to refine existing content instead of creating new' } + ], + handler: async (args: any) => { + const prefs = readPrefs(); + + // If refining existing content, use the current draft as context + let existingContent = ''; + if (args?.refine_existing) { + // Get current draft from the page context + const textarea = document.querySelector('textarea') as HTMLTextAreaElement; + const currentDraft = textarea?.value || ''; + if (currentDraft) { + existingContent = `\n\nREFINE THIS EXISTING CONTENT:\n${currentDraft}`; + } + } + + const res = await linkedInWriterApi.generatePost({ + topic: args?.topic || prefs.topic || 'AI transformation in business', + industry: mapIndustry(args?.industry || prefs.industry), + post_type: mapPostType(args?.post_type || prefs.post_type), + tone: mapTone(args?.tone || prefs.tone), + target_audience: args?.target_audience || prefs.target_audience || 'Business leaders and professionals', + key_points: args?.key_points || prefs.key_points || [], + include_hashtags: args?.include_hashtags ?? (prefs.include_hashtags ?? true), + include_call_to_action: args?.include_call_to_action ?? (prefs.include_call_to_action ?? true), + research_enabled: args?.research_enabled ?? (prefs.research_enabled ?? true), + search_engine: mapSearchEngine(args?.search_engine || prefs.search_engine), + max_length: args?.max_length || prefs.max_length || 2000 + }); + + if (res.success && res.data) { + const content = res.data.content; + const hashtags = res.data.hashtags?.map(h => h.hashtag).join(' ') || ''; + const cta = res.data.call_to_action || ''; + + let fullContent = content; + if (hashtags) fullContent += `\n\n${hashtags}`; + if (cta) fullContent += `\n\n${cta}`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:updateDraft', { detail: fullContent })); + return { success: true, content: fullContent }; + } + return { success: false, message: res.error || 'Failed to generate LinkedIn post' }; + } + }); + + // LinkedIn Article Generation + useCopilotActionTyped({ + name: 'generateLinkedInArticle', + description: 'Generate a comprehensive LinkedIn article with thought leadership content', + parameters: [ + { name: 'topic', type: 'string', required: false }, + { name: 'industry', type: 'string', required: false }, + { name: 'tone', type: 'string', required: false }, + { name: 'word_count', type: 'number', required: false } + ], + handler: async (args: any) => { + const prefs = readPrefs(); + const res = await linkedInWriterApi.generateArticle({ + topic: args?.topic || prefs.topic || 'Digital transformation strategies', + industry: mapIndustry(args?.industry || prefs.industry), + tone: mapTone(args?.tone || prefs.tone), + target_audience: args?.target_audience || prefs.target_audience || 'Industry professionals and executives', + key_sections: args?.key_sections || prefs.key_sections || [], + include_images: args?.include_images ?? (prefs.include_images ?? true), + seo_optimization: args?.seo_optimization ?? (prefs.seo_optimization ?? true), + research_enabled: args?.research_enabled ?? (prefs.research_enabled ?? true), + search_engine: mapSearchEngine(args?.search_engine || prefs.search_engine), + word_count: args?.word_count || prefs.word_count || 1500 + }); + + if (res.success && res.data) { + const content = `# ${res.data.title}\n\n${res.data.content}`; + window.dispatchEvent(new CustomEvent('linkedinwriter:updateDraft', { detail: content })); + return { success: true, content }; + } + return { success: false, message: res.error || 'Failed to generate LinkedIn article' }; + } + }); + + // LinkedIn Carousel Generation + useCopilotActionTyped({ + name: 'generateLinkedInCarousel', + description: 'Generate a LinkedIn carousel with multiple slides for visual content', + parameters: [ + { name: 'topic', type: 'string', required: false }, + { name: 'industry', type: 'string', required: false }, + { name: 'slide_count', type: 'number', required: false } + ], + handler: async (args: any) => { + const prefs = readPrefs(); + const res = await linkedInWriterApi.generateCarousel({ + topic: args?.topic || prefs.topic || 'Professional development tips', + industry: mapIndustry(args?.industry || prefs.industry), + slide_count: args?.slide_count || prefs.slide_count || 8, + tone: mapTone(args?.tone || prefs.tone), + target_audience: args?.target_audience || prefs.target_audience || 'Professionals seeking growth', + key_takeaways: args?.key_takeaways || prefs.key_takeaways || [], + include_cover_slide: args?.include_cover_slide ?? (prefs.include_cover_slide ?? true), + include_cta_slide: args?.include_cta_slide ?? (prefs.include_cta_slide ?? true), + visual_style: args?.visual_style || prefs.visual_style || 'modern' + }); + + if (res.success && res.data) { + let content = `# ${res.data.title}\n\n`; + res.data.slides.forEach((slide, index) => { + content += `## Slide ${index + 1}: ${slide.title}\n\n${slide.content}\n\n`; + }); + + window.dispatchEvent(new CustomEvent('linkedinwriter:updateDraft', { detail: content })); + return { success: true, content }; + } + return { success: false, message: res.error || 'Failed to generate LinkedIn carousel' }; + } + }); + + // LinkedIn Video Script Generation + useCopilotActionTyped({ + name: 'generateLinkedInVideoScript', + description: 'Generate a LinkedIn video script with hook, content, and captions', + parameters: [ + { name: 'topic', type: 'string', required: false }, + { name: 'industry', type: 'string', required: false }, + { name: 'video_length', type: 'number', required: false } + ], + handler: async (args: any) => { + const prefs = readPrefs(); + const res = await linkedInWriterApi.generateVideoScript({ + topic: args?.topic || prefs.topic || 'Professional networking tips', + industry: mapIndustry(args?.industry || prefs.industry), + video_length: args?.video_length || prefs.video_length || 60, + tone: mapTone(args?.tone || prefs.tone), + target_audience: args?.target_audience || prefs.target_audience || 'Professional networkers', + key_messages: args?.key_messages || prefs.key_messages || [], + include_hook: args?.include_hook ?? (prefs.include_hook ?? true), + include_captions: args?.include_captions ?? (prefs.include_captions ?? true) + }); + + if (res.success && res.data) { + let content = `# Video Script: ${args?.topic || 'Professional Content'}\n\n`; + content += `## Hook\n${res.data.hook}\n\n`; + content += `## Main Content\n`; + res.data.main_content.forEach((scene, index) => { + content += `### Scene ${index + 1} (${scene.duration || '30s'})\n${scene.content}\n\n`; + }); + content += `## Conclusion\n${res.data.conclusion}\n\n`; + content += `## Video Description\n${res.data.video_description}\n\n`; + + if (res.data.captions) { + content += `## Captions\n${res.data.captions.join('\n')}\n\n`; + } + + window.dispatchEvent(new CustomEvent('linkedinwriter:updateDraft', { detail: content })); + return { success: true, content }; + } + return { success: false, message: res.error || 'Failed to generate LinkedIn video script' }; + } + }); + + // LinkedIn Comment Response Generation + useCopilotActionTyped({ + name: 'generateLinkedInCommentResponse', + description: 'Generate a professional response to a LinkedIn comment', + parameters: [ + { name: 'original_post', type: 'string', required: false }, + { name: 'comment', type: 'string', required: false }, + { name: 'response_type', type: 'string', required: false } + ], + handler: async (args: any) => { + const prefs = readPrefs(); + const res = await linkedInWriterApi.generateCommentResponse({ + original_post: args?.original_post || prefs.original_post || 'Sample LinkedIn post content', + comment: args?.comment || prefs.comment || 'Sample comment to respond to', + response_type: args?.response_type || prefs.response_type || 'professional', + tone: mapTone(args?.tone || prefs.tone), + include_question: args?.include_question ?? (prefs.include_question ?? false), + brand_voice: args?.brand_voice || prefs.brand_voice + }); + + if (res.success && res.response) { + window.dispatchEvent(new CustomEvent('linkedinwriter:updateDraft', { detail: res.response })); + return { success: true, content: res.response }; + } + return { success: false, message: res.error || 'Failed to generate LinkedIn comment response' }; + } + }); + + // LinkedIn Profile Optimization + useCopilotActionTyped({ + name: 'optimizeLinkedInProfile', + description: 'Optimize LinkedIn profile sections for better professional visibility', + parameters: [ + { name: 'current_headline', type: 'string', required: false }, + { name: 'industry', type: 'string', required: false }, + { name: 'experience_level', type: 'string', required: false } + ], + handler: async (args: any) => { + const res = await linkedInWriterApi.optimizeProfile({ + current_headline: args?.current_headline || 'Professional', + industry: mapIndustry(args?.industry), + experience_level: args?.experience_level || 'mid-level', + target_role: args?.target_role, + key_skills: args?.key_skills || [] + }); + + if (res.success && res.data) { + let content = `# LinkedIn Profile Optimization\n\n`; + content += `## Optimized Headline\n${res.data.headline}\n\n`; + content += `## About Section\n${res.data.about}\n\n`; + content += `## Key Skills\n${res.data.skills?.join(', ')}\n\n`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:updateDraft', { detail: content })); + return { success: true, content }; + } + return { success: false, message: res.error || 'Failed to optimize LinkedIn profile' }; + } + }); + + // LinkedIn Poll Generation + useCopilotActionTyped({ + name: 'generateLinkedInPoll', + description: 'Generate an engaging LinkedIn poll with professional questions', + parameters: [ + { name: 'topic', type: 'string', required: false }, + { name: 'industry', type: 'string', required: false }, + { name: 'poll_type', type: 'string', required: false } + ], + handler: async (args: any) => { + const res = await linkedInWriterApi.generatePoll({ + topic: args?.topic || 'Professional development', + industry: mapIndustry(args?.industry), + poll_type: args?.poll_type || 'professional', + target_audience: args?.target_audience || 'Industry professionals', + question_count: args?.question_count || 1 + }); + + if (res.success && res.data) { + let content = `# LinkedIn Poll: ${res.data.question}\n\n`; + content += `## Options\n`; + res.data.options?.forEach((option: string, index: number) => { + content += `${index + 1}. ${option}\n`; + }); + content += `\n## Context\n${res.data.context || ''}\n\n`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:updateDraft', { detail: content })); + return { success: true, content }; + } + return { success: false, message: res.error || 'Failed to generate LinkedIn poll' }; + } + }); + + // LinkedIn Company Update Generation + useCopilotActionTyped({ + name: 'generateLinkedInCompanyUpdate', + description: 'Generate a professional company update for LinkedIn', + parameters: [ + { name: 'company_name', type: 'string', required: false }, + { name: 'update_type', type: 'string', required: false }, + { name: 'industry', type: 'string', required: false } + ], + handler: async (args: any) => { + const res = await linkedInWriterApi.generateCompanyUpdate({ + company_name: args?.company_name || 'Your Company', + update_type: args?.update_type || 'achievement', + industry: mapIndustry(args?.industry), + announcement: args?.announcement, + target_audience: args?.target_audience || 'Industry professionals and clients', + include_metrics: args?.include_metrics ?? true + }); + + if (res.success && res.data) { + const content = res.data.content; + window.dispatchEvent(new CustomEvent('linkedinwriter:updateDraft', { detail: content })); + return { success: true, content }; + } + return { success: false, message: res.error || 'Failed to generate LinkedIn company update' }; + } + }); + + return null; +}; + +export default RegisterLinkedInActions; diff --git a/frontend/src/components/LinkedInWriter/RegisterLinkedInEditActions.tsx b/frontend/src/components/LinkedInWriter/RegisterLinkedInEditActions.tsx new file mode 100644 index 00000000..38489fef --- /dev/null +++ b/frontend/src/components/LinkedInWriter/RegisterLinkedInEditActions.tsx @@ -0,0 +1,161 @@ +import React from 'react'; +import { useCopilotAction } from '@copilotkit/react-core'; + +const useCopilotActionTyped = useCopilotAction as any; + +const RegisterLinkedInEditActions: React.FC = () => { + // Professionalize Content + useCopilotActionTyped({ + name: 'professionalizeLinkedInContent', + description: 'Make LinkedIn content more professional and industry-appropriate', + parameters: [ + { name: 'content', type: 'string', required: false }, + { name: 'industry', type: 'string', required: false }, + { name: 'target_audience', type: 'string', required: false } + ], + handler: async (args: any) => { + // This would integrate with a backend endpoint for content professionalization + const content = args?.content || ''; + const industry = args?.industry || 'Technology'; + const targetAudience = args?.target_audience || 'Professionals'; + + // For now, return a placeholder response + const professionalizedContent = `[Professionalized version of your content for ${industry} industry targeting ${targetAudience}]\n\n${content}`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:applyEdit', { detail: { target: professionalizedContent } })); + return { success: true, content: professionalizedContent }; + } + }); + + // Optimize for Engagement + useCopilotActionTyped({ + name: 'optimizeLinkedInEngagement', + description: 'Optimize LinkedIn content for better engagement and reach', + parameters: [ + { name: 'content', type: 'string', required: false }, + { name: 'content_type', type: 'string', required: false } + ], + handler: async (args: any) => { + const content = args?.content || ''; + const contentType = args?.content_type || 'post'; + + // Placeholder for engagement optimization + const optimizedContent = `[Engagement-optimized ${contentType}]\n\n${content}\n\n#ProfessionalDevelopment #Networking #IndustryInsights`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:applyEdit', { detail: { target: optimizedContent } })); + return { success: true, content: optimizedContent }; + } + }); + + // Add Professional Hashtags + useCopilotActionTyped({ + name: 'addLinkedInHashtags', + description: 'Add relevant professional hashtags to LinkedIn content', + parameters: [ + { name: 'content', type: 'string', required: false }, + { name: 'industry', type: 'string', required: false } + ], + handler: async (args: any) => { + const content = args?.content || ''; + const industry = args?.industry || 'Technology'; + + // Placeholder for hashtag addition + const hashtags = '#ProfessionalDevelopment #Networking #IndustryInsights #CareerGrowth'; + const contentWithHashtags = `${content}\n\n${hashtags}`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:applyEdit', { detail: { target: contentWithHashtags } })); + return { success: true, content: contentWithHashtags }; + } + }); + + // Adjust Tone + useCopilotActionTyped({ + name: 'adjustLinkedInTone', + description: 'Adjust the tone of LinkedIn content to be more professional, conversational, or authoritative', + parameters: [ + { name: 'content', type: 'string', required: false }, + { name: 'target_tone', type: 'string', required: false } + ], + handler: async (args: any) => { + const content = args?.content || ''; + const targetTone = args?.target_tone || 'professional'; + + // Placeholder for tone adjustment + const adjustedContent = `[Content adjusted to ${targetTone} tone]\n\n${content}`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:applyEdit', { detail: { target: adjustedContent } })); + return { success: true, content: adjustedContent }; + } + }); + + // Expand Content + useCopilotActionTyped({ + name: 'expandLinkedInContent', + description: 'Expand LinkedIn content with more details and insights', + parameters: [ + { name: 'content', type: 'string', required: false }, + { name: 'expansion_type', type: 'string', required: false } + ], + handler: async (args: any) => { + const content = args?.content || ''; + const expansionType = args?.expansion_type || 'insights'; + + // Placeholder for content expansion + const expandedContent = `${content}\n\n[Additional ${expansionType} and context added here]`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:applyEdit', { detail: { target: expandedContent } })); + return { success: true, content: expandedContent }; + } + }); + + // Condense Content + useCopilotActionTyped({ + name: 'condenseLinkedInContent', + description: 'Condense LinkedIn content to be more concise and impactful', + parameters: [ + { name: 'content', type: 'string', required: false }, + { name: 'target_length', type: 'string', required: false } + ], + handler: async (args: any) => { + const content = args?.content || ''; + const targetLength = args?.target_length || 'short'; + + // Placeholder for content condensation + const condensedContent = `[Condensed to ${targetLength} format]\n\n${content.substring(0, Math.min(content.length, 500))}...`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:applyEdit', { detail: { target: condensedContent } })); + return { success: true, content: condensedContent }; + } + }); + + // Add Call to Action + useCopilotActionTyped({ + name: 'addLinkedInCallToAction', + description: 'Add a professional call to action to LinkedIn content', + parameters: [ + { name: 'content', type: 'string', required: false }, + { name: 'cta_type', type: 'string', required: false } + ], + handler: async (args: any) => { + const content = args?.content || ''; + const ctaType = args?.cta_type || 'engagement'; + + const ctaOptions = { + engagement: 'What are your thoughts on this? Share your experience in the comments below!', + networking: 'Let\'s connect if you\'re interested in discussing this further.', + learning: 'Would you like to learn more about this topic? Drop a comment or DM me.', + collaboration: 'Interested in collaborating on similar projects? Let\'s connect!' + }; + + const cta = ctaOptions[ctaType as keyof typeof ctaOptions] || ctaOptions.engagement; + const contentWithCTA = `${content}\n\n${cta}`; + + window.dispatchEvent(new CustomEvent('linkedinwriter:applyEdit', { detail: { target: contentWithCTA } })); + return { success: true, content: contentWithCTA }; + } + }); + + return null; +}; + +export default RegisterLinkedInEditActions; diff --git a/frontend/src/components/LinkedInWriter/components/ArticleHITL.tsx b/frontend/src/components/LinkedInWriter/components/ArticleHITL.tsx new file mode 100644 index 00000000..8243aeb5 --- /dev/null +++ b/frontend/src/components/LinkedInWriter/components/ArticleHITL.tsx @@ -0,0 +1,274 @@ +import React from 'react'; +import { linkedInWriterApi, LinkedInArticleRequest } from '../../../services/linkedInWriterApi'; +import { + readPrefs, + writePrefs, + logAssistant, + mapTone, + mapIndustry, + mapSearchEngine, + getPersonalizedPlaceholder, + VALID_TONES, + VALID_INDUSTRIES, + VALID_SEARCH_ENGINES +} from '../utils/linkedInWriterUtils'; + +interface ArticleHITLProps { + args: any; + respond: (data: any) => void; +} + +const ArticleHITL: React.FC = ({ args, respond }) => { + const prefs = React.useMemo(() => readPrefs(), []); + const [form, setForm] = React.useState({ + topic: args.topic ?? prefs.topic ?? '', + target_audience: args.target_audience ?? prefs.target_audience ?? '', + tone: args.tone ?? prefs.tone ?? 'professional', + industry: args.industry ?? prefs.industry ?? 'technology', + key_sections: args.key_sections ?? prefs.key_sections ?? [], + include_images: args.include_images ?? (prefs.include_images ?? true), + seo_optimization: args.seo_optimization ?? (prefs.seo_optimization ?? true), + research_enabled: args.research_enabled ?? (prefs.research_enabled ?? true), + word_count: args.word_count ?? (prefs.word_count ?? 800), + search_engine: args.search_engine ?? (prefs.search_engine ?? 'google') + }); + + const [isLoading, setIsLoading] = React.useState(false); + + const run = async () => { + try { + setIsLoading(true); + + // Emit loading start event + window.dispatchEvent(new CustomEvent('linkedinwriter:loadingStart', { + detail: { + action: 'Generating LinkedIn Article', + message: `Creating a comprehensive LinkedIn article about "${form.topic}" for your ${form.target_audience}. This will include ${form.key_sections?.length || 'several'} key sections and take approximately ${Math.round(form.word_count / 200)} minutes to read.` + } + })); + + logAssistant('Starting LinkedIn article generation...'); + + // Read user preferences + const prefs = readPrefs(); + if (prefs) { + form.tone = prefs.tone || form.tone; + form.industry = prefs.industry || form.industry; + } + + // Normalize and map enum values + const request: LinkedInArticleRequest = { + topic: form.topic, + target_audience: form.target_audience, + tone: mapTone(form.tone), + industry: mapIndustry(form.industry), + key_sections: form.key_sections, + include_images: form.include_images, + seo_optimization: form.seo_optimization, + research_enabled: form.research_enabled, + word_count: form.word_count, + search_engine: mapSearchEngine(form.search_engine) + }; + + const res = await linkedInWriterApi.generateArticle(request); + + // Write preferences + writePrefs({ + tone: form.tone, + industry: form.industry, + target_audience: form.target_audience, + key_sections: form.key_sections, + include_images: form.include_images, + seo_optimization: form.seo_optimization, + research_enabled: form.research_enabled, + word_count: form.word_count, + search_engine: form.search_engine + }); + + logAssistant('LinkedIn article generated successfully'); + + // Update draft content + if (res.data) { + const content = `# ${res.data.title}\n\n${res.data.content}`; + + // Emit loading end event + window.dispatchEvent(new CustomEvent('linkedinwriter:loadingEnd', { detail: {} })); + + window.dispatchEvent(new CustomEvent('linkedinwriter:updateDraft', { + detail: content + })); + + respond({ + success: true, + article: res.data.content, + title: res.data.title, + word_count: res.data.word_count, + reading_time: res.data.reading_time + }); + } else { + throw new Error('No data received from API'); + } + + } catch (error) { + console.error('LinkedIn Article Generation Error:', error); + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + + // Emit loading end event with error + window.dispatchEvent(new CustomEvent('linkedinwriter:loadingEnd', { + detail: { error: errorMessage } + })); + + logAssistant(`Error generating LinkedIn article: ${errorMessage}`); + respond({ + success: false, + error: errorMessage + }); + } finally { + setIsLoading(false); + } + }; + + return ( +
+

Generate LinkedIn Article

+ +
+ + setForm({ ...form, topic: e.target.value })} + placeholder={getPersonalizedPlaceholder('article', 'topic', prefs)} + required + /> +
+ +
+ + setForm({ ...form, target_audience: e.target.value })} + placeholder={getPersonalizedPlaceholder('article', 'target_audience', prefs)} + /> +
+ +
+ + +
+ +
+ + +
+ +
+ +