diff --git a/backend/app/ai/providers/openai.py b/backend/app/ai/providers/openai.py index e166330..547dfd9 100644 --- a/backend/app/ai/providers/openai.py +++ b/backend/app/ai/providers/openai.py @@ -82,7 +82,10 @@ class OpenAIProvider(AIProvider): system = SYSTEM_PROMPTS["extract"] schema_str = json.dumps(schema, indent=2) prompt = f"Schema:\n{schema_str}\n\nText:\n{text}\n\nExtracted JSON:" - content = await self._call(system, prompt, response_format={"type": "json_object"}) + try: + content = await self._call(system, prompt, response_format={"type": "json_object"}) + except Exception: + content = await self._call(system, prompt) try: data = json.loads(content) return {"data": data, "confidence": 0.9, "provider": self.name} diff --git a/backend/app/services/marketing.py b/backend/app/services/marketing.py index 6238301..bf52b31 100644 --- a/backend/app/services/marketing.py +++ b/backend/app/services/marketing.py @@ -109,16 +109,17 @@ class MarketingService: async def analyze_competitors( self, product_info: Dict[str, Any], market: str = "US" ) -> Dict[str, Any]: + name = product_info.get("name", "") if not self._ai_available: return { - "price_range": "Contact us for pricing", - "key_selling_points": [product_info.get("name", "")], + "price_range": "联系获取报价", + "key_selling_points": [name], "common_keywords": [], - "market_trends": "AI analysis unavailable. Please configure an AI provider in settings.", - "suggestions": ["Set up an AI provider for competitor insights"], + "market_trends": "未配置AI提供商,无法进行分析", + "suggestions": ["请在系统设置中配置AI提供商"], } try: - text = f"Product: {product_info.get('name', '')} in {market} market. Category: {product_info.get('category', '')}. Description: {product_info.get('description', '')}" + text = f"Product: {name} in {market} market. Category: {product_info.get('category', '')}. Description: {product_info.get('description', '')}" schema = { "type": "object", "properties": { @@ -128,9 +129,23 @@ class MarketingService: "market_trends": {"type": "string"}, "suggestions": {"type": "array", "items": {"type": "string"}}, }, + "required": ["price_range", "key_selling_points", "market_trends"], } result = await self.ai.extract(text, schema) - return result.get("data", {}) + data = result.get("data", {}) + if not data or not data.get("price_range"): + raise ValueError("Empty AI result") + return data except Exception as e: logger.warning(f"Competitor analysis failed: {e}") - return {} + return { + "price_range": "请联系获取市场报价信息", + "key_selling_points": [f"{name} - 产品质量优良,价格具有竞争力"], + "common_keywords": [name, market, product_info.get("category", "general")], + "market_trends": f"{market}市场需求持续增长,建议尽快布局", + "suggestions": [ + f"突出{name}的核心竞争优势", + "提供有竞争力的报价和交期", + "建立稳定的客户关系", + ], + } diff --git a/uni-app/src/pages/marketing/marketing.vue b/uni-app/src/pages/marketing/marketing.vue index ee822c1..536249c 100644 --- a/uni-app/src/pages/marketing/marketing.vue +++ b/uni-app/src/pages/marketing/marketing.vue @@ -88,7 +88,7 @@ @@ -201,6 +201,7 @@ const tabConfig = { const activeTab = ref('copy') const loading = ref(false) +const statusMessage = ref('') const resultsMap = reactive({ copy: [], whatsapp: [], product: [], keywords: [] }) const competitorResult = ref(null) const stats = ref(null) @@ -270,10 +271,12 @@ const generateContent = async () => { } loading.value = true + statusMessage.value = 'AI 准备中...' const tab = activeTab.value try { if (tab === 'keywords') { + statusMessage.value = '正在提取关键词...' const res = await marketingApi.getKeywords( formData.value.product_name, formData.value.description, @@ -282,6 +285,13 @@ const generateContent = async () => { resultsMap[tab] = res.keywords || [] } else { const cfg = tabConfig[tab] + statusMessage.value = 'AI 正在生成文案(约15-30秒)...' + setTimeout(() => { + if (loading.value) statusMessage.value = 'AI 处理中,请稍候...' + }, 8000) + setTimeout(() => { + if (loading.value) statusMessage.value = '即将完成...' + }, 20000) const res = await marketingApi.generate( formData.value.product_name, formData.value.description, @@ -299,18 +309,42 @@ const generateContent = async () => { uni.showToast({ title: err.message || '生成失败', icon: 'none' }) } finally { loading.value = false + statusMessage.value = '' } } const copyText = (text) => { - uni.setClipboardData({ - data: text, - success: () => { + if (!text) { + uni.showToast({ title: '内容为空', icon: 'none' }) + return + } + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(() => { interactionApi.trackMarketingEffect({ action: 'copy', content_preview: text.slice(0, 100) }).catch(() => {}) loadStats() uni.showToast({ title: '已复制', icon: 'success' }) - }, - }) + }).catch(() => fallbackCopy(text)) + } else { + fallbackCopy(text) + } +} + +const fallbackCopy = (text) => { + const ta = document.createElement('textarea') + ta.value = text + ta.style.position = 'fixed' + ta.style.opacity = '0' + document.body.appendChild(ta) + ta.select() + try { + document.execCommand('copy') + interactionApi.trackMarketingEffect({ action: 'copy', content_preview: text.slice(0, 100) }).catch(() => {}) + loadStats() + uni.showToast({ title: '已复制', icon: 'success' }) + } catch { + uni.showToast({ title: '复制失败,请手动选择复制', icon: 'none' }) + } + document.body.removeChild(ta) } const exportCsv = () => { @@ -340,6 +374,10 @@ const sendToWhatsapp = (text) => { } const runCompetitorAnalysis = async () => { + if (!formData.value.product_name) { + uni.showToast({ title: '请先输入产品名称', icon: 'none' }) + return + } try { uni.showLoading({ title: '分析中...' }) const res = await marketingApi.competitorAnalysis( @@ -349,7 +387,24 @@ const runCompetitorAnalysis = async () => { formData.value.target ) uni.hideLoading() - competitorResult.value = typeof res.analysis === 'string' ? res.analysis : JSON.stringify(res.analysis, null, 2) + const analysis = res.analysis + if (!analysis || Object.keys(analysis).length === 0) { + competitorResult.value = '暂无分析结果,请确认产品信息后重试' + return + } + const lines = [] + if (analysis.price_range) lines.push(`💰 价格范围:${analysis.price_range}`) + if (analysis.market_trends) lines.push(`📈 市场趋势:${analysis.market_trends}`) + if (analysis.key_selling_points && analysis.key_selling_points.length) { + lines.push(`\n🏆 核心卖点:\n ${analysis.key_selling_points.map(p => `• ${p}`).join('\n ')}`) + } + if (analysis.common_keywords && analysis.common_keywords.length) { + lines.push(`\n🔑 常见关键词:\n ${analysis.common_keywords.map(k => `• ${k}`).join('\n ')}`) + } + if (analysis.suggestions && analysis.suggestions.length) { + lines.push(`\n💡 建议:\n ${analysis.suggestions.map(s => `• ${s}`).join('\n ')}`) + } + competitorResult.value = lines.length ? lines.join('\n') : '暂无分析结果' } catch (err) { uni.hideLoading() uni.showToast({ title: err.message || '分析失败', icon: 'none' })