Added citation and quality metrics to the content editor.
This commit is contained in:
@@ -5,12 +5,74 @@ export function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
// Format draft content with proper LinkedIn styling
|
||||
export function formatDraftContent(content: string): string {
|
||||
// Format draft content with proper LinkedIn styling and inline citations
|
||||
export function formatDraftContent(content: string, citations?: any[], researchSources?: any[]): string {
|
||||
if (!content) return '';
|
||||
|
||||
let formatted = escapeHtml(content);
|
||||
|
||||
// Insert inline citations if available
|
||||
if (citations && citations.length > 0 && researchSources && researchSources.length > 0) {
|
||||
console.log('🔍 [formatDraftContent] Processing citations:', {
|
||||
citationsCount: citations.length,
|
||||
researchSourcesCount: researchSources.length,
|
||||
citations: citations,
|
||||
contentLength: content.length
|
||||
});
|
||||
|
||||
// Create a map of citation references to source numbers
|
||||
const citationMap = new Map();
|
||||
citations.forEach((citation, index) => {
|
||||
if (citation.reference && citation.reference.startsWith('Source ')) {
|
||||
const sourceNum = citation.reference.replace('Source ', '');
|
||||
citationMap.set(citation.reference, sourceNum);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('🔍 [formatDraftContent] Citation map created:', citationMap);
|
||||
|
||||
// Since citation references don't exist in the content text,
|
||||
// we need to insert citations strategically throughout the content
|
||||
const citationEntries = Array.from(citationMap.entries());
|
||||
const totalCitations = citationEntries.length;
|
||||
|
||||
if (totalCitations > 0) {
|
||||
// Split content into sentences for strategic citation placement
|
||||
const sentences = formatted.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
||||
const sentencesWithCitations: string[] = [];
|
||||
|
||||
citationEntries.forEach(([reference, sourceNum], index) => {
|
||||
// Distribute citations across sentences
|
||||
const targetSentenceIndex = Math.floor((index / totalCitations) * sentences.length);
|
||||
const targetSentence = sentences[targetSentenceIndex] || sentences[sentences.length - 1];
|
||||
|
||||
// Add citation to the end of the target sentence using a superscript marker
|
||||
const citeHtml = ` <sup class="liw-cite" data-source-index="${sourceNum}">[${sourceNum}]</sup>`;
|
||||
const sentenceWithCitation = targetSentence.trim() + citeHtml;
|
||||
sentencesWithCitations[targetSentenceIndex] = sentenceWithCitation;
|
||||
|
||||
console.log(`✅ [formatDraftContent] Added citation [${sourceNum}] to sentence ${targetSentenceIndex + 1}`);
|
||||
});
|
||||
|
||||
// Reconstruct content with citations
|
||||
formatted = sentences.map((sentence, index) => {
|
||||
return sentencesWithCitations[index] || sentence;
|
||||
}).join('. ') + '.';
|
||||
|
||||
console.log(`✅ [formatDraftContent] Inserted ${totalCitations} citations strategically throughout content`);
|
||||
|
||||
// Debug: Show sample of content with citations
|
||||
const sampleContent = formatted.substring(0, 500) + (formatted.length > 500 ? '...' : '');
|
||||
console.log('🔍 [formatDraftContent] Sample content with citations:', sampleContent);
|
||||
|
||||
// Debug: Count citation markers in final content
|
||||
const citationMarkers = (formatted.match(/\[\d+\]/g) || []).length;
|
||||
console.log(`🔍 [formatDraftContent] Found ${citationMarkers} citation markers in final content`);
|
||||
}
|
||||
|
||||
console.log('🔍 [formatDraftContent] Final formatted content length:', formatted.length);
|
||||
}
|
||||
|
||||
// Format hashtags
|
||||
formatted = formatted.replace(/#(\w+)/g, '<span style="color: #0a66c2; font-weight: 600;">#$1</span>');
|
||||
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Enhanced persistence utility for CopilotKit integration
|
||||
* Uses localStorage and CopilotKit hooks for better state management
|
||||
*/
|
||||
|
||||
import { useCopilotContext } from '@copilotkit/react-core';
|
||||
|
||||
// Storage keys for different types of data
|
||||
export const STORAGE_KEYS = {
|
||||
CHAT_HISTORY: 'alwrity-copilot-chat-history',
|
||||
USER_PREFERENCES: 'alwrity-copilot-user-preferences',
|
||||
CONVERSATION_CONTEXT: 'alwrity-copilot-conversation-context',
|
||||
DRAFT_CONTENT: 'alwrity-copilot-draft-content',
|
||||
LAST_SESSION: 'alwrity-copilot-last-session'
|
||||
};
|
||||
|
||||
// Chat message interface
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
timestamp: number;
|
||||
metadata?: {
|
||||
action?: string;
|
||||
result?: any;
|
||||
context?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// User preferences interface
|
||||
export interface UserPreferences {
|
||||
tone: string;
|
||||
industry: string;
|
||||
target_audience: string;
|
||||
content_goals: string[];
|
||||
writing_style: string;
|
||||
hashtag_preferences: boolean;
|
||||
cta_preferences: boolean;
|
||||
last_used_actions: string[];
|
||||
favorite_topics: string[];
|
||||
last_updated: number;
|
||||
}
|
||||
|
||||
// Conversation context interface
|
||||
export interface ConversationContext {
|
||||
currentTopic: string;
|
||||
industry: string;
|
||||
tone: string;
|
||||
targetAudience: string;
|
||||
keyPoints: string[];
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
// Main persistence manager class
|
||||
export class CopilotPersistenceManager {
|
||||
private static instance: CopilotPersistenceManager;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): CopilotPersistenceManager {
|
||||
if (!CopilotPersistenceManager.instance) {
|
||||
CopilotPersistenceManager.instance = new CopilotPersistenceManager();
|
||||
}
|
||||
return CopilotPersistenceManager.instance;
|
||||
}
|
||||
|
||||
// Chat history persistence
|
||||
public saveChatHistory(messages: ChatMessage[]): void {
|
||||
try {
|
||||
// Keep only last 100 messages to prevent excessive storage
|
||||
const trimmedMessages = messages.slice(-100);
|
||||
localStorage.setItem(STORAGE_KEYS.CHAT_HISTORY, JSON.stringify(trimmedMessages));
|
||||
console.log(`💾 Saved ${trimmedMessages.length} chat messages`);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to save chat history:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public loadChatHistory(): ChatMessage[] {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.CHAT_HISTORY);
|
||||
if (!stored) return [];
|
||||
|
||||
const messages = JSON.parse(stored);
|
||||
console.log(`📖 Loaded ${messages.length} chat messages`);
|
||||
return messages;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load chat history:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public addChatMessage(message: ChatMessage): void {
|
||||
try {
|
||||
const existing = this.loadChatHistory();
|
||||
existing.push(message);
|
||||
this.saveChatHistory(existing);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to add chat message:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// User preferences persistence
|
||||
public saveUserPreferences(preferences: Partial<UserPreferences>): void {
|
||||
try {
|
||||
const existing = this.loadUserPreferences();
|
||||
const updated = { ...existing, ...preferences, last_updated: Date.now() };
|
||||
localStorage.setItem(STORAGE_KEYS.USER_PREFERENCES, JSON.stringify(updated));
|
||||
console.log('💾 Saved user preferences');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to save user preferences:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public loadUserPreferences(): UserPreferences {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.USER_PREFERENCES);
|
||||
if (!stored) {
|
||||
return {
|
||||
tone: 'Professional',
|
||||
industry: 'Technology',
|
||||
target_audience: 'Professionals',
|
||||
content_goals: ['Engagement', 'Thought Leadership'],
|
||||
writing_style: 'Clear and Concise',
|
||||
hashtag_preferences: true,
|
||||
cta_preferences: true,
|
||||
last_used_actions: [],
|
||||
favorite_topics: [],
|
||||
last_updated: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
const preferences = JSON.parse(stored);
|
||||
console.log('📖 Loaded user preferences');
|
||||
return preferences;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load user preferences:', error);
|
||||
// Return default preferences instead of recursive call
|
||||
return {
|
||||
tone: 'Professional',
|
||||
industry: 'Technology',
|
||||
target_audience: 'Professionals',
|
||||
content_goals: ['Engagement', 'Thought Leadership'],
|
||||
writing_style: 'Clear and Concise',
|
||||
hashtag_preferences: true,
|
||||
cta_preferences: true,
|
||||
last_used_actions: [],
|
||||
favorite_topics: [],
|
||||
last_updated: Date.now()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Conversation context persistence
|
||||
public saveConversationContext(context: Partial<ConversationContext>): void {
|
||||
try {
|
||||
const existing = this.loadConversationContext();
|
||||
const updated = { ...existing, ...context, lastUpdated: Date.now() };
|
||||
localStorage.setItem(STORAGE_KEYS.CONVERSATION_CONTEXT, JSON.stringify(updated));
|
||||
console.log('💾 Saved conversation context');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to save conversation context:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public loadConversationContext(): ConversationContext {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.CONVERSATION_CONTEXT);
|
||||
if (!stored) {
|
||||
return {
|
||||
currentTopic: '',
|
||||
industry: 'Technology',
|
||||
tone: 'Professional',
|
||||
targetAudience: 'Professionals',
|
||||
keyPoints: [],
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
const context = JSON.parse(stored);
|
||||
console.log('📖 Loaded conversation context');
|
||||
return context;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load conversation context:', error);
|
||||
// Return default context instead of recursive call
|
||||
return {
|
||||
currentTopic: '',
|
||||
industry: 'Technology',
|
||||
tone: 'Professional',
|
||||
targetAudience: 'Professionals',
|
||||
keyPoints: [],
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Draft content persistence
|
||||
public saveDraftContent(draft: string): void {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEYS.DRAFT_CONTENT, draft);
|
||||
console.log('💾 Saved draft content');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to save draft content:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public loadDraftContent(): string {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.DRAFT_CONTENT);
|
||||
if (stored) {
|
||||
console.log('📖 Loaded draft content');
|
||||
return stored;
|
||||
}
|
||||
return '';
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load draft content:', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Session management
|
||||
public saveLastSession(): void {
|
||||
try {
|
||||
const sessionData = {
|
||||
timestamp: Date.now(),
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent
|
||||
};
|
||||
localStorage.setItem(STORAGE_KEYS.LAST_SESSION, JSON.stringify(sessionData));
|
||||
console.log('💾 Saved session data');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to save session data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public loadLastSession(): any {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEYS.LAST_SESSION);
|
||||
if (stored) {
|
||||
const session = JSON.parse(stored);
|
||||
console.log('📖 Loaded session data');
|
||||
return session;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load session data:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all persistence data
|
||||
public clearAllData(): void {
|
||||
try {
|
||||
Object.values(STORAGE_KEYS).forEach(key => {
|
||||
localStorage.removeItem(key);
|
||||
});
|
||||
console.log('🗑️ Cleared all persistence data');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to clear persistence data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get storage statistics
|
||||
public getStorageStats(): any {
|
||||
try {
|
||||
const stats = {
|
||||
chatHistory: this.loadChatHistory().length,
|
||||
hasUserPreferences: !!localStorage.getItem(STORAGE_KEYS.USER_PREFERENCES),
|
||||
hasConversationContext: !!localStorage.getItem(STORAGE_KEYS.CONVERSATION_CONTEXT),
|
||||
hasDraftContent: !!localStorage.getItem(STORAGE_KEYS.DRAFT_CONTENT),
|
||||
hasLastSession: !!localStorage.getItem(STORAGE_KEYS.LAST_SESSION),
|
||||
totalKeys: Object.keys(localStorage).filter(key => key.includes('alwrity-copilot')).length
|
||||
};
|
||||
|
||||
console.log('📊 Storage statistics:', stats);
|
||||
return stats;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to get storage stats:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hook for using persistence in React components
|
||||
export const useCopilotPersistence = () => {
|
||||
const copilotContext = useCopilotContext();
|
||||
const persistenceManager = CopilotPersistenceManager.getInstance();
|
||||
|
||||
return {
|
||||
persistenceManager,
|
||||
copilotContext,
|
||||
// Convenience methods
|
||||
saveChatHistory: persistenceManager.saveChatHistory.bind(persistenceManager),
|
||||
loadChatHistory: persistenceManager.loadChatHistory.bind(persistenceManager),
|
||||
addChatMessage: persistenceManager.addChatMessage.bind(persistenceManager),
|
||||
saveUserPreferences: persistenceManager.saveUserPreferences.bind(persistenceManager),
|
||||
loadUserPreferences: persistenceManager.loadUserPreferences.bind(persistenceManager),
|
||||
saveConversationContext: persistenceManager.saveConversationContext.bind(persistenceManager),
|
||||
loadConversationContext: persistenceManager.loadConversationContext.bind(persistenceManager),
|
||||
saveDraftContent: persistenceManager.saveDraftContent.bind(persistenceManager),
|
||||
loadDraftContent: persistenceManager.loadDraftContent.bind(persistenceManager),
|
||||
saveLastSession: persistenceManager.saveLastSession.bind(persistenceManager),
|
||||
loadLastSession: persistenceManager.loadLastSession.bind(persistenceManager),
|
||||
clearAllData: persistenceManager.clearAllData.bind(persistenceManager),
|
||||
getStorageStats: persistenceManager.getStorageStats.bind(persistenceManager)
|
||||
};
|
||||
};
|
||||
@@ -23,7 +23,6 @@ export const VALID_TONES = [
|
||||
] as const;
|
||||
|
||||
export const VALID_SEARCH_ENGINES = [
|
||||
'metaphor',
|
||||
'google',
|
||||
'tavily'
|
||||
] as const;
|
||||
@@ -158,8 +157,12 @@ export function mapIndustry(industry: string | undefined): string {
|
||||
}
|
||||
|
||||
export function mapSearchEngine(engine: string | undefined): SearchEngine {
|
||||
// Force Google for now until METAPHOR issue is resolved
|
||||
return SearchEngine.GOOGLE;
|
||||
|
||||
/* Original logic - commented out temporarily
|
||||
const eng = normalizeEnum(engine);
|
||||
if (!eng) return SearchEngine.METAPHOR;
|
||||
if (!eng) return SearchEngine.GOOGLE;
|
||||
|
||||
const exact = VALID_SEARCH_ENGINES.find(v => v.toLowerCase() === eng);
|
||||
if (exact) return exact as SearchEngine;
|
||||
@@ -167,7 +170,8 @@ export function mapSearchEngine(engine: string | undefined): SearchEngine {
|
||||
if (eng.includes('google')) return SearchEngine.GOOGLE;
|
||||
if (eng.includes('tavily')) return SearchEngine.TAVILY;
|
||||
|
||||
return SearchEngine.METAPHOR;
|
||||
return SearchEngine.GOOGLE;
|
||||
*/
|
||||
}
|
||||
|
||||
export function mapResponseType(responseType: string | undefined): string {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Utility to test and debug CopilotKit persistence
|
||||
*/
|
||||
|
||||
export const testPersistence = () => {
|
||||
console.log('🧪 Testing CopilotKit persistence...');
|
||||
|
||||
// Check localStorage for persisted data
|
||||
const chatData = localStorage.getItem('alwrity-copilot-chat');
|
||||
const prefsData = localStorage.getItem('alwrity-copilot-preferences');
|
||||
const contextData = localStorage.getItem('alwrity-copilot-context');
|
||||
|
||||
console.log('📊 Persistence Test Results:', {
|
||||
chat: {
|
||||
exists: !!chatData,
|
||||
length: chatData ? JSON.parse(chatData).length : 0,
|
||||
sample: chatData ? JSON.parse(chatData).slice(0, 2) : null
|
||||
},
|
||||
preferences: {
|
||||
exists: !!prefsData,
|
||||
data: prefsData ? JSON.parse(prefsData) : null
|
||||
},
|
||||
context: {
|
||||
exists: !!contextData,
|
||||
data: contextData ? JSON.parse(contextData) : null
|
||||
}
|
||||
});
|
||||
|
||||
// Check for any other CopilotKit related data
|
||||
const allKeys = Object.keys(localStorage);
|
||||
const copilotKeys = allKeys.filter(key => key.includes('copilot') || key.includes('alwrity'));
|
||||
|
||||
console.log('🔍 All CopilotKit related localStorage keys:', copilotKeys);
|
||||
|
||||
return {
|
||||
chat: !!chatData,
|
||||
preferences: !!prefsData,
|
||||
context: !!contextData,
|
||||
allCopilotKeys: copilotKeys
|
||||
};
|
||||
};
|
||||
|
||||
export const clearPersistence = () => {
|
||||
console.log('🗑️ Clearing CopilotKit persistence...');
|
||||
|
||||
localStorage.removeItem('alwrity-copilot-chat');
|
||||
localStorage.removeItem('alwrity-copilot-preferences');
|
||||
localStorage.removeItem('alwrity-copilot-context');
|
||||
|
||||
// Clear any other CopilotKit related data
|
||||
const allKeys = Object.keys(localStorage);
|
||||
const copilotKeys = allKeys.filter(key => key.includes('copilot') || key.includes('alwrity'));
|
||||
|
||||
copilotKeys.forEach(key => {
|
||||
localStorage.removeItem(key);
|
||||
console.log(`🗑️ Removed: ${key}`);
|
||||
});
|
||||
|
||||
console.log('✅ Persistence cleared');
|
||||
};
|
||||
|
||||
export const simulateChatMessage = () => {
|
||||
console.log('💬 Simulating chat message for persistence test...');
|
||||
|
||||
const testMessage = {
|
||||
role: 'user',
|
||||
content: 'This is a test message to verify persistence',
|
||||
timestamp: Date.now(),
|
||||
id: `test-${Date.now()}`
|
||||
};
|
||||
|
||||
// Try to store in the expected format
|
||||
try {
|
||||
const existingChat = localStorage.getItem('alwrity-copilot-chat');
|
||||
const chatArray = existingChat ? JSON.parse(existingChat) : [];
|
||||
chatArray.push(testMessage);
|
||||
|
||||
// Keep only last 10 messages for testing
|
||||
const trimmedChat = chatArray.slice(-10);
|
||||
localStorage.setItem('alwrity-copilot-chat', JSON.stringify(trimmedChat));
|
||||
|
||||
console.log('✅ Test message stored:', testMessage);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to store test message:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user