@@ -837,34 +757,6 @@ const renderMarkdown = (markdown) => {
}
};
-// Computed свойство для рендеринга summary
-const renderedSummary = computed(() => {
- if (!report.value || !report.value.summary) return '';
- return renderMarkdown(report.value.summary);
-});
-
-// Chart data computed properties
-const seasonalityChartData = computed(() => {
- if (!report.value?.chartsData?.seasonality) return null;
- const seasonality = report.value.chartsData.seasonality;
- const months = Object.keys(seasonality);
- const values = Object.values(seasonality);
-
- return {
- labels: months,
- datasets: [
- {
- label: 'Спрос (%)',
- data: values,
- borderColor: '#3B82F6',
- backgroundColor: 'rgba(59, 130, 246, 0.1)',
- tension: 0.4,
- fill: true
- }
- ]
- };
-});
-
const seasonalityChartOptions = {
responsive: true,
maintainAspectRatio: false,
@@ -887,32 +779,6 @@ const seasonalityChartOptions = {
}
};
-const conversionFunnelChartData = computed(() => {
- if (!report.value?.chartsData?.conversionFunnel || report.value.chartsData.conversionFunnel.length === 0) return null;
- const funnel = report.value.chartsData.conversionFunnel;
-
- return {
- labels: funnel.map((item) => item.stage),
- datasets: [
- {
- label: 'Значение',
- data: funnel.map((item) => item.value),
- backgroundColor: '#3B82F6',
- borderColor: '#2563EB',
- borderWidth: 1
- },
- {
- label: 'Конверсия (%)',
- data: funnel.map((item) => item.conversion),
- backgroundColor: '#10B981',
- borderColor: '#059669',
- borderWidth: 1,
- yAxisID: 'y1'
- }
- ]
- };
-});
-
const conversionFunnelChartOptions = {
responsive: true,
maintainAspectRatio: false,
@@ -951,32 +817,6 @@ const conversionFunnelChartOptions = {
}
};
-const channelsPotentialChartData = computed(() => {
- if (!report.value?.chartsData?.channelsPotential || report.value.chartsData.channelsPotential.length === 0) return null;
- const channels = report.value.chartsData.channelsPotential;
-
- return {
- labels: channels.map((ch) => ch.name),
- datasets: [
- {
- label: 'Потенциал',
- data: channels.map((ch) => ch.potential),
- backgroundColor: channels.map((ch) => {
- if (ch.potential >= 80) return '#10B981';
- if (ch.potential >= 60) return '#F59E0B';
- return '#3B82F6';
- }),
- borderColor: channels.map((ch) => {
- if (ch.potential >= 80) return '#059669';
- if (ch.potential >= 60) return '#D97706';
- return '#2563EB';
- }),
- borderWidth: 1
- }
- ]
- };
-});
-
const channelsPotentialChartOptions = {
responsive: true,
maintainAspectRatio: false,
@@ -998,42 +838,233 @@ const channelsPotentialChartOptions = {
}
};
-const ageGroupsChartData = computed(() => {
- if (!report.value?.chartsData?.audienceSegmentation?.ageGroups || report.value.chartsData.audienceSegmentation.ageGroups.length === 0) return null;
- const ageGroups = report.value.chartsData.audienceSegmentation.ageGroups;
+const buildSeasonalityChartData = (seasonality) => {
+ if (!seasonality || typeof seasonality !== 'object') return null;
+ const months = Object.keys(seasonality);
+ const values = Object.values(seasonality);
+ if (!months.length) return null;
+ return {
+ labels: months,
+ datasets: [
+ {
+ label: 'Спрос (%)',
+ data: values,
+ borderColor: '#3B82F6',
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
+ tension: 0.4,
+ fill: true
+ }
+ ]
+ };
+};
+
+const buildConversionFunnelChartData = (funnel) => {
+ if (!Array.isArray(funnel) || funnel.length === 0) return null;
+ return {
+ labels: funnel.map((item) => item.stage),
+ datasets: [
+ {
+ label: 'Значение',
+ data: funnel.map((item) => item.value),
+ backgroundColor: '#3B82F6',
+ borderColor: '#2563EB',
+ borderWidth: 1
+ },
+ {
+ label: 'Конверсия (%)',
+ data: funnel.map((item) => item.conversion),
+ backgroundColor: '#10B981',
+ borderColor: '#059669',
+ borderWidth: 1,
+ yAxisID: 'y1'
+ }
+ ]
+ };
+};
+
+const buildChannelsPotentialChartData = (channels) => {
+ if (!Array.isArray(channels) || channels.length === 0) return null;
+ return {
+ labels: channels.map((ch) => ch.name),
+ datasets: [
+ {
+ label: 'Потенциал',
+ data: channels.map((ch) => ch.potential),
+ backgroundColor: channels.map((ch) => {
+ if (ch.potential >= 80) return '#10B981';
+ if (ch.potential >= 60) return '#F59E0B';
+ return '#3B82F6';
+ }),
+ borderColor: channels.map((ch) => {
+ if (ch.potential >= 80) return '#059669';
+ if (ch.potential >= 60) return '#D97706';
+ return '#2563EB';
+ }),
+ borderWidth: 1
+ }
+ ]
+ };
+};
+
+const buildAudienceSegmentationChartData = (segmentation) => {
+ if (!segmentation) return { ageGroupsChartData: null, gendersChartData: null };
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899'];
- return {
- labels: ageGroups.map((ag) => ag.label),
- datasets: [
- {
- data: ageGroups.map((ag) => parseFloat(ag.value)),
- backgroundColor: ageGroups.map((_, idx) => colors[idx % colors.length]),
- borderColor: '#fff',
- borderWidth: 2
+ const ageGroupsChartData =
+ Array.isArray(segmentation.ageGroups) && segmentation.ageGroups.length > 0
+ ? {
+ labels: segmentation.ageGroups.map((ag) => ag.label),
+ datasets: [
+ {
+ data: segmentation.ageGroups.map((ag) => parseFloat(ag.value)),
+ backgroundColor: segmentation.ageGroups.map((_, idx) => colors[idx % colors.length]),
+ borderColor: '#fff',
+ borderWidth: 2
+ }
+ ]
+ }
+ : null;
+
+ const gendersChartData =
+ Array.isArray(segmentation.genders) && segmentation.genders.length > 0
+ ? {
+ labels: segmentation.genders.map((g) => g.label),
+ datasets: [
+ {
+ data: segmentation.genders.map((g) => parseFloat(g.value)),
+ backgroundColor: segmentation.genders.map((_, idx) => ['#EC4899', '#3B82F6'][idx % 2]),
+ borderColor: '#fff',
+ borderWidth: 2
+ }
+ ]
+ }
+ : null;
+
+ return { ageGroupsChartData, gendersChartData };
+};
+
+const mapJsonBlockToSegment = (identifier, data, rawText) => {
+ const key = identifier?.toLowerCase();
+ switch (key) {
+ case 'seasonality': {
+ const chartData = buildSeasonalityChartData(data);
+ if (!chartData) return null;
+ return {
+ type: 'chart',
+ key: 'seasonality',
+ title: 'Сезонность спроса',
+ chartType: 'line',
+ data: chartData,
+ options: seasonalityChartOptions,
+ chartHeight: 'height: 300px'
+ };
+ }
+ case 'funnel':
+ case 'conversionfunnel': {
+ const chartData = buildConversionFunnelChartData(data);
+ if (!chartData) return null;
+ return {
+ type: 'chart',
+ key: 'funnel',
+ title: 'Воронка конверсии',
+ chartType: 'bar',
+ data: chartData,
+ options: conversionFunnelChartOptions,
+ chartHeight: 'height: 400px'
+ };
+ }
+ case 'channelspotential': {
+ const chartData = buildChannelsPotentialChartData(data);
+ if (!chartData) return null;
+ return {
+ type: 'chart',
+ key: 'channelsPotential',
+ title: 'Потенциал каналов продвижения',
+ chartType: 'bar',
+ data: chartData,
+ options: channelsPotentialChartOptions,
+ chartHeight: 'height: 300px',
+ channels: Array.isArray(data) ? data : []
+ };
+ }
+ case 'audiencesegmentation': {
+ const { ageGroupsChartData, gendersChartData } = buildAudienceSegmentationChartData(data || {});
+ if (!ageGroupsChartData && !gendersChartData) return null;
+ return {
+ type: 'audienceSegmentation',
+ ageGroupsChartData,
+ gendersChartData
+ };
+ }
+ case 'competitors': {
+ if (!Array.isArray(data) || data.length === 0) return null;
+ return {
+ type: 'competitors',
+ competitors: data
+ };
+ }
+ case 'swot': {
+ if (!data || typeof data !== 'object') return null;
+ return {
+ type: 'swot',
+ data: {
+ strengths: data.strengths || [],
+ weaknesses: data.weaknesses || [],
+ opportunities: data.opportunities || [],
+ threats: data.threats || []
+ }
+ };
+ }
+ default:
+ // Unknown block type: render raw markdown so nothing is lost
+ return {
+ type: 'markdown',
+ html: renderMarkdown(rawText || '')
+ };
+ }
+};
+
+const parsedContent = computed(() => {
+ if (!report.value?.fullAnalysis) return [];
+
+ const content = report.value.fullAnalysis;
+ const regex = /```json:(\w+)\n([\s\S]*?)\n```/g;
+ const segments = [];
+ let lastIndex = 0;
+ let match;
+
+ while ((match = regex.exec(content)) !== null) {
+ const [fullMatch, identifier, jsonBlock] = match;
+
+ if (match.index > lastIndex) {
+ const textBefore = content.slice(lastIndex, match.index);
+ if (textBefore.trim()) {
+ segments.push({ type: 'markdown', html: renderMarkdown(textBefore) });
}
- ]
- };
-});
+ }
-const gendersChartData = computed(() => {
- if (!report.value?.chartsData?.audienceSegmentation?.genders || report.value.chartsData.audienceSegmentation.genders.length === 0) return null;
- const genders = report.value.chartsData.audienceSegmentation.genders;
+ let parsedData = null;
+ try {
+ parsedData = JSON.parse(jsonBlock);
+ } catch (error) {
+ console.error(`Не удалось распарсить блок ${identifier}:`, error);
+ }
- const colors = ['#EC4899', '#3B82F6'];
+ const segment = mapJsonBlockToSegment(identifier, parsedData, fullMatch);
+ if (segment) {
+ segments.push(segment);
+ }
- return {
- labels: genders.map((g) => g.label),
- datasets: [
- {
- data: genders.map((g) => parseFloat(g.value)),
- backgroundColor: genders.map((_, idx) => colors[idx % colors.length]),
- borderColor: '#fff',
- borderWidth: 2
- }
- ]
- };
+ lastIndex = regex.lastIndex;
+ }
+
+ const remaining = content.slice(lastIndex);
+ if (remaining.trim()) {
+ segments.push({ type: 'markdown', html: renderMarkdown(remaining) });
+ }
+
+ return segments;
});
const pieChartOptions = {