Research component integration, Copilotkit implementation, SEO copilotkit implementation, Wix SEO metadata complete, Wix SEO metadata review
This commit is contained in:
@@ -17,6 +17,23 @@ export interface ResearchSource {
|
||||
source_type?: string;
|
||||
}
|
||||
|
||||
export type ResearchMode = 'basic' | 'comprehensive' | 'targeted';
|
||||
export type ResearchProvider = 'google' | 'exa';
|
||||
export type SourceType = 'web' | 'academic' | 'news' | 'industry' | 'expert';
|
||||
export type DateRange = 'last_week' | 'last_month' | 'last_3_months' | 'last_6_months' | 'last_year' | 'all_time';
|
||||
|
||||
export interface ResearchConfig {
|
||||
mode?: ResearchMode;
|
||||
provider?: ResearchProvider;
|
||||
date_range?: DateRange;
|
||||
source_types?: SourceType[];
|
||||
max_sources?: number;
|
||||
include_statistics?: boolean;
|
||||
include_expert_quotes?: boolean;
|
||||
include_competitors?: boolean;
|
||||
include_trends?: boolean;
|
||||
}
|
||||
|
||||
export interface BlogResearchRequest {
|
||||
keywords: string[];
|
||||
topic?: string;
|
||||
@@ -25,6 +42,8 @@ export interface BlogResearchRequest {
|
||||
tone?: string;
|
||||
word_count_target?: number;
|
||||
persona?: PersonaInfo;
|
||||
research_mode?: ResearchMode;
|
||||
config?: ResearchConfig;
|
||||
}
|
||||
|
||||
export interface GroundingChunk {
|
||||
|
||||
158
frontend/src/services/blogWriterCache.ts
Normal file
158
frontend/src/services/blogWriterCache.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Blog Writer Cache Service
|
||||
*
|
||||
* Provides persistent caching for outline and content to survive page refreshes
|
||||
* and avoid unnecessary API calls. Shared by both CopilotKit and manual flows.
|
||||
*/
|
||||
|
||||
interface CachedOutlineEntry {
|
||||
outline: any[];
|
||||
title_options?: string[];
|
||||
research_keywords: string[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface CachedContentEntry {
|
||||
sections: Record<string, string>;
|
||||
outline_ids: string[];
|
||||
research_keywords: string[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
class BlogWriterCacheService {
|
||||
private readonly OUTLINE_CACHE_KEY = 'blog_outline';
|
||||
private readonly TITLE_OPTIONS_CACHE_KEY = 'blog_title_options';
|
||||
private readonly CONTENT_CACHE_PREFIX = 'blog_content_';
|
||||
|
||||
/**
|
||||
* Get cached outline for research keywords
|
||||
*/
|
||||
getCachedOutline(researchKeywords: string[]): { outline: any[]; title_options?: string[] } | null {
|
||||
try {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
const savedOutline = localStorage.getItem(this.OUTLINE_CACHE_KEY);
|
||||
const savedTitleOptions = localStorage.getItem(this.TITLE_OPTIONS_CACHE_KEY);
|
||||
|
||||
if (!savedOutline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsedOutline = JSON.parse(savedOutline);
|
||||
if (!Array.isArray(parsedOutline) || parsedOutline.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Basic validation: if we have an outline saved and it has sections, use it
|
||||
// More sophisticated matching could compare research keywords if needed
|
||||
const titleOptions = savedTitleOptions ? JSON.parse(savedTitleOptions) : undefined;
|
||||
|
||||
console.log(`Cache hit for outline (${parsedOutline.length} sections)`);
|
||||
return {
|
||||
outline: parsedOutline,
|
||||
title_options: titleOptions
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error retrieving cached outline:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache outline result
|
||||
*/
|
||||
cacheOutline(outline: any[], titleOptions?: string[]): void {
|
||||
try {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
localStorage.setItem(this.OUTLINE_CACHE_KEY, JSON.stringify(outline));
|
||||
if (titleOptions) {
|
||||
localStorage.setItem(this.TITLE_OPTIONS_CACHE_KEY, JSON.stringify(titleOptions));
|
||||
}
|
||||
console.log(`Cached outline (${outline.length} sections)`);
|
||||
} catch (error) {
|
||||
console.error('Error caching outline:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate cache key for content based on outline section IDs
|
||||
*/
|
||||
private generateContentCacheKey(outlineIds: string[]): string {
|
||||
const sortedIds = [...outlineIds].sort().join('|');
|
||||
return `${this.CONTENT_CACHE_PREFIX}${sortedIds}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached content for outline sections
|
||||
*/
|
||||
getCachedContent(outlineIds: string[]): Record<string, string> | null {
|
||||
try {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
const cacheKey = this.generateContentCacheKey(outlineIds);
|
||||
const cachedContent = localStorage.getItem(cacheKey);
|
||||
|
||||
if (!cachedContent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsedSections = JSON.parse(cachedContent);
|
||||
if (!parsedSections || typeof parsedSections !== 'object' || Object.keys(parsedSections).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify that cached sections match outline structure
|
||||
const cachedIds = new Set(Object.keys(parsedSections));
|
||||
const outlineIdsSet = new Set(outlineIds.map(id => String(id)));
|
||||
const idsMatch = outlineIdsSet.size === cachedIds.size &&
|
||||
Array.from(outlineIdsSet).every(id => cachedIds.has(id));
|
||||
|
||||
if (!idsMatch) {
|
||||
console.log('Cached content does not match outline structure');
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Cache hit for content (${Object.keys(parsedSections).length} sections)`);
|
||||
return parsedSections;
|
||||
} catch (error) {
|
||||
console.error('Error retrieving cached content:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache content sections
|
||||
*/
|
||||
cacheContent(sections: Record<string, string>, outlineIds: string[]): void {
|
||||
try {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (!sections || Object.keys(sections).length === 0) return;
|
||||
|
||||
const cacheKey = this.generateContentCacheKey(outlineIds);
|
||||
localStorage.setItem(cacheKey, JSON.stringify(sections));
|
||||
console.log(`Cached content (${Object.keys(sections).length} sections)`);
|
||||
} catch (error) {
|
||||
console.error('Error caching content:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content exists in state (helper for manual flow)
|
||||
*/
|
||||
contentExistsInState(sections: Record<string, string>, outlineIds: string[]): boolean {
|
||||
if (!sections || Object.keys(sections).length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const existingIds = new Set(Object.keys(sections));
|
||||
const outlineIdsSet = new Set(outlineIds.map(id => String(id)));
|
||||
return outlineIdsSet.size === existingIds.size &&
|
||||
Array.from(outlineIdsSet).every(id => existingIds.has(id));
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const blogWriterCache = new BlogWriterCacheService();
|
||||
export default blogWriterCache;
|
||||
|
||||
Reference in New Issue
Block a user