import { defineStore } from 'pinia' import { ref } from 'vue' import axios from 'axios' export interface AiAnalysis { themes: string[] tone: string positioning: string gaps: string[] moves: string[] } export type KeywordIntent = 'informational' | 'commercial' | 'transactional' | 'navigational' export interface CompetitorKeyword { term: string intent: KeywordIntent extractedAt?: string } export interface GapItem { term: string intent: KeywordIntent } export interface CoveredItem extends GapItem { matchedHashtags: string[] } export interface GapAnalysis { gaps: GapItem[] covered: CoveredItem[] totalKeywords: number hashtagStatsEmpty: boolean lastAnalyzed: string } export interface RoadmapPost { topic: string headline: string keywords: string[] rationale: string } export interface CompetitorSuggestion { name: string websiteUrl: string reason: string } export interface Competitor { _id: string name: string websiteUrl: string socialUrls: Partial> scrapedContent: { source: string; url: string; text: string; scrapedAt: string }[] aiSummary: string aiAnalysis?: AiAnalysis keywords: CompetitorKeyword[] contentChanged?: boolean gapAnalysis?: GapAnalysis contentRoadmap?: RoadmapPost[] lastScraped: string | null createdAt: string updatedAt: string } export const useCompetitorStore = defineStore('competitors', () => { const competitors = ref([]) const loading = ref(false) const scraping = ref>({}) const summarizing = ref>({}) const extractingKeywords = ref>({}) const analyzingGaps = ref>({}) const generatingRoadmap = ref>({}) const scrapeResults = ref>({}) const error = ref(null) async function fetchCompetitors() { loading.value = true error.value = null try { const res = await axios.get('/api/competitors') competitors.value = res.data } catch (err: any) { error.value = err.response?.data?.error || 'Failed to load competitors' } finally { loading.value = false } } async function addCompetitor(data: { name: string; websiteUrl: string; socialUrls?: Record }): Promise { error.value = null try { const res = await axios.post('/api/competitors', data) competitors.value.push(res.data) return true } catch (err: any) { error.value = err.response?.data?.error || 'Failed to add competitor' return false } } async function updateCompetitor(id: string, data: Partial>): Promise { error.value = null try { const res = await axios.put(`/api/competitors/${id}`, data) const idx = competitors.value.findIndex((c) => c._id === id) if (idx !== -1) competitors.value[idx] = res.data return true } catch (err: any) { error.value = err.response?.data?.error || 'Failed to update competitor' return false } } async function deleteCompetitor(id: string): Promise { error.value = null try { await axios.delete(`/api/competitors/${id}`) competitors.value = competitors.value.filter((c) => c._id !== id) return true } catch (err: any) { error.value = err.response?.data?.error || 'Failed to delete competitor' return false } } async function pollScrapeJob(competitorId: string, jobId: string): Promise<{ ok: boolean; sources: number; message: string }> { return new Promise((resolve) => { const check = async () => { try { const res = await axios.get(`/api/competitors/${competitorId}/scrape-status/${jobId}`) const { status, sources, message } = res.data if (status === 'done' || status === 'failed') { resolve({ ok: status === 'done', sources: sources ?? 0, message: message || '' }) } else { setTimeout(check, 2000) } } catch { resolve({ ok: false, sources: 0, message: 'Status check failed' }) } } check() }) } async function scrapeCompetitor(id: string): Promise { scraping.value = { ...scraping.value, [id]: true } try { const res = await axios.post(`/api/competitors/${id}/scrape`) const { jobId } = res.data const result = await pollScrapeJob(id, jobId) scrapeResults.value = { ...scrapeResults.value, [id]: result } await fetchCompetitors() } catch (err: any) { const msg = err.response?.data?.detail || err.response?.data?.error || err.message scrapeResults.value = { ...scrapeResults.value, [id]: { sources: 0, ok: false, message: msg } } } finally { scraping.value = { ...scraping.value, [id]: false } } } async function summarizeCompetitor(id: string): Promise { summarizing.value = { ...summarizing.value, [id]: true } error.value = null try { const res = await axios.post(`/api/competitors/${id}/summarize`) const idx = competitors.value.findIndex((c) => c._id === id) if (idx !== -1) { competitors.value[idx].aiAnalysis = res.data.aiAnalysis competitors.value[idx].aiSummary = '' } } catch (err: any) { // Long AI calls can exceed the proxy timeout while the server still completes. // Re-fetch before showing an error — if the data saved, surface it silently. await fetchCompetitors() const saved = competitors.value.find((c) => c._id === id)?.aiAnalysis if (!saved) { error.value = err.response?.data?.detail || err.response?.data?.error || 'Summarization failed' } } finally { summarizing.value = { ...summarizing.value, [id]: false } } } async function extractKeywords(id: string): Promise { extractingKeywords.value = { ...extractingKeywords.value, [id]: true } error.value = null try { const res = await axios.post(`/api/competitors/${id}/extract-keywords`) const idx = competitors.value.findIndex((c) => c._id === id) if (idx !== -1) competitors.value[idx].keywords = res.data.keywords || [] } catch (err: any) { error.value = err.response?.data?.detail || err.response?.data?.error || 'Keyword extraction failed' } finally { extractingKeywords.value = { ...extractingKeywords.value, [id]: false } } } async function analyzeGaps(id: string): Promise { analyzingGaps.value = { ...analyzingGaps.value, [id]: true } error.value = null try { const res = await axios.post(`/api/competitors/${id}/analyze-gaps`) const idx = competitors.value.findIndex((c) => c._id === id) if (idx !== -1) competitors.value[idx].gapAnalysis = res.data } catch (err: any) { error.value = err.response?.data?.detail || err.response?.data?.error || 'Gap analysis failed' } finally { analyzingGaps.value = { ...analyzingGaps.value, [id]: false } } } async function generateRoadmap(id: string): Promise { generatingRoadmap.value = { ...generatingRoadmap.value, [id]: true } error.value = null try { const res = await axios.post(`/api/competitors/${id}/content-roadmap`) const idx = competitors.value.findIndex((c) => c._id === id) if (idx !== -1) competitors.value[idx].contentRoadmap = res.data.contentRoadmap } catch (err: any) { // Long AI calls can exceed the proxy timeout while the server still completes. // Re-fetch before showing an error — if the data saved, surface it silently. await fetchCompetitors() const saved = competitors.value.find((c) => c._id === id)?.contentRoadmap if (!saved?.length) { error.value = err.response?.data?.detail || err.response?.data?.error || 'Roadmap generation failed' } } finally { generatingRoadmap.value = { ...generatingRoadmap.value, [id]: false } } } const discoveringCompetitors = ref(false) const discoverySuggestions = ref([]) async function discoverCompetitors(): Promise { discoveringCompetitors.value = true error.value = null discoverySuggestions.value = [] try { const res = await axios.post('/api/competitors/discover') discoverySuggestions.value = res.data.suggestions || [] } catch (err: any) { error.value = err.response?.data?.detail || err.response?.data?.error || 'Discovery failed' } finally { discoveringCompetitors.value = false } } return { competitors, loading, scraping, summarizing, extractingKeywords, analyzingGaps, generatingRoadmap, scrapeResults, discoveringCompetitors, discoverySuggestions, error, fetchCompetitors, addCompetitor, updateCompetitor, deleteCompetitor, scrapeCompetitor, summarizeCompetitor, extractKeywords, analyzeGaps, generateRoadmap, discoverCompetitors, } })