fix
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
export const STRATEGY_MODELS = {
|
||||
ENTRY: {
|
||||
name: 'ENTRY',
|
||||
title: 'Entry — Вход / Формирование присутствия',
|
||||
description: 'Для новых бизнесов. Цель — узнаваемость и первые обращения.',
|
||||
color: '#6366F1', // indigo
|
||||
colorLight: '#EEF2FF',
|
||||
icon: '🚀',
|
||||
badge: 'Старт'
|
||||
},
|
||||
AUTHORITY: {
|
||||
name: 'AUTHORITY',
|
||||
title: 'Authority — Экспертность / Компетентность',
|
||||
description: 'Длинный цикл сделки, высокий чек, B2B. Цель — экспертный статус.',
|
||||
color: '#0EA5E9', // sky blue
|
||||
colorLight: '#F0F9FF',
|
||||
icon: '🏆',
|
||||
badge: 'Экспертиза'
|
||||
},
|
||||
TRUST: {
|
||||
name: 'TRUST',
|
||||
title: 'Trust — Усиление доверия / Перегретый рынок',
|
||||
description: 'Высокая конкуренция. Цель — снять недоверие через доказательства.',
|
||||
color: '#10B981', // emerald
|
||||
colorLight: '#ECFDF5',
|
||||
icon: '🛡️',
|
||||
badge: 'Доверие'
|
||||
},
|
||||
CONVERSION: {
|
||||
name: 'CONVERSION',
|
||||
title: 'Conversion — Прямая конверсия / Импульс',
|
||||
description: 'Быстрые покупки, B2C, визуальный продукт. Цель — максимум заявок.',
|
||||
color: '#F59E0B', // amber
|
||||
colorLight: '#FFFBEB',
|
||||
icon: '⚡',
|
||||
badge: 'Продажи'
|
||||
}
|
||||
};
|
||||
|
||||
export const DEFAULT_STRATEGY_MODEL = {
|
||||
title: 'Стратегия',
|
||||
icon: '📋',
|
||||
color: '#6B7280',
|
||||
colorLight: '#F9FAFB',
|
||||
badge: 'Стратегия'
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import MarketingV3Service from '@/service/MarketingV3Service';
|
||||
import { STRATEGY_MODELS, DEFAULT_STRATEGY_MODEL } from '@/config/marketingModels';
|
||||
import Button from 'primevue/button';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import Tag from 'primevue/tag';
|
||||
@@ -103,6 +104,24 @@ let pollInterval = null;
|
||||
let messageInterval = null;
|
||||
const strategyData = ref(null);
|
||||
const activeWeeks = ref([1]);
|
||||
const videoPollingIntervals = ref({});
|
||||
|
||||
const toggleMuteFromBtn = (event) => {
|
||||
const container = event.currentTarget.closest('.video-container');
|
||||
if (!container) return;
|
||||
const video = container.querySelector('video');
|
||||
if (video) {
|
||||
video.muted = !video.muted;
|
||||
const icon = event.currentTarget.querySelector('i');
|
||||
if (video.muted) {
|
||||
icon.className = 'pi pi-volume-off';
|
||||
event.currentTarget.title = "Включить звук";
|
||||
} else {
|
||||
icon.className = 'pi pi-volume-up';
|
||||
event.currentTarget.title = "Выключить звук";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toggleWeek = (week) => {
|
||||
if (activeWeeks.value.includes(week)) {
|
||||
@@ -189,6 +208,23 @@ const getPlatformIcon = (platform) => {
|
||||
return 'pi pi-share-alt';
|
||||
};
|
||||
|
||||
const getModelInfo = (modelName) => {
|
||||
return STRATEGY_MODELS[modelName?.toUpperCase()] || DEFAULT_STRATEGY_MODEL;
|
||||
};
|
||||
|
||||
// Clean rationale from debug scores
|
||||
const formatRationale = (text) => {
|
||||
if (!text) return '';
|
||||
return text.replace(/\(баллы:.*?\)\.?/g, '').trim();
|
||||
};
|
||||
|
||||
const getMediaState = (post) => {
|
||||
if (post.videoUrl === 'generating...') return 'video-generating';
|
||||
if (post.videoUrl && post.videoUrl !== 'generating...') return 'video';
|
||||
if (post.imageUrl) return 'image';
|
||||
return 'empty';
|
||||
};
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return '';
|
||||
return new Date(dateStr).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long' });
|
||||
@@ -208,6 +244,8 @@ const blobUrls = ref({});
|
||||
const loadMediaUrls = async () => {
|
||||
if (!strategyData.value?.postCalendar) return;
|
||||
for (const post of strategyData.value.postCalendar) {
|
||||
if (post.videoUrl === 'generating...') continue;
|
||||
|
||||
if (post.videoFilename && !blobUrls.value[post.videoFilename]) {
|
||||
MarketingV3Service.loadVideoAsBlobUrl(post.videoFilename).then((url) => {
|
||||
if (url) blobUrls.value[post.videoFilename] = url;
|
||||
@@ -218,11 +256,15 @@ const loadMediaUrls = async () => {
|
||||
if (url) blobUrls.value[post.imageFilename] = url;
|
||||
});
|
||||
}
|
||||
// Fallback for legacy data or if filenames are in mediaFilename
|
||||
if (!post.videoFilename && !post.imageFilename && post.mediaFilename && !blobUrls.value[post.mediaFilename]) {
|
||||
const method = post.contentType === 'видео' || post.contentType === 'video' || post.contentType === 'reels' ? 'loadVideoAsBlobUrl' : 'loadImageAsBlobUrl';
|
||||
MarketingV3Service[method](post.mediaFilename).then((url) => {
|
||||
if (url) blobUrls.value[post.mediaFilename] = url;
|
||||
// Fallback or explicit mapping
|
||||
if (post.imageUrl && !post.imageFilename && !blobUrls.value[post.imageUrl]) {
|
||||
MarketingV3Service.loadImageAsBlobUrl(post.imageUrl).then((url) => {
|
||||
if (url) blobUrls.value[post.imageUrl] = url;
|
||||
});
|
||||
}
|
||||
if (post.videoUrl && post.videoUrl !== 'generating...' && !post.videoFilename && !blobUrls.value[post.videoUrl]) {
|
||||
MarketingV3Service.loadVideoAsBlobUrl(post.videoUrl).then((url) => {
|
||||
if (url) blobUrls.value[post.videoUrl] = url;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -285,6 +327,13 @@ const loadStrategyById = async (id) => {
|
||||
if (data.status === 'completed' || data.status === 'COMPLETED') {
|
||||
status.value = 'COMPLETED';
|
||||
loadMediaUrls();
|
||||
if (data.postCalendar) {
|
||||
data.postCalendar.forEach((post, index) => {
|
||||
if (post.videoUrl === 'generating...') {
|
||||
pollPostVideo(id, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (data.status === 'failed' || data.status === 'FAILED') {
|
||||
status.value = 'FAILED';
|
||||
} else {
|
||||
@@ -306,16 +355,23 @@ const handleRegenerateMedia = async (type, post, index) => {
|
||||
try {
|
||||
regeneratingPosts.value.add(index);
|
||||
const method = type === 'video' ? 'regeneratePostVideo' : 'regeneratePostImage';
|
||||
const updatedPost = await MarketingV3Service[method](sid, index);
|
||||
const result = await MarketingV3Service[method](sid, index);
|
||||
|
||||
// Reactively update the post in the calendar
|
||||
if (type === 'video') {
|
||||
// Async video generation (202 Accepted)
|
||||
strategyData.value.postCalendar[index].videoUrl = 'generating...';
|
||||
pollPostVideo(sid, index);
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Генерация',
|
||||
detail: 'Видео генерируется через Veo 3...',
|
||||
life: 3000
|
||||
});
|
||||
} else {
|
||||
// Sync image generation
|
||||
const updatedPost = result;
|
||||
strategyData.value.postCalendar[index] = updatedPost;
|
||||
|
||||
// Load new media URLs immediately
|
||||
if (updatedPost.videoFilename) {
|
||||
const url = await MarketingV3Service.loadVideoAsBlobUrl(updatedPost.videoFilename);
|
||||
if (url) blobUrls.value[updatedPost.videoFilename] = url;
|
||||
}
|
||||
if (updatedPost.imageFilename) {
|
||||
const url = await MarketingV3Service.loadImageAsBlobUrl(updatedPost.imageFilename);
|
||||
if (url) blobUrls.value[updatedPost.imageFilename] = url;
|
||||
@@ -324,9 +380,10 @@ const handleRegenerateMedia = async (type, post, index) => {
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Обновлено',
|
||||
detail: type === 'video' ? 'ИИ сгенерировал видео!' : 'ИИ обновил изображение!',
|
||||
detail: 'ИИ обновил изображение!',
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Regeneration error:', e);
|
||||
toast.add({ severity: 'error', summary: 'Ошибка', detail: 'Не удалось перегенерировать медиа. Попробуйте позже.', life: 4000 });
|
||||
@@ -335,6 +392,27 @@ const handleRegenerateMedia = async (type, post, index) => {
|
||||
}
|
||||
};
|
||||
|
||||
const pollPostVideo = (sid, index) => {
|
||||
if (videoPollingIntervals.value[index]) return;
|
||||
videoPollingIntervals.value[index] = setInterval(async () => {
|
||||
try {
|
||||
const strategy = await MarketingV3Service.getStrategyById(sid);
|
||||
const post = strategy.postCalendar[index];
|
||||
if (post?.videoUrl && post.videoUrl !== 'generating...') {
|
||||
clearInterval(videoPollingIntervals.value[index]);
|
||||
delete videoPollingIntervals.value[index];
|
||||
strategyData.value.postCalendar[index] = post;
|
||||
if (post.videoFilename) {
|
||||
const url = await MarketingV3Service.loadVideoAsBlobUrl(post.videoFilename);
|
||||
if (url) blobUrls.value[post.videoFilename] = url;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Video polling error', e);
|
||||
}
|
||||
}, 15000);
|
||||
};
|
||||
|
||||
// B3: Start Strategy
|
||||
const isAutoPostingStarted = ref(false);
|
||||
const startingStrategy = ref(false);
|
||||
@@ -379,6 +457,8 @@ onUnmounted(() => {
|
||||
stopPolling();
|
||||
window.removeEventListener('paste', handlePaste);
|
||||
|
||||
Object.values(videoPollingIntervals.value).forEach(clearInterval);
|
||||
|
||||
// Revoke any pending ObjectURLs
|
||||
selectedFiles.value.forEach((item) => {
|
||||
if (item.previewUrl) URL.revokeObjectURL(item.previewUrl);
|
||||
@@ -398,7 +478,11 @@ onUnmounted(() => {
|
||||
<span class="px-2 py-1 rounded-md bg-emerald-100 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-400 text-[10px] font-bold tracking-wider uppercase">Content Factory V3</span>
|
||||
</div>
|
||||
<h1 class="text-3xl font-extrabold text-slate-900 dark:text-white tracking-tight">Генерация стратегии</h1>
|
||||
<p class="text-slate-500 dark:text-slate-400 mt-1">Создание визуального и текстового контента на основе вашего анализа</p>
|
||||
<div v-if="strategyData?.strategyData?.modelTitle || strategyData?.modelTitle" class="mt-2 flex items-center gap-2">
|
||||
<span class="text-[10px] font-black uppercase tracking-widest text-emerald-500 bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20">Модель:</span>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ strategyData?.strategyData?.modelTitle || strategyData?.modelTitle }}</span>
|
||||
</div>
|
||||
<p v-else class="text-slate-500 dark:text-slate-400 mt-1">Создание визуального и текстового контента на основе вашего анализа</p>
|
||||
</div>
|
||||
<Button label="К анализу" icon="pi pi-arrow-left" severity="secondary" text class="rounded-xl hover:bg-white dark:hover:bg-slate-900 shadow-sm border border-slate-200 dark:border-slate-800" @click="goBack" />
|
||||
</div>
|
||||
@@ -418,25 +502,60 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="previewData" class="card relative p-8 bg-gradient-to-br from-emerald-500 to-emerald-600 dark:from-emerald-600 dark:to-emerald-800 border-none shadow-2xl shadow-emerald-200 dark:shadow-none overflow-hidden">
|
||||
<!-- Abstract Background elements -->
|
||||
<div class="absolute top-0 right-0 -mt-10 -mr-10 w-64 h-64 bg-white/10 blur-3xl rounded-full"></div>
|
||||
<div class="absolute bottom-0 left-0 -mb-10 -ml-10 w-48 h-48 bg-black/5 blur-2xl rounded-full"></div>
|
||||
|
||||
<div class="flex flex-col md:flex-row items-start md:items-center gap-6 relative z-10 text-white">
|
||||
<div class="p-4 bg-white/20 backdrop-blur-md rounded-2xl border border-white/30 shadow-inner">
|
||||
<i class="pi pi-sparkles text-3xl animate-pulse" />
|
||||
<div v-else-if="previewData" class="card relative p-8 bg-white dark:bg-slate-900 border-none shadow-xl overflow-hidden">
|
||||
<div class="flex flex-col md:flex-row items-center gap-8 relative z-10">
|
||||
<!-- Model Icon Badge -->
|
||||
<div
|
||||
:style="{ backgroundColor: getModelInfo(previewData.recommendedModel).colorLight, color: getModelInfo(previewData.recommendedModel).color }"
|
||||
class="w-24 h-24 rounded-3xl flex items-center justify-center text-4xl shadow-sm shrink-0"
|
||||
>
|
||||
{{ getModelInfo(previewData.recommendedModel).icon }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-xl font-black mb-2 flex items-center gap-2">
|
||||
{{ previewData.modelTitle || 'Оптимальная стратегия' }}
|
||||
<i class="pi pi-info-circle text-xs opacity-60" />
|
||||
|
||||
<div class="flex-1 text-center md:text-left">
|
||||
<div class="flex items-center justify-center md:justify-start gap-2 mb-2">
|
||||
<span :style="{ color: getModelInfo(previewData.recommendedModel).color }" class="text-[10px] font-black uppercase tracking-[0.2em]">Система рекомендует</span>
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
|
||||
</div>
|
||||
<h3 class="text-3xl font-black mb-1 tracking-tight text-slate-900 dark:text-white">
|
||||
{{ previewData.modelTitle || getModelInfo(previewData.recommendedModel).title }}
|
||||
</h3>
|
||||
<p class="text-emerald-50/90 leading-relaxed font-medium">
|
||||
{{ previewData.description }}
|
||||
<p class="text-slate-500 dark:text-slate-400 leading-relaxed font-medium">
|
||||
{{ previewData.description || getModelInfo(previewData.recommendedModel).description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Score Bars in Preview -->
|
||||
<div v-if="previewData.scores" class="mt-10 grid grid-cols-1 md:grid-cols-2 gap-6 p-6 rounded-2xl bg-slate-50 dark:bg-slate-800/40 border border-slate-100 dark:border-slate-800">
|
||||
<div v-for="(score, key) in previewData.scores" :key="key" class="space-y-2">
|
||||
<div class="flex justify-between items-center text-[10px] font-black uppercase tracking-widest">
|
||||
<span :style="{ color: STRATEGY_MODELS[key.toUpperCase()]?.color }">{{ STRATEGY_MODELS[key.toUpperCase()]?.badge }}</span>
|
||||
<span class="text-slate-400">{{ score }}</span>
|
||||
</div>
|
||||
<div class="h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
class="h-full rounded-full transition-all duration-1000"
|
||||
:style="{
|
||||
width: `${Math.round((score / Math.max(...Object.values(previewData.scores))) * 100)}%`,
|
||||
backgroundColor: STRATEGY_MODELS[key.toUpperCase()]?.color,
|
||||
opacity: key.toUpperCase() === previewData.recommendedModel ? 1 : 0.4
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Modifiers -->
|
||||
<div v-if="previewData.activeModifiers?.length" class="mt-6 flex flex-wrap gap-2 justify-center md:justify-start">
|
||||
<span
|
||||
v-for="mod in previewData.activeModifiers"
|
||||
:key="mod.key"
|
||||
class="px-3 py-1.5 rounded-xl bg-emerald-50 dark:bg-emerald-900/20 text-emerald-600 dark:text-emerald-400 text-xs font-bold border border-emerald-100 dark:border-emerald-800"
|
||||
>
|
||||
# {{ mod.title }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -568,40 +687,96 @@ onUnmounted(() => {
|
||||
<!-- 4. STAGE: COMPLETED (Result Display) -->
|
||||
<div v-else-if="status === 'COMPLETED' && strategyData" class="space-y-10 animate-in fade-in slide-in-from-bottom-5 duration-1000">
|
||||
<!-- Strategy Rationale Banner -->
|
||||
<div class="card p-8 md:p-12 bg-slate-900 dark:bg-black text-white relative overflow-hidden group">
|
||||
<div class="absolute top-0 right-0 w-96 h-96 bg-emerald-500/20 blur-3xl rounded-full -mr-48 -mt-48 group-hover:bg-emerald-500/30 transition-colors duration-1000"></div>
|
||||
|
||||
<div class="relative z-10 flex flex-col md:flex-row gap-10 items-start">
|
||||
<div class="flex-1">
|
||||
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-emerald-500/20 text-emerald-400 text-[10px] font-black uppercase tracking-wider border border-emerald-500/30 mb-6">
|
||||
<span class="relative flex h-2 w-2">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
|
||||
</span>
|
||||
Готовый результат
|
||||
<div class="card p-0 bg-white dark:bg-slate-900 border-none shadow-xl overflow-hidden">
|
||||
<div class="p-8 md:p-10">
|
||||
<!-- Main Info Grid -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-10 items-stretch">
|
||||
<!-- Left Column: Model & Rationale -->
|
||||
<div class="lg:col-span-7 xl:col-span-8 space-y-8">
|
||||
<div class="flex items-center gap-6">
|
||||
<div
|
||||
:style="{ backgroundColor: getModelInfo(strategyData.scoringModelName).colorLight, color: getModelInfo(strategyData.scoringModelName).color, borderColor: getModelInfo(strategyData.scoringModelName).color + '20' }"
|
||||
class="w-24 h-24 rounded-[2.5rem] flex items-center justify-center text-5xl shadow-2xl shadow-inner shrink-0 border-2 transition-transform duration-700 hover:rotate-12"
|
||||
>
|
||||
{{ getModelInfo(strategyData.scoringModelName).icon }}
|
||||
</div>
|
||||
<h2 class="text-3xl font-black mb-6 tracking-tight">Стратегическое обоснование</h2>
|
||||
<p class="text-slate-400 leading-relaxed text-lg font-medium whitespace-pre-line">
|
||||
{{ strategyData.strategyData?.rationale }}
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-2 text-[10px] font-black uppercase tracking-[0.3em] mb-2">
|
||||
<span :style="{ color: getModelInfo(strategyData.scoringModelName).color }">Модель бизнеса</span>
|
||||
<span class="px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-500 border border-slate-200/50 dark:border-slate-700/50">{{ strategyData.scoringModelName || 'STRATEGY' }}</span>
|
||||
</div>
|
||||
<h2 class="text-3xl md:text-4xl lg:text-5xl font-black text-slate-900 dark:text-white leading-tight break-words">
|
||||
{{ strategyData.scoringModelTitle || getModelInfo(strategyData.scoringModelName).title }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative group/rationale">
|
||||
<div class="absolute -inset-1 bg-gradient-to-r from-emerald-500/20 to-blue-500/20 rounded-3xl blur opacity-25 group-hover/rationale:opacity-50 transition duration-1000"></div>
|
||||
<div class="relative p-8 rounded-3xl bg-white dark:bg-slate-800 border border-slate-100 dark:border-slate-700 shadow-sm">
|
||||
<p class="text-slate-500 dark:text-slate-400 text-[10px] font-black uppercase tracking-[0.3em] mb-4 flex items-center gap-2">
|
||||
<i class="pi pi-info-circle text-emerald-500" /> Обоснование стратегии
|
||||
</p>
|
||||
<p class="text-slate-700 dark:text-slate-300 leading-relaxed font-semibold italic text-xl lg:text-2xl font-serif">
|
||||
«{{ formatRationale(strategyData.strategyData?.scoringRationale || strategyData.strategyData?.rationale) }}»
|
||||
</p>
|
||||
</div>
|
||||
<div class="w-full md:w-72 space-y-4">
|
||||
<div class="p-5 rounded-2xl bg-white/5 border border-white/10 backdrop-blur-sm">
|
||||
<p class="text-slate-400 text-[10px] font-black uppercase mb-2 flex items-center gap-2"><i class="pi pi-calendar" /> Длительность кампании</p>
|
||||
<p class="font-bold text-slate-200 text-xl">{{ strategyData.durationWeeks || strategyData.strategyData?.durationWeeks || 4 }} <span class="text-sm font-medium text-slate-500">недели</span></p>
|
||||
</div>
|
||||
<div class="p-5 rounded-2xl bg-emerald-500/10 border border-emerald-500/20 backdrop-blur-sm">
|
||||
<p class="text-emerald-500 text-[10px] font-black uppercase mb-3 flex items-center gap-2"><i class="pi pi-sitemap" /> Рекомендованные платформы</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Score Bars -->
|
||||
<div v-if="strategyData.strategyData?.scores" class="lg:col-span-5 xl:col-span-4">
|
||||
<div class="p-8 rounded-[2.5rem] bg-slate-50 dark:bg-slate-800/40 border border-slate-100 dark:border-slate-800 h-full flex flex-col justify-center">
|
||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] mb-8 flex items-center justify-between">
|
||||
<span>Веса стратегии</span>
|
||||
<i class="pi pi-chart-bar text-emerald-500" />
|
||||
</p>
|
||||
<div class="space-y-6">
|
||||
<div v-for="(score, key) in strategyData.strategyData.scores" :key="key" class="space-y-2 group/bar">
|
||||
<div class="flex justify-between items-center text-[10px] font-black uppercase tracking-widest">
|
||||
<span :style="{ color: STRATEGY_MODELS[key.toUpperCase()]?.color }" class="group-hover/bar:translate-x-1 transition-transform inline-block">{{ STRATEGY_MODELS[key.toUpperCase()]?.badge }}</span>
|
||||
<span class="text-slate-400 bg-white dark:bg-slate-900 px-2 py-1 rounded-lg shadow-sm border border-slate-100 dark:border-slate-800">{{ score }}</span>
|
||||
</div>
|
||||
<div class="h-3 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden shadow-inner p-0.5">
|
||||
<div
|
||||
v-for="platform in strategyData.priorityPlatforms || strategyData.strategyData?.priorityPlatforms || []"
|
||||
:key="platform"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-emerald-500/20 text-emerald-400 text-xs font-bold border border-emerald-500/30"
|
||||
class="h-full rounded-full transition-all duration-1000 relative overflow-hidden"
|
||||
:style="{
|
||||
width: `${Math.round((score / Math.max(...Object.values(strategyData.strategyData.scores))) * 100)}%`,
|
||||
backgroundColor: STRATEGY_MODELS[key.toUpperCase()]?.color,
|
||||
opacity: key.toUpperCase() === strategyData.scoringModelName ? 1 : 0.4
|
||||
}"
|
||||
>
|
||||
<i :class="getPlatformIcon(platform)"></i>
|
||||
{{ platform }}
|
||||
<div v-if="key.toUpperCase() === strategyData.scoringModelName" class="absolute inset-0 bg-white/30 animate-pulse"></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent skew-x-12 -translate-x-full group-hover/bar:translate-x-full transition-transform duration-1000"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex flex-wrap gap-4 pt-8 border-t border-slate-100 dark:border-slate-800">
|
||||
<div class="flex items-center gap-4 group/item">
|
||||
<div class="w-12 h-12 rounded-2xl bg-emerald-50 dark:bg-emerald-900/20 text-emerald-500 flex items-center justify-center shadow-sm group-hover/item:scale-110 transition-transform">
|
||||
<i class="pi pi-calendar text-xl" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-[0.2em]">Длительность</p>
|
||||
<p class="font-black text-slate-900 dark:text-white text-lg">{{ strategyData.durationWeeks || strategyData.strategyData?.durationWeeks || 4 }} <span class="text-xs font-medium text-slate-500 uppercase tracking-widest ml-1">недели</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 group/item">
|
||||
<div class="w-12 h-12 rounded-2xl bg-blue-50 dark:bg-blue-900/20 text-blue-500 flex items-center justify-center shadow-sm group-hover/item:scale-110 transition-transform">
|
||||
<i class="pi pi-sitemap text-xl" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-[0.2em]">Платформы</p>
|
||||
<div class="flex gap-2">
|
||||
<div v-for="p in strategyData.priorityPlatforms || strategyData.strategyData?.priorityPlatforms || []" :key="p" class="w-8 h-8 rounded-lg bg-white dark:bg-slate-800 flex items-center justify-center border border-slate-100 dark:border-slate-700 shadow-sm transition-all hover:border-blue-500/50 hover:-translate-y-1">
|
||||
<i :class="getPlatformIcon(p)" class="text-slate-600 dark:text-slate-400" v-tooltip.top="p" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!(strategyData.priorityPlatforms || strategyData.strategyData?.priorityPlatforms)?.length" class="text-slate-400 text-sm font-medium">Универсальная стратегия</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -659,15 +834,12 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Calendar -->
|
||||
<!-- Post Calendar -->
|
||||
<div v-if="strategyData.postCalendar?.length">
|
||||
<div class="flex items-center justify-between mb-8 px-2">
|
||||
<h3 class="text-xl font-black text-slate-900 dark:text-white flex items-center gap-3">
|
||||
<h3 class="text-xl font-black text-slate-900 dark:text-white mb-6 px-2 flex items-center gap-3">
|
||||
<i class="pi pi-images text-emerald-500" />
|
||||
Медиа-активы и посты
|
||||
Контент-план (Посты)
|
||||
</h3>
|
||||
<Button label="Скачать календарь" icon="pi pi-download" size="small" severity="secondary" rounded text class="font-bold" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<div
|
||||
@@ -676,56 +848,104 @@ onUnmounted(() => {
|
||||
class="card p-0 overflow-hidden bg-white dark:bg-slate-900 border-none shadow-xl shadow-slate-200/50 dark:shadow-none hover:-translate-y-2 transition-transform duration-500 group"
|
||||
>
|
||||
<!-- Media Header -->
|
||||
<div class="aspect-square relative bg-slate-100 dark:bg-slate-800 flex items-center justify-center overflow-hidden">
|
||||
<div :class="[
|
||||
'relative w-full overflow-hidden group/media bg-slate-100 dark:bg-slate-800 flex flex-col items-center justify-center',
|
||||
(getMediaState(post).includes('video') || (getMediaState(post) === 'empty' && !post.imageUrl)) ? 'aspect-[9/16]' : 'aspect-[3/4]'
|
||||
]">
|
||||
<!-- Loading Overlay -->
|
||||
<div v-if="regeneratingPosts.has(idx)" class="absolute inset-0 z-30 bg-black/60 backdrop-blur-md flex flex-col items-center justify-center text-white p-6 text-center">
|
||||
<i class="pi pi-spin pi-spinner text-4xl mb-3 text-emerald-400" />
|
||||
<div v-if="regeneratingPosts.has(idx) && getMediaState(post) !== 'video-generating'" class="absolute inset-0 z-30 bg-white/90 dark:bg-slate-900/90 backdrop-blur-md flex flex-col items-center justify-center text-slate-900 dark:text-white p-6 text-center">
|
||||
<i class="pi pi-spin pi-spinner text-4xl mb-3 text-emerald-500" />
|
||||
<p class="text-sm font-bold animate-pulse">Магия нейросетей в процессе...</p>
|
||||
<p class="text-[10px] opacity-60 mt-2">Генерируем контент... (до 2 минут)</p>
|
||||
</div>
|
||||
|
||||
<!-- Smart Content Rendering -->
|
||||
<template v-if="post.contentType === 'видео'">
|
||||
<template v-if="getMediaState(post) === 'video-generating'">
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center bg-gradient-to-br from-slate-800 to-slate-900 gap-3">
|
||||
<div class="relative">
|
||||
<div class="w-16 h-16 rounded-full bg-emerald-500/20 animate-ping absolute inset-0" />
|
||||
<div class="w-16 h-16 rounded-full bg-emerald-500/30 flex items-center justify-center relative shadow-[0_0_15px_rgba(16,185,129,0.5)]">
|
||||
<i class="pi pi-video text-emerald-400 text-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center px-4 mt-2">
|
||||
<p class="text-white font-medium text-sm">Veo 3 генерирует видео</p>
|
||||
<p class="text-slate-400 text-xs mt-1">Обычно занимает 3–8 минут</p>
|
||||
</div>
|
||||
<div class="w-32 h-1 bg-slate-700/50 rounded-full overflow-hidden mt-1">
|
||||
<div class="h-full bg-emerald-500 rounded-full w-full animate-pulse blur-[1px]"></div>
|
||||
</div>
|
||||
<p class="text-slate-500/80 text-[10px] mt-1 uppercase tracking-widest font-black">со звуком 🔊</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="getMediaState(post) === 'video'">
|
||||
<div class="absolute inset-0 bg-black video-container flex items-center justify-center">
|
||||
<video
|
||||
v-if="post.videoFilename && getMediaUrl(post.videoFilename)"
|
||||
:src="getMediaUrl(post.videoFilename)"
|
||||
v-if="getMediaUrl(post.videoFilename || post.videoUrl)"
|
||||
:src="getMediaUrl(post.videoFilename || post.videoUrl)"
|
||||
:poster="getMediaUrl(post.imageFilename || post.imageUrl) || undefined"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
controls
|
||||
class="w-full h-full object-cover transition-transform duration-700"
|
||||
:controls="false"
|
||||
class="w-full h-full object-cover transition-transform duration-700 group-hover/media:scale-105"
|
||||
/>
|
||||
<div v-else class="text-center p-8 opacity-40 bg-slate-200 dark:bg-slate-800 w-full h-full flex flex-col items-center justify-center">
|
||||
<i class="pi pi-exclamation-triangle text-5xl mb-2" />
|
||||
<p class="text-[10px] font-bold uppercase italic">Не удалось сгенерировать видео</p>
|
||||
<p class="text-[10px] font-bold uppercase italic">Не удалось загрузить видео</p>
|
||||
</div>
|
||||
|
||||
<!-- Mute Button -->
|
||||
<button v-if="getMediaUrl(post.videoFilename || post.videoUrl)"
|
||||
@click="toggleMuteFromBtn($event)"
|
||||
class="absolute bottom-3 right-3 bg-black/60 hover:bg-black/80 text-white rounded-full w-9 h-9 flex items-center justify-center transition-all backdrop-blur-sm z-20"
|
||||
title="Включить звук"
|
||||
>
|
||||
<i class="pi pi-volume-off" />
|
||||
</button>
|
||||
|
||||
<!-- Label Veo 3 -->
|
||||
<div class="absolute top-2 right-2 bg-black/60 text-white text-[10px] px-2 py-1 rounded-full z-20 tracking-wide font-bold backdrop-blur-sm shadow-sm border border-white/10">
|
||||
Veo 3 · AI видео
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-4 right-4 px-2 py-1 bg-black/60 rounded backdrop-blur-md text-[8px] font-bold text-white uppercase tracking-tighter z-10">Video AI</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="post.contentType === 'фото'">
|
||||
<img v-if="post.imageFilename && getMediaUrl(post.imageFilename)" :src="getMediaUrl(post.imageFilename)" class="w-full h-full object-cover transition-transform duration-700" />
|
||||
<template v-else-if="getMediaState(post) === 'image'">
|
||||
<img v-if="getMediaUrl(post.imageFilename || post.imageUrl)" :src="getMediaUrl(post.imageFilename || post.imageUrl)" class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover/media:scale-105" />
|
||||
<div v-else class="text-center p-8 opacity-40 bg-slate-200 dark:bg-slate-800 w-full h-full flex flex-col items-center justify-center">
|
||||
<i class="pi pi-exclamation-triangle text-5xl mb-2" />
|
||||
<p class="text-[10px] font-bold uppercase italic">Не удалось сгенерировать фото</p>
|
||||
<p class="text-[10px] font-bold uppercase italic">Не удалось загрузить фото</p>
|
||||
</div>
|
||||
<div class="absolute top-4 right-4 px-2 py-1 bg-black/60 rounded backdrop-blur-md text-[8px] font-bold text-white uppercase tracking-tighter z-10">Image AI</div>
|
||||
<div class="absolute top-2 right-2 px-2 py-1 bg-black/60 rounded-full backdrop-blur-md text-[10px] font-bold text-white tracking-wide border border-white/10 z-10">AI Photo</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<!-- Fallback for other potential types or old data -->
|
||||
<img v-if="getMediaUrl(post.imageFilename || post.mediaFilename)" :src="getMediaUrl(post.imageFilename || post.mediaFilename)" class="w-full h-full object-cover" />
|
||||
<video v-else-if="getMediaUrl(post.videoFilename)" :src="getMediaUrl(post.videoFilename)" autoplay loop muted playsinline controls class="w-full h-full object-cover" />
|
||||
<div v-else class="text-center p-8 opacity-40 bg-slate-200 dark:bg-slate-800 w-full h-full flex flex-col items-center justify-center">
|
||||
<i class="pi pi-images text-5xl mb-2" />
|
||||
<p class="text-[10px] font-bold uppercase italic">Медиа недоступно</p>
|
||||
<div class="absolute inset-0 bg-slate-100 dark:bg-slate-800 border-2 border-dashed border-slate-300 dark:border-slate-600 flex flex-col items-center justify-center gap-3 cursor-pointer hover:border-emerald-400 dark:hover:border-emerald-500 transition-colors group/empty m-2 rounded-xl"
|
||||
@click="handleRegenerateMedia('video', post, idx)">
|
||||
|
||||
<div class="w-14 h-14 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center group-hover/empty:scale-110 transition-transform">
|
||||
<i class="pi pi-video text-emerald-500 text-2xl" />
|
||||
</div>
|
||||
|
||||
<div class="text-center px-4">
|
||||
<p class="text-slate-700 dark:text-slate-300 font-medium text-sm">
|
||||
{{ regeneratingPosts.has(idx) ? 'Запускаю генерацию...' : 'Сгенерировать видео' }}
|
||||
</p>
|
||||
<p class="text-slate-500 text-xs mt-1">Veo 3 · со звуком · 9:16</p>
|
||||
</div>
|
||||
|
||||
<button v-if="!regeneratingPosts.has(idx)" class="mt-2 px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white text-xs rounded-lg font-medium transition-colors shadow-sm">
|
||||
🎬 Создать видео
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="absolute inset-0 bg-emerald-500/10 opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||
<div class="absolute top-4 left-4 flex gap-2">
|
||||
<div class="p-2 rounded-xl bg-white/90 dark:bg-slate-900/90 backdrop-blur-md shadow-lg">
|
||||
<i :class="getPlatformIcon(post.platform)" class="text-lg text-emerald-500" />
|
||||
<div class="absolute inset-0 bg-emerald-500/10 opacity-0 group-hover/media:opacity-100 transition-opacity pointer-events-none"></div>
|
||||
<div class="absolute top-2 left-2 flex gap-2 z-10">
|
||||
<div class="p-1.5 rounded-lg bg-white/90 dark:bg-slate-900/90 backdrop-blur-md shadow-lg border border-slate-100 dark:border-slate-800">
|
||||
<i :class="getPlatformIcon(post.platform)" class="text-base text-emerald-500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -742,6 +962,10 @@ onUnmounted(() => {
|
||||
|
||||
<div class="p-4 rounded-xl bg-slate-50 dark:bg-slate-800/40 border border-slate-100 dark:border-slate-800 mb-6 group-hover:bg-emerald-50/30 dark:group-hover:bg-emerald-900/10 transition-colors">
|
||||
<p class="text-[13px] text-slate-600 dark:text-slate-300 line-clamp-4 font-medium leading-relaxed">{{ post.postText }}</p>
|
||||
<!-- Hashtags -->
|
||||
<div v-if="post.hashtags?.length" class="mt-3 flex flex-wrap gap-1">
|
||||
<span v-for="tag in post.hashtags" :key="tag" class="text-[10px] font-bold text-emerald-600 dark:text-emerald-400">{{ tag.startsWith('#') ? tag : '#' + tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 mb-4">
|
||||
@@ -756,11 +980,12 @@ onUnmounted(() => {
|
||||
@click="handleRegenerateMedia('image', post, idx)"
|
||||
/>
|
||||
<Button
|
||||
v-if="getMediaState(post) !== 'video-generating'"
|
||||
icon="pi pi-video"
|
||||
label="Сделать видео"
|
||||
:label="getMediaState(post) === 'video' ? 'Новое видео' : 'Видео + звук'"
|
||||
class="flex-1 text-[9px] font-black uppercase tracking-wider rounded-xl py-2"
|
||||
severity="secondary"
|
||||
outlined
|
||||
:severity="getMediaState(post) === 'video' ? 'secondary' : 'success'"
|
||||
:outlined="getMediaState(post) === 'video'"
|
||||
:disabled="regeneratingPosts.has(idx)"
|
||||
@click="handleRegenerateMedia('video', post, idx)"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import MarketingV3Service from '@/service/MarketingV3Service';
|
||||
import { STRATEGY_MODELS, DEFAULT_STRATEGY_MODEL } from '@/config/marketingModels';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
@@ -96,6 +97,18 @@ const getPlatformIcon = (platform) => {
|
||||
return 'pi pi-share-alt';
|
||||
};
|
||||
|
||||
const getModelInfo = (modelName) => {
|
||||
if (!modelName) return DEFAULT_STRATEGY_MODEL;
|
||||
return STRATEGY_MODELS[modelName.toUpperCase()] || DEFAULT_STRATEGY_MODEL;
|
||||
};
|
||||
|
||||
const getLatestStatusMessage = (item) => {
|
||||
if (item.statusHistory && item.statusHistory.length > 0) {
|
||||
return item.statusHistory[item.statusHistory.length - 1].message;
|
||||
}
|
||||
return item.status === 'QUEUED' ? 'В очереди...' : 'Фабрика генерирует контент...';
|
||||
};
|
||||
|
||||
// Actions
|
||||
const openStrategy = (item) => {
|
||||
const s = (item.status || '').toUpperCase();
|
||||
@@ -184,9 +197,11 @@ onMounted(load);
|
||||
<div
|
||||
v-for="item in paginated"
|
||||
:key="item.strategyId || item.id"
|
||||
class="card cursor-pointer hover:-translate-y-0.5 hover:shadow-md transition-all duration-200 border border-surface-200 dark:border-surface-700"
|
||||
class="card group relative overflow-hidden cursor-pointer hover:-translate-y-1 hover:shadow-2xl hover:shadow-emerald-500/10 transition-all duration-300 border border-surface-200 dark:border-surface-800 bg-white/50 dark:bg-surface-900/50 backdrop-blur-sm"
|
||||
@click="openStrategy(item)"
|
||||
>
|
||||
<!-- Background Glow on Hover -->
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-emerald-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-4">
|
||||
<!-- Status badge -->
|
||||
<div class="shrink-0">
|
||||
@@ -198,12 +213,22 @@ onMounted(load);
|
||||
<!-- Main info -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-2 mb-1">
|
||||
<h3 class="text-sm font-bold text-surface-900 dark:text-surface-0 truncate">
|
||||
{{ item.analysisData?.requestData?.businessNiche || 'Стратегия #' + (item.strategyId || item.id || '').slice(-6) }}
|
||||
<h3 class="text-base font-extrabold text-surface-900 dark:text-surface-0 truncate group-hover:text-emerald-600 dark:group-hover:text-emerald-400 transition-colors">
|
||||
{{ item.analysisTitle || (item.analysisData?.requestData?.productName ? `${item.analysisData.requestData.productName} — ${item.analysisData.requestData.businessNiche}` : (item.analysisData?.requestData?.businessNiche || 'Стратегия #' + (item.strategyId || item.id || '').slice(-6))) }}
|
||||
</h3>
|
||||
<span :class="['inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold', statusTagClass(item.status)]">
|
||||
<div class="flex items-center gap-2">
|
||||
<span :class="['inline-flex items-center px-1.5 py-0.5 rounded-lg text-[10px] font-black uppercase tracking-wider', statusTagClass(item.status)]">
|
||||
{{ statusLabel(item.status) }}
|
||||
</span>
|
||||
<!-- Strategy Model Badge -->
|
||||
<div
|
||||
:style="{ backgroundColor: getModelInfo(item.scoringModelName).colorLight, color: getModelInfo(item.scoringModelName).color, borderColor: getModelInfo(item.scoringModelName).color + '20' }"
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-black uppercase tracking-widest border shadow-sm transition-transform group-hover:scale-105"
|
||||
>
|
||||
<span class="text-xs">{{ getModelInfo(item.scoringModelName).icon }}</span>
|
||||
{{ getModelInfo(item.scoringModelName).badge }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-surface-500 dark:text-surface-400 mt-2">
|
||||
<span v-if="item.durationWeeks || item.strategyData?.durationWeeks" class="flex items-center gap-1 font-medium"> <i class="pi pi-calendar" /> {{ item.durationWeeks || item.strategyData?.durationWeeks }} недель </span>
|
||||
@@ -237,7 +262,7 @@ onMounted(load);
|
||||
<!-- Progress bar for PROCESSING -->
|
||||
<div v-if="item.status === 'PROCESSING' || item.status === 'QUEUED'" class="mt-3">
|
||||
<div class="flex justify-between text-xs text-surface-500 dark:text-surface-400 mb-1">
|
||||
<span>{{ item.status === 'QUEUED' ? 'В очереди...' : 'Фабрика генерирует контент...' }}</span>
|
||||
<span>{{ getLatestStatusMessage(item) }}</span>
|
||||
</div>
|
||||
<div class="h-1.5 bg-surface-200 dark:bg-surface-700 rounded-full overflow-hidden">
|
||||
<div class="h-1.5 rounded-full bg-gradient-to-r from-emerald-400 to-emerald-600 animate-pulse" :style="{ width: item.status === 'QUEUED' ? '10%' : '60%' }" />
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// vite.config.mjs
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
import { PrimeVueResolver } from "file:///C:/Users/777/IdeaProjects/marketing/node_modules/@primevue/auto-import-resolver/index.mjs";
|
||||
import vue from "file:///C:/Users/777/IdeaProjects/marketing/node_modules/@vitejs/plugin-vue/dist/index.mjs";
|
||||
import Components from "file:///C:/Users/777/IdeaProjects/marketing/node_modules/unplugin-vue-components/dist/vite.js";
|
||||
import { defineConfig } from "file:///C:/Users/777/IdeaProjects/marketing/node_modules/vite/dist/node/index.js";
|
||||
var __vite_injected_original_import_meta_url = "file:///C:/Users/777/IdeaProjects/marketing/vite.config.mjs";
|
||||
var vite_config_default = defineConfig({
|
||||
optimizeDeps: {
|
||||
noDiscovery: true
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
Components({
|
||||
resolvers: [PrimeVueResolver()]
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", __vite_injected_original_import_meta_url))
|
||||
}
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "https://api.konturai.kz",
|
||||
changeOrigin: true,
|
||||
secure: true,
|
||||
configure: (proxy, _options) => {
|
||||
proxy.on("error", (err, _req, _res) => {
|
||||
console.log("proxy error", err);
|
||||
});
|
||||
proxy.on("proxyReq", (proxyReq, req, _res) => {
|
||||
console.log("Sending Request to the Target:", req.method, req.url);
|
||||
});
|
||||
proxy.on("proxyRes", (proxyRes, req, _res) => {
|
||||
console.log("Received Response from the Target:", proxyRes.statusCode, req.url);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
export {
|
||||
vite_config_default as default
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcubWpzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiQzpcXFxcVXNlcnNcXFxcNzc3XFxcXElkZWFQcm9qZWN0c1xcXFxtYXJrZXRpbmdcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFVzZXJzXFxcXDc3N1xcXFxJZGVhUHJvamVjdHNcXFxcbWFya2V0aW5nXFxcXHZpdGUuY29uZmlnLm1qc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovVXNlcnMvNzc3L0lkZWFQcm9qZWN0cy9tYXJrZXRpbmcvdml0ZS5jb25maWcubWpzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnO1xuXG5pbXBvcnQgeyBQcmltZVZ1ZVJlc29sdmVyIH0gZnJvbSAnQHByaW1ldnVlL2F1dG8taW1wb3J0LXJlc29sdmVyJztcbmltcG9ydCB2dWUgZnJvbSAnQHZpdGVqcy9wbHVnaW4tdnVlJztcbmltcG9ydCBDb21wb25lbnRzIGZyb20gJ3VucGx1Z2luLXZ1ZS1jb21wb25lbnRzL3ZpdGUnO1xuaW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSc7XG5cbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICAgIG9wdGltaXplRGVwczoge1xuICAgICAgICBub0Rpc2NvdmVyeTogdHJ1ZVxuICAgIH0sXG4gICAgcGx1Z2luczogW1xuICAgICAgICB2dWUoKSxcbiAgICAgICAgQ29tcG9uZW50cyh7XG4gICAgICAgICAgICByZXNvbHZlcnM6IFtQcmltZVZ1ZVJlc29sdmVyKCldXG4gICAgICAgIH0pXG4gICAgXSxcbiAgICByZXNvbHZlOiB7XG4gICAgICAgIGFsaWFzOiB7XG4gICAgICAgICAgICAnQCc6IGZpbGVVUkxUb1BhdGgobmV3IFVSTCgnLi9zcmMnLCBpbXBvcnQubWV0YS51cmwpKVxuICAgICAgICB9XG4gICAgfSxcbiAgICBzZXJ2ZXI6IHtcbiAgICAgICAgcHJveHk6IHtcbiAgICAgICAgICAgICcvYXBpJzoge1xuICAgICAgICAgICAgICAgIHRhcmdldDogJ2h0dHBzOi8vYXBpLmtvbnR1cmFpLmt6JyxcbiAgICAgICAgICAgICAgICBjaGFuZ2VPcmlnaW46IHRydWUsXG4gICAgICAgICAgICAgICAgc2VjdXJlOiB0cnVlLFxuICAgICAgICAgICAgICAgIGNvbmZpZ3VyZTogKHByb3h5LCBfb3B0aW9ucykgPT4ge1xuICAgICAgICAgICAgICAgICAgICBwcm94eS5vbignZXJyb3InLCAoZXJyLCBfcmVxLCBfcmVzKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygncHJveHkgZXJyb3InLCBlcnIpO1xuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICAgICAgcHJveHkub24oJ3Byb3h5UmVxJywgKHByb3h5UmVxLCByZXEsIF9yZXMpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCdTZW5kaW5nIFJlcXVlc3QgdG8gdGhlIFRhcmdldDonLCByZXEubWV0aG9kLCByZXEudXJsKTtcbiAgICAgICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgICAgIHByb3h5Lm9uKCdwcm94eVJlcycsIChwcm94eVJlcywgcmVxLCBfcmVzKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygnUmVjZWl2ZWQgUmVzcG9uc2UgZnJvbSB0aGUgVGFyZ2V0OicsIHByb3h5UmVzLnN0YXR1c0NvZGUsIHJlcS51cmwpO1xuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59KTtcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBdVMsU0FBUyxlQUFlLFdBQVc7QUFFMVUsU0FBUyx3QkFBd0I7QUFDakMsT0FBTyxTQUFTO0FBQ2hCLE9BQU8sZ0JBQWdCO0FBQ3ZCLFNBQVMsb0JBQW9CO0FBTDJKLElBQU0sMkNBQTJDO0FBUXpPLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQ3hCLGNBQWM7QUFBQSxJQUNWLGFBQWE7QUFBQSxFQUNqQjtBQUFBLEVBQ0EsU0FBUztBQUFBLElBQ0wsSUFBSTtBQUFBLElBQ0osV0FBVztBQUFBLE1BQ1AsV0FBVyxDQUFDLGlCQUFpQixDQUFDO0FBQUEsSUFDbEMsQ0FBQztBQUFBLEVBQ0w7QUFBQSxFQUNBLFNBQVM7QUFBQSxJQUNMLE9BQU87QUFBQSxNQUNILEtBQUssY0FBYyxJQUFJLElBQUksU0FBUyx3Q0FBZSxDQUFDO0FBQUEsSUFDeEQ7QUFBQSxFQUNKO0FBQUEsRUFDQSxRQUFRO0FBQUEsSUFDSixPQUFPO0FBQUEsTUFDSCxRQUFRO0FBQUEsUUFDSixRQUFRO0FBQUEsUUFDUixjQUFjO0FBQUEsUUFDZCxRQUFRO0FBQUEsUUFDUixXQUFXLENBQUMsT0FBTyxhQUFhO0FBQzVCLGdCQUFNLEdBQUcsU0FBUyxDQUFDLEtBQUssTUFBTSxTQUFTO0FBQ25DLG9CQUFRLElBQUksZUFBZSxHQUFHO0FBQUEsVUFDbEMsQ0FBQztBQUNELGdCQUFNLEdBQUcsWUFBWSxDQUFDLFVBQVUsS0FBSyxTQUFTO0FBQzFDLG9CQUFRLElBQUksa0NBQWtDLElBQUksUUFBUSxJQUFJLEdBQUc7QUFBQSxVQUNyRSxDQUFDO0FBQ0QsZ0JBQU0sR0FBRyxZQUFZLENBQUMsVUFBVSxLQUFLLFNBQVM7QUFDMUMsb0JBQVEsSUFBSSxzQ0FBc0MsU0FBUyxZQUFZLElBQUksR0FBRztBQUFBLFVBQ2xGLENBQUM7QUFBQSxRQUNMO0FBQUEsTUFDSjtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBQ0osQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K
|
||||
Reference in New Issue
Block a user