|
""" |
|
AdGuardian - AI-Powered Advertising Compliance Agent |
|
Multi-Country · Multi-Language · Three-Stage Analysis · AI-Driven Version |
|
Real AI advertising compliance analysis with professional visualization |
|
""" |
|
import gradio as gr |
|
import asyncio |
|
import aiohttp |
|
import json |
|
import os |
|
import datetime |
|
from typing import Dict, List |
|
|
|
|
|
class AIContentAnalyzer: |
|
def __init__(self): |
|
|
|
self.siliconflow_api_key = os.getenv("SILICONFLOW_API_KEY", "sk-your-api-key-here") |
|
self.siliconflow_base_url = "https://api.siliconflow.cn/v1/chat/completions" |
|
|
|
|
|
self.nebius_api_key = os.getenv("NEBIUS_API_KEY", "your-nebius-api-key") |
|
self.nebius_base_url = "https://api.studio.nebius.com/v1/chat/completions" |
|
|
|
|
|
self.model = "Qwen/Qwen3-8B" |
|
self.api_key = self.siliconflow_api_key |
|
self.base_url = self.siliconflow_base_url |
|
|
|
def set_model(self, model: str): |
|
"""设置模型并选择对应的API""" |
|
self.model = model |
|
|
|
|
|
siliconflow_models = ["Qwen/Qwen3-8B"] |
|
|
|
|
|
nebius_models = [ |
|
"deepseek-ai/DeepSeek-V3-0324", |
|
"deepseek-ai/DeepSeek-R1-0528", |
|
"Qwen/Qwen3-235B-A22B", |
|
"google/gemma-3-27b-it" |
|
] |
|
|
|
if model in siliconflow_models: |
|
self.api_key = self.siliconflow_api_key |
|
self.base_url = self.siliconflow_base_url |
|
elif model in nebius_models: |
|
self.api_key = self.nebius_api_key |
|
self.base_url = self.nebius_base_url |
|
else: |
|
|
|
self.api_key = self.siliconflow_api_key |
|
self.base_url = self.siliconflow_base_url |
|
|
|
async def analyze_content(self, text: str, keywords: List[str] = None) -> Dict: |
|
"""AI-powered content analysis""" |
|
keywords_str = ", ".join(keywords) if keywords else "无" |
|
|
|
prompt = f""" |
|
你是专业的内容审核专家,请对以下文本进行全面审核分析。 |
|
|
|
文本内容:{text} |
|
关键词:{keywords_str} |
|
|
|
参考示例案例的分析方式: |
|
原始文本:生活需要妆,美丽不打烊!但凡精制的女生,都不会放弃自我管理。脸蛋只有一张,精制一点又何妨?用了这款特效面霜,我的肌肤就像剥了壳的鸡蛋一样立刻变白,色斑色块全部消失,仿佛皮肤吃了仙丹一样,一夜见效,完全无副作用,效果简直绝绝子! |
|
|
|
分析结果: |
|
- 错别字标注:"精制" 应为 "精致"(2处) |
|
- 违规内容标注:1处违规(使用了"仙丹"这一封建迷信类用语,属于违反公序良俗类违规) |
|
- 虚假内容标注:4处虚假,夸大及绝对化表述 |
|
1. "色斑全部消失" 表述过于绝对,不符合实际护肤效果 |
|
2. "一夜见效"属于夸大描述,不符合护肤效果 |
|
3. "立刻变白"使用过于夸张的比喻,且"立刻"一词使美白效果的描述过于绝对 |
|
4. "完全无副作用"不符合实际情况,任何护肤品都不能保证绝对无副作用 |
|
- 不当广告用语:绝绝子等网络用语不适合正式广告 |
|
|
|
请按照这个详细程度分析给定文本,以JSON格式返回: |
|
{{ |
|
"typos": [ |
|
{{"original": "错误词", "correct": "正确词", "position": 位置, "reason": "详细错误原因"}} |
|
], |
|
"violations": [ |
|
{{"content": "具体违规内容", "type": "违规类型", "position": 位置, "reason": "详细违规原因", "suggestion": "具体修改建议"}} |
|
], |
|
"fake_content": [ |
|
{{"content": "具体虚假内容", "type": "虚假类型", "position": 位置, "reason": "详细分析原因", "suggestion": "具体修改建议"}} |
|
], |
|
"inappropriate_ads": [ |
|
{{"word": "不当用词", "position": 位置, "reason": "详细违规原因", "suggestion": "具体替代建议"}} |
|
], |
|
"keywords_analysis": [ |
|
{{"keyword": "关键词", "frequency": 次数, "positions": [位置列表], "context": "使用语境分析", "assessment": "使用评价"}} |
|
], |
|
"overall_assessment": {{ |
|
"risk_level": "低/中/高", |
|
"total_issues": 问题总数, |
|
"main_concerns": ["主要问题1", "主要问题2"], |
|
"recommendations": ["具体建议1", "具体建议2"], |
|
"revised_text": "修改后的文本建议" |
|
}} |
|
}} |
|
|
|
要求: |
|
1. 每个问题都要给出具体位置和详细分析 |
|
2. 建议要具体可操作 |
|
3. 分析要深入,不能只是简单列举 |
|
4. 必须包含修改后的文本建议 |
|
""" |
|
|
|
try: |
|
headers = { |
|
"Authorization": f"Bearer {self.api_key}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
data = { |
|
"model": self.model, |
|
"messages": [ |
|
{"role": "system", "content": "你是专业的内容审核专家,擅长识别各类内容问题并提供专业建议。"}, |
|
{"role": "user", "content": prompt} |
|
], |
|
"temperature": 0.1, |
|
"max_tokens": 2000 |
|
} |
|
|
|
async with aiohttp.ClientSession() as session: |
|
async with session.post(self.base_url, headers=headers, json=data) as response: |
|
if response.status == 200: |
|
result = await response.json() |
|
ai_response = result["choices"][0]["message"]["content"] |
|
|
|
|
|
try: |
|
|
|
if "```json" in ai_response: |
|
ai_response = ai_response.split("```json")[1].split("```")[0] |
|
elif "```" in ai_response: |
|
ai_response = ai_response.split("```")[1].split("```")[0] |
|
|
|
analysis_result = json.loads(ai_response.strip()) |
|
return analysis_result |
|
except json.JSONDecodeError: |
|
|
|
return self._create_fallback_analysis(text, keywords) |
|
else: |
|
return self._create_fallback_analysis(text, keywords) |
|
|
|
except Exception as e: |
|
print(f"AI analysis error: {e}") |
|
return self._create_fallback_analysis(text, keywords) |
|
|
|
async def analyze_content_with_options(self, text: str, keywords: List[str] = None, country: str = "中国", language: str = "简体中文") -> Dict: |
|
"""带国家和语言选项的AI内容分析""" |
|
keywords_str = ", ".join(keywords) if keywords else "无" |
|
|
|
|
|
country_standards = { |
|
"China": "中国广告法和相关法规", |
|
"USA": "美国FTC广告标准", |
|
"UK": "英国ASA广告标准", |
|
"Japan": "日本景品表示法", |
|
"Korea": "韩国公正交易委员会标准", |
|
"Singapore": "新加坡CCCS标准", |
|
"Malaysia": "马来西亚广告标准", |
|
"Other": "国际通用广告标准" |
|
} |
|
|
|
country_names = { |
|
"China": "中国", |
|
"USA": "美国", |
|
"UK": "英国", |
|
"Japan": "日本", |
|
"Korea": "韩国", |
|
"Singapore": "新加坡", |
|
"Malaysia": "马来西亚", |
|
"Other": "其他地区" |
|
} |
|
|
|
standard = country_standards.get(country, "国际通用广告标准") |
|
country_cn = country_names.get(country, country) |
|
|
|
prompt = f""" |
|
你是专业的内容审核专家,请根据{country_cn}的{standard}对以下文本进行全面审核分析。 |
|
|
|
文本内容:{text} |
|
关键词:{keywords_str} |
|
审核地区:{country_cn} |
|
回答语言:{language} |
|
|
|
请按照{country_cn}的法规标准进行分析,并用{language}回答。 |
|
|
|
参考示例案例的分析方式: |
|
原始文本:生活需要妆,美丽不打烊!但凡精制的女生,都不会放弃自我管理。脸蛋只有一张,精制一点又何妨?用了这款特效面霜,我的肌肤就像剥了壳的鸡蛋一样立刻变白,色斑色块全部消失,仿佛皮肤吃了仙丹一样,一夜见效,完全无副作用,效果简直绝绝子! |
|
|
|
分析结果: |
|
- 错别字标注:"精制" 应为 "精致"(2处) |
|
- 违规内容标注:1处违规(使用了"仙丹"这一封建迷信类用语,属于违反公序良俗类违规) |
|
- 虚假内容标注:4处虚假,夸大及绝对化表述 |
|
1. "色斑全部消失" 表述过于绝对,不符合实际护肤效果 |
|
2. "一夜见效"属于夸大描述,不符合护肤效果 |
|
3. "立刻变白"使用过于夸张的比喻,且"立刻"一词使美白效果的描述过于绝对 |
|
4. "完全无副作用"不符合实际情况,任何护肤品都不能保证绝对无副作用 |
|
- 不当广告用语:绝绝子等网络用语不适合正式广告 |
|
|
|
请按照这个详细程度分析给定文本,以JSON格式返回: |
|
{{ |
|
"typos": [ |
|
{{"original": "错误词", "correct": "正确词", "position": 位置, "reason": "详细错误原因"}} |
|
], |
|
"violations": [ |
|
{{"content": "具体违规内容", "type": "违规类型", "position": 位置, "reason": "详细违规原因", "suggestion": "具体修改建议"}} |
|
], |
|
"fake_content": [ |
|
{{"content": "具体虚假内容", "type": "虚假类型", "position": 位置, "reason": "详细分析原因", "suggestion": "具体修改建议"}} |
|
], |
|
"inappropriate_ads": [ |
|
{{"word": "不当用词", "position": 位置, "reason": "详细违规原因", "suggestion": "具体替代建议"}} |
|
], |
|
"keywords_analysis": [ |
|
{{"keyword": "关键词", "frequency": 次数, "positions": [位置列表], "context": "使用语境分析", "assessment": "使用评价"}} |
|
], |
|
"overall_assessment": {{ |
|
"risk_level": "低/中/高", |
|
"total_issues": 问题总数, |
|
"main_concerns": ["主要问题1", "主要问题2"], |
|
"recommendations": ["具体建议1", "具体建议2"], |
|
"revised_text": "修改后的文本建议", |
|
"country_specific_notes": "针对{country}的特殊说明" |
|
}} |
|
}} |
|
|
|
要求: |
|
1. 每个问题都要给出具体位置和详细分析 |
|
2. 建议要具体可操作 |
|
3. 分析要深入,不能只是简单列举 |
|
4. 必须包含修改后的文本建议 |
|
5. 考虑{country}的特殊法规要求 |
|
6. 使用{language}回答 |
|
""" |
|
|
|
try: |
|
headers = { |
|
"Authorization": f"Bearer {self.api_key}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
data = { |
|
"model": self.model, |
|
"messages": [ |
|
{"role": "system", "content": f"你是专业的内容审核专家,擅长识别各类内容问题并提供专业建议。请根据{country}的法规标准进行分析,并用{language}回答。"}, |
|
{"role": "user", "content": prompt} |
|
], |
|
"temperature": 0.1, |
|
"max_tokens": 3000 |
|
} |
|
|
|
async with aiohttp.ClientSession() as session: |
|
async with session.post(self.base_url, headers=headers, json=data) as response: |
|
if response.status == 200: |
|
result = await response.json() |
|
ai_response = result["choices"][0]["message"]["content"] |
|
|
|
|
|
try: |
|
|
|
if "```json" in ai_response: |
|
ai_response = ai_response.split("```json")[1].split("```")[0] |
|
elif "```" in ai_response: |
|
ai_response = ai_response.split("```")[1].split("```")[0] |
|
|
|
analysis_result = json.loads(ai_response.strip()) |
|
return analysis_result |
|
except json.JSONDecodeError: |
|
|
|
return self._create_fallback_analysis(text, keywords, country, language) |
|
else: |
|
return self._create_fallback_analysis(text, keywords, country, language) |
|
|
|
except Exception as e: |
|
print(f"AI analysis error: {e}") |
|
return self._create_fallback_analysis(text, keywords, country, language) |
|
|
|
def _create_fallback_analysis(self, text: str, keywords: List[str] = None, country: str = "China", language: str = "简体中文") -> Dict: |
|
"""备用分析方法 - 简单返回无法分析的提示""" |
|
return { |
|
"typos": [], |
|
"violations": [], |
|
"fake_content": [], |
|
"inappropriate_ads": [], |
|
"keywords_analysis": [], |
|
"overall_assessment": { |
|
"risk_level": "未知", |
|
"total_issues": 0, |
|
"main_concerns": ["AI分析服务暂时不可用"], |
|
"recommendations": ["请稍后重试或检查网络连接"], |
|
"revised_text": text |
|
} |
|
} |
|
|
|
|
|
ai_analyzer = AIContentAnalyzer() |
|
|
|
def generate_hanyu_xinxie_cards(analysis_result: Dict, original_text: str) -> str: |
|
"""生成汉语新解风格的SVG卡片""" |
|
overall = analysis_result.get("overall_assessment", {}) |
|
total_issues = overall.get("total_issues", 0) |
|
risk_level = overall.get("risk_level", "低") |
|
|
|
|
|
colors = { |
|
"primary": "#B6B5A7", |
|
"secondary": "#9A8F8F", |
|
"accent": "#C5B4A0", |
|
"background": "#F2EDE9", |
|
"text": "#5B5B5B", |
|
"light_text": "#8C8C8C", |
|
"divider": "#D1CBC3" |
|
} |
|
|
|
|
|
if risk_level == "高": |
|
colors["secondary"] = "#B85450" |
|
colors["accent"] = "#D4776B" |
|
elif risk_level == "中": |
|
colors["secondary"] = "#C4965A" |
|
colors["accent"] = "#D4A574" |
|
|
|
|
|
main_card = f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 20px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 25px; |
|
"> |
|
<div style=" |
|
width: 100%; |
|
background-color: {colors['background']}; |
|
border-radius: 20px; |
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1); |
|
overflow: hidden; |
|
position: relative; |
|
"> |
|
<!-- 标题区域 --> |
|
<div style=" |
|
background-color: {colors['secondary']}; |
|
color: {colors['background']}; |
|
padding: 20px; |
|
text-align: left; |
|
"> |
|
<h1 style="font-size: 28px; margin: 0; font-weight: 700;">🛡️ AI Content Audit Report</h1> |
|
<p style="font-size: 16px; margin: 8px 0 0 0; opacity: 0.9;">China · Simplified Chinese · Hanyu Xinxie Style</p> |
|
</div> |
|
|
|
<!-- 内容区域 --> |
|
<div style="padding: 25px 20px;"> |
|
<!-- 主要统计 --> |
|
<div style="text-align: left; margin-bottom: 25px;"> |
|
<div style=" |
|
font-size: 48px; |
|
color: {colors['text']}; |
|
margin-bottom: 12px; |
|
font-weight: bold; |
|
position: relative; |
|
"> |
|
{total_issues} Issues Found |
|
<div style=" |
|
position: absolute; |
|
left: 0; |
|
bottom: -6px; |
|
width: 80px; |
|
height: 4px; |
|
background-color: {colors['accent']}; |
|
"></div> |
|
</div> |
|
<div style=" |
|
font-size: 20px; |
|
color: {colors['light_text']}; |
|
margin: 15px 0; |
|
">Risk Level: {risk_level}</div> |
|
</div> |
|
|
|
<!-- 分隔线 --> |
|
<div style=" |
|
width: 100%; |
|
height: 1px; |
|
background-color: {colors['divider']}; |
|
margin: 20px 0; |
|
"></div> |
|
|
|
<!-- AI分析结果 --> |
|
<div style=" |
|
font-size: 18px; |
|
line-height: 1.8; |
|
text-align: left; |
|
"> |
|
<div style=" |
|
padding-left: 20px; |
|
border-left: 4px solid {colors['accent']}; |
|
">""" |
|
|
|
|
|
if analysis_result.get("typos"): |
|
main_card += f'<p style="margin: 12px 0; color: {colors["text"]}; font-weight: bold; font-size: 18px;">📝 Typos: {len(analysis_result["typos"])} found</p>' |
|
for typo in analysis_result["typos"][:2]: |
|
main_card += f'<p style="margin: 8px 0 8px 20px; color: {colors["light_text"]}; font-size: 16px;">"{typo.get("original", "")}" → "{typo.get("correct", "")}"</p>' |
|
|
|
if analysis_result.get("violations"): |
|
main_card += f'<p style="margin: 12px 0; color: {colors["text"]}; font-weight: bold; font-size: 18px;">⚠️ Violations: {len(analysis_result["violations"])} found</p>' |
|
for violation in analysis_result["violations"][:2]: |
|
main_card += f'<p style="margin: 8px 0 8px 20px; color: {colors["light_text"]}; font-size: 16px;">"{violation.get("content", "")}" - {violation.get("type", "")}</p>' |
|
|
|
if analysis_result.get("fake_content"): |
|
main_card += f'<p style="margin: 12px 0; color: {colors["text"]}; font-weight: bold; font-size: 18px;">🚫 Fake Content: {len(analysis_result["fake_content"])} found</p>' |
|
for fake in analysis_result["fake_content"][:2]: |
|
main_card += f'<p style="margin: 8px 0 8px 20px; color: {colors["light_text"]}; font-size: 16px;">"{fake.get("content", "")}" - {fake.get("type", "")}</p>' |
|
|
|
if analysis_result.get("inappropriate_ads"): |
|
main_card += f'<p style="margin: 12px 0; color: {colors["text"]}; font-weight: bold; font-size: 18px;">📢 Inappropriate Ads: {len(analysis_result["inappropriate_ads"])} found</p>' |
|
for ad in analysis_result["inappropriate_ads"][:2]: |
|
main_card += f'<p style="margin: 8px 0 8px 20px; color: {colors["light_text"]}; font-size: 16px;">"{ad.get("word", "")}" - {ad.get("reason", "")}</p>' |
|
|
|
if analysis_result.get("keywords_analysis"): |
|
main_card += f'<p style="margin: 12px 0; color: {colors["text"]}; font-weight: bold; font-size: 18px;">🔍 Keywords: {len(analysis_result["keywords_analysis"])} analyzed</p>' |
|
for kw in analysis_result["keywords_analysis"][:2]: |
|
main_card += f'<p style="margin: 8px 0 8px 20px; color: {colors["light_text"]}; font-size: 16px;">"{kw.get("keyword", "")}" - appears {kw.get("frequency", 0)} times</p>' |
|
|
|
if total_issues == 0: |
|
main_card += f'<p style="margin: 12px 0; color: {colors["light_text"]}; font-size: 18px;">✅ No obvious issues found</p>' |
|
|
|
main_card += f""" |
|
</div> |
|
</div> |
|
|
|
<!-- AI建议 --> |
|
<div style=" |
|
margin-top: 20px; |
|
padding: 15px; |
|
background-color: rgba(255,255,255,0.5); |
|
border-radius: 10px; |
|
border-left: 4px solid {colors['accent']}; |
|
"> |
|
<h4 style="margin: 0 0 10px 0; color: {colors['text']}; font-size: 14px;">🤖 AI建议</h4>""" |
|
|
|
recommendations = overall.get("recommendations", ["内容表述规范"]) |
|
for rec in recommendations[:2]: |
|
main_card += f'<p style="margin: 5px 0; color: {colors["light_text"]}; font-size: 13px;">• {rec}</p>' |
|
|
|
main_card += f""" |
|
</div> |
|
|
|
<!-- 中式印章风格装饰 --> |
|
<div style=" |
|
text-align: center; |
|
margin-top: 25px; |
|
padding: 10px; |
|
border: 2px solid {colors['accent']}; |
|
border-radius: 50%; |
|
width: 70px; |
|
height: 70px; |
|
margin-left: auto; |
|
margin-right: auto; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
"> |
|
<span style=" |
|
font-size: 18px; |
|
color: {colors['secondary']}; |
|
font-weight: bold; |
|
">AI审核</span> |
|
</div> |
|
</div> |
|
|
|
<!-- 背景装饰文字 --> |
|
<div style=" |
|
position: absolute; |
|
font-size: 120px; |
|
color: rgba(182, 181, 167, 0.05); |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
font-weight: bold; |
|
pointer-events: none; |
|
z-index: 0; |
|
">AI</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
detail_card = "" |
|
if total_issues > 0: |
|
detail_card = generate_detail_analysis_card(analysis_result, colors) |
|
|
|
|
|
revision_card = "" |
|
overall = analysis_result.get("overall_assessment", {}) |
|
if overall.get("revised_text"): |
|
revision_card = generate_revision_card(analysis_result, colors) |
|
|
|
return main_card + detail_card + revision_card |
|
|
|
def generate_detail_analysis_card(analysis_result: Dict, colors: Dict) -> str: |
|
"""生成详细分析卡片""" |
|
detail_card = f""" |
|
<div style=" |
|
display: flex; |
|
justify-content: center; |
|
padding: 20px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 15px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 20px; |
|
"> |
|
<div style=" |
|
width: 500px; |
|
background-color: {colors['background']}; |
|
border-radius: 20px; |
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1); |
|
overflow: hidden; |
|
position: relative; |
|
"> |
|
<!-- 标题区域 --> |
|
<div style=" |
|
background-color: {colors['secondary']}; |
|
color: {colors['background']}; |
|
padding: 20px; |
|
text-align: left; |
|
"> |
|
<h2 style="font-size: 18px; margin: 0; font-weight: 700;">📋 详细分析报告</h2> |
|
<p style="font-size: 12px; margin: 5px 0 0 0; opacity: 0.9;">Detailed Analysis Report</p> |
|
</div> |
|
|
|
<!-- 内容区域 --> |
|
<div style="padding: 25px 20px;"> |
|
""" |
|
|
|
|
|
if analysis_result.get("typos"): |
|
detail_card += f""" |
|
<div style="margin-bottom: 20px;"> |
|
<h3 style="color: {colors['text']}; margin-bottom: 10px; font-size: 16px;">📝 错别字检测</h3> |
|
""" |
|
for i, typo in enumerate(analysis_result["typos"], 1): |
|
detail_card += f""" |
|
<div style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 12px; |
|
border-radius: 8px; |
|
margin-bottom: 8px; |
|
border-left: 4px solid {colors['accent']}; |
|
"> |
|
<p style="margin: 0; color: {colors['text']}; font-size: 14px;"> |
|
<strong>{i}. "{typo.get('original', '')}" → "{typo.get('correct', '')}"</strong> |
|
</p> |
|
<p style="margin: 5px 0 0 0; color: {colors['light_text']}; font-size: 12px;"> |
|
位置: {typo.get('position', '未知')} | 原因: {typo.get('reason', '')} |
|
</p> |
|
</div> |
|
""" |
|
detail_card += "</div>" |
|
|
|
|
|
if analysis_result.get("violations"): |
|
detail_card += f""" |
|
<div style="margin-bottom: 20px;"> |
|
<h3 style="color: {colors['text']}; margin-bottom: 10px; font-size: 16px;">⚠️ 违规内容检测</h3> |
|
""" |
|
for i, violation in enumerate(analysis_result["violations"], 1): |
|
detail_card += f""" |
|
<div style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 12px; |
|
border-radius: 8px; |
|
margin-bottom: 8px; |
|
border-left: 4px solid #B85450; |
|
"> |
|
<p style="margin: 0; color: {colors['text']}; font-size: 14px;"> |
|
<strong>{i}. "{violation.get('content', '')}"</strong> |
|
</p> |
|
<p style="margin: 5px 0 0 0; color: {colors['light_text']}; font-size: 12px;"> |
|
类型: {violation.get('type', '')} | 位置: {violation.get('position', '未知')} |
|
</p> |
|
<p style="margin: 5px 0 0 0; color: {colors['light_text']}; font-size: 12px;"> |
|
原因: {violation.get('reason', '')} |
|
</p> |
|
<p style="margin: 5px 0 0 0; color: {colors['text']}; font-size: 12px;"> |
|
建议: {violation.get('suggestion', '')} |
|
</p> |
|
</div> |
|
""" |
|
detail_card += "</div>" |
|
|
|
|
|
if analysis_result.get("fake_content"): |
|
detail_card += f""" |
|
<div style="margin-bottom: 20px;"> |
|
<h3 style="color: {colors['text']}; margin-bottom: 10px; font-size: 16px;">🚫 虚假内容检测</h3> |
|
""" |
|
for i, fake in enumerate(analysis_result["fake_content"], 1): |
|
detail_card += f""" |
|
<div style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 12px; |
|
border-radius: 8px; |
|
margin-bottom: 8px; |
|
border-left: 4px solid #D4776B; |
|
"> |
|
<p style="margin: 0; color: {colors['text']}; font-size: 14px;"> |
|
<strong>{i}. "{fake.get('content', '')}"</strong> |
|
</p> |
|
<p style="margin: 5px 0 0 0; color: {colors['light_text']}; font-size: 12px;"> |
|
类型: {fake.get('type', '')} | 位置: {fake.get('position', '未知')} |
|
</p> |
|
<p style="margin: 5px 0 0 0; color: {colors['light_text']}; font-size: 12px;"> |
|
原因: {fake.get('reason', '')} |
|
</p> |
|
<p style="margin: 5px 0 0 0; color: {colors['text']}; font-size: 12px;"> |
|
建议: {fake.get('suggestion', '')} |
|
</p> |
|
</div> |
|
""" |
|
detail_card += "</div>" |
|
|
|
detail_card += """ |
|
</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
return detail_card |
|
|
|
def generate_revision_card(analysis_result: Dict, colors: Dict) -> str: |
|
"""生成修改建议卡片""" |
|
overall = analysis_result.get("overall_assessment", {}) |
|
revised_text = overall.get("revised_text", "") |
|
|
|
revision_card = f""" |
|
<div style=" |
|
display: flex; |
|
justify-content: center; |
|
padding: 20px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 15px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 20px; |
|
"> |
|
<div style=" |
|
width: 500px; |
|
background-color: {colors['background']}; |
|
border-radius: 20px; |
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1); |
|
overflow: hidden; |
|
position: relative; |
|
"> |
|
<!-- 标题区域 --> |
|
<div style=" |
|
background-color: #4A7C59; |
|
color: {colors['background']}; |
|
padding: 20px; |
|
text-align: left; |
|
"> |
|
<h2 style="font-size: 18px; margin: 0; font-weight: 700;">✨ 修改建议</h2> |
|
<p style="font-size: 12px; margin: 5px 0 0 0; opacity: 0.9;">Revision Suggestions</p> |
|
</div> |
|
|
|
<!-- 内容区域 --> |
|
<div style="padding: 25px 20px;"> |
|
<div style=" |
|
background-color: rgba(74, 124, 89, 0.1); |
|
padding: 15px; |
|
border-radius: 10px; |
|
border-left: 4px solid #4A7C59; |
|
"> |
|
<h4 style="margin: 0 0 10px 0; color: {colors['text']}; font-size: 14px;">📝 修改后文本:</h4> |
|
<p style="margin: 0; color: {colors['text']}; font-size: 14px; line-height: 1.6;"> |
|
{revised_text} |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
return revision_card |
|
|
|
def generate_error_card(error_msg: str) -> str: |
|
"""生成错误信息卡片""" |
|
return f""" |
|
<div style=" |
|
display: flex; |
|
justify-content: center; |
|
padding: 20px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 15px; |
|
font-family: 'Microsoft YaHei', sans-serif; |
|
"> |
|
<div style=" |
|
width: 350px; |
|
background-color: #F2EDE9; |
|
border-radius: 20px; |
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1); |
|
padding: 30px; |
|
text-align: center; |
|
"> |
|
<h2 style="color: #B85450; margin-bottom: 15px;">⚠️ 分析出错</h2> |
|
<p style="color: #5B5B5B; margin-bottom: 20px;">AI分析过程中遇到问题:</p> |
|
<div style=" |
|
background-color: rgba(184, 84, 80, 0.1); |
|
padding: 15px; |
|
border-radius: 10px; |
|
border-left: 4px solid #B85450; |
|
text-align: left; |
|
"> |
|
<code style="color: #B85450; font-size: 12px;">{error_msg}</code> |
|
</div> |
|
<p style="color: #8C8C8C; margin-top: 15px; font-size: 14px;">请稍后重试或检查网络连接</p> |
|
</div> |
|
</div> |
|
""" |
|
|
|
def comprehensive_text_audit(text, keywords=""): |
|
""" |
|
AI-powered comprehensive text audit with SVG visualization |
|
""" |
|
if not text.strip(): |
|
return "❌ Please enter text content" |
|
|
|
|
|
keyword_list = [k.strip() for k in keywords.split(",") if k.strip()] if keywords else [] |
|
|
|
try: |
|
|
|
loop = asyncio.new_event_loop() |
|
asyncio.set_event_loop(loop) |
|
|
|
try: |
|
|
|
analysis_result = loop.run_until_complete( |
|
ai_analyzer.analyze_content(text, keyword_list) |
|
) |
|
finally: |
|
loop.close() |
|
|
|
|
|
svg_cards = generate_hanyu_xinxie_cards(analysis_result, text) |
|
|
|
return svg_cards |
|
|
|
except Exception as e: |
|
print(f"Analysis error: {e}") |
|
|
|
return generate_error_card(str(e)) |
|
|
|
|
|
async def streaming_ai_analysis(text: str, keywords: List[str], country: str, language: str, model: str = "Qwen/Qwen3-8B (Free - SiliconFlow)"): |
|
"""流式AI分析生成器""" |
|
ai = AIContentAnalyzer() |
|
|
|
|
|
model_mapping = { |
|
"Qwen/Qwen3-8B (Free - SiliconFlow)": "Qwen/Qwen3-8B", |
|
"deepseek-ai/DeepSeek-V3-0324 (Paid - Nebius)": "deepseek-ai/DeepSeek-V3-0324", |
|
"deepseek-ai/DeepSeek-R1-0528 (Paid - Nebius)": "deepseek-ai/DeepSeek-R1-0528", |
|
"Qwen/Qwen3-235B-A22B (Paid - Nebius)": "Qwen/Qwen3-235B-A22B", |
|
"google/gemma-3-27b-it (Paid - Nebius, Vision)": "google/gemma-3-27b-it" |
|
} |
|
|
|
actual_model = model_mapping.get(model, "Qwen/Qwen3-8B") |
|
ai.set_model(actual_model) |
|
|
|
|
|
country_standards = { |
|
"China": "中国广告法和相关法规", |
|
"USA": "美国FTC广告标准", |
|
"UK": "英国ASA广告标准", |
|
"Japan": "日本景品表示法", |
|
"Korea": "韩国公正交易委员会标准", |
|
"Singapore": "新加坡CCCS标准", |
|
"Malaysia": "马来西亚广告标准", |
|
"Other": "国际通用广告标准" |
|
} |
|
|
|
country_names = { |
|
"China": "中国", |
|
"USA": "美国", |
|
"UK": "英国", |
|
"Japan": "日本", |
|
"Korea": "韩国", |
|
"Singapore": "新加坡", |
|
"Malaysia": "马来西亚", |
|
"Other": "其他地区" |
|
} |
|
|
|
standard = country_standards.get(country, "国际通用广告标准") |
|
country_cn = country_names.get(country, country) |
|
keywords_str = ", ".join(keywords) if keywords else "无" |
|
|
|
prompt = f""" |
|
你是专业的内容审核专家,请根据{country_cn}的{standard}对以下文本进行全面审核分析。 |
|
|
|
文本内容:{text} |
|
关键词:{keywords_str} |
|
审核地区:{country_cn} |
|
回答语言:{language} |
|
|
|
请按照以下JSON格式返回分析结果: |
|
{{ |
|
"reasoning_content": "详细的推理过程和分析思路", |
|
"sentence_analysis": [ |
|
{{ |
|
"sentence": "具体句子内容", |
|
"risk_level": "green/yellow/orange/red", |
|
"issues": ["具体问题1", "具体问题2"], |
|
"explanation": "为什么认为有问题的详细解释" |
|
}} |
|
], |
|
"revised_text": "修改后的完整文本", |
|
"summary": {{ |
|
"total_issues": 问题总数, |
|
"risk_level": "整体风险等级", |
|
"main_concerns": ["主要问题1", "主要问题2"] |
|
}} |
|
}} |
|
|
|
风险等级说明: |
|
- green: 无问题,表述规范 |
|
- yellow: 轻微问题,建议优化 |
|
- orange: 中等问题,需要修改 |
|
- red: 严重问题,必须修改 |
|
|
|
请确保: |
|
1. 每个句子都要分析并标注风险等级 |
|
2. 问题解释要具体明确 |
|
3. 修改后的文本要保持原意但符合法规 |
|
4. 返回标准的JSON格式 |
|
""" |
|
|
|
try: |
|
headers = { |
|
"Authorization": f"Bearer {ai.api_key}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
data = { |
|
"model": ai.model, |
|
"messages": [ |
|
{"role": "system", "content": f"你是专业的内容审核专家,请根据{country_cn}的法规标准进行分析,并用{language}回答。"}, |
|
{"role": "user", "content": prompt} |
|
], |
|
"temperature": 0.1, |
|
"max_tokens": 3000, |
|
"stream": True |
|
} |
|
|
|
async with aiohttp.ClientSession() as session: |
|
async with session.post(ai.base_url, headers=headers, json=data) as response: |
|
if response.status == 200: |
|
async for line in response.content: |
|
if line: |
|
line_str = line.decode('utf-8').strip() |
|
if line_str.startswith('data: '): |
|
data_str = line_str[6:] |
|
if data_str == '[DONE]': |
|
break |
|
try: |
|
chunk = json.loads(data_str) |
|
if 'choices' in chunk and len(chunk['choices']) > 0: |
|
delta = chunk['choices'][0].get('delta', {}) |
|
if 'content' in delta and delta['content']: |
|
yield str(delta['content']) |
|
except json.JSONDecodeError: |
|
continue |
|
else: |
|
yield "API调用失败,使用备用分析..." |
|
|
|
fallback = ai._create_fallback_analysis(text, keywords, country, language) |
|
yield f"分析完成:发现{fallback['overall_assessment']['total_issues']}个问题" |
|
|
|
except Exception as e: |
|
yield f"分析出错:{str(e)}" |
|
|
|
|
|
def comprehensive_text_audit_streaming(text, keywords="", country="China", language="简体中文", model="Qwen/Qwen3-8B (Free - SiliconFlow)"): |
|
""" |
|
三次渐进式输出的AI内容审核 - 缓存所有阶段结果 |
|
""" |
|
if not text.strip(): |
|
yield ("❌ Please enter text content", "", "", "") |
|
return |
|
|
|
|
|
keyword_list = [k.strip() for k in keywords.split(",") if k.strip()] if keywords else [] |
|
|
|
|
|
messages = [] |
|
stage1_result = "" |
|
stage2_result = "" |
|
stage3_result = "" |
|
|
|
try: |
|
|
|
yield ("🔍 Stage 1: Basic Analysis in progress...", "", "", "") |
|
|
|
stage1_final = None |
|
for chunk in perform_streaming_analysis(text, keyword_list, country, language, model, messages, stage=1): |
|
stage1_final = chunk |
|
yield (chunk, "", "", "") |
|
|
|
stage1_result = stage1_final |
|
|
|
|
|
yield (stage1_result, "🎨 Stage 2: Detailed Annotation in progress...", "", "") |
|
|
|
stage2_final = None |
|
for chunk in perform_streaming_analysis(text, keyword_list, country, language, model, messages, stage=2): |
|
stage2_final = chunk |
|
yield (stage1_result, chunk, "", "") |
|
|
|
stage2_result = stage2_final |
|
|
|
|
|
yield (stage1_result, stage2_result, "✨ Stage 3: Revision Suggestions in progress...", "") |
|
|
|
stage3_final = None |
|
for chunk in perform_streaming_analysis(text, keyword_list, country, language, model, messages, stage=3): |
|
stage3_final = chunk |
|
yield (stage1_result, stage2_result, chunk, "") |
|
|
|
stage3_result = stage3_final |
|
|
|
|
|
summary_interface = generate_analysis_summary() |
|
yield (stage1_result, stage2_result, stage3_result, summary_interface) |
|
|
|
except Exception as e: |
|
print(f"Analysis error: {e}") |
|
error_msg = generate_error_card(str(e)) |
|
yield (error_msg, "", "", "") |
|
|
|
|
|
def perform_streaming_analysis(text: str, keywords: List[str], country: str, language: str, model: str, messages: list, stage: int): |
|
"""执行真正的流式AI分析""" |
|
|
|
|
|
model_mapping = { |
|
"Qwen/Qwen3-8B (Free - SiliconFlow)": { |
|
"model": "Qwen/Qwen3-8B", |
|
"api_key": os.getenv("SILICONFLOW_API_KEY", "sk-your-api-key-here"), |
|
"base_url": "https://api.siliconflow.cn/v1/chat/completions" |
|
}, |
|
"deepseek-ai/DeepSeek-V3-0324 (Paid - Nebius)": { |
|
"model": "deepseek-ai/DeepSeek-V3-0324", |
|
"api_key": os.getenv("NEBIUS_API_KEY", "your-nebius-api-key"), |
|
"base_url": "https://api.studio.nebius.com/v1/chat/completions" |
|
}, |
|
"deepseek-ai/DeepSeek-R1-0528 (Paid - Nebius)": { |
|
"model": "deepseek-ai/DeepSeek-R1-0528", |
|
"api_key": os.getenv("NEBIUS_API_KEY", "your-nebius-api-key"), |
|
"base_url": "https://api.studio.nebius.com/v1/chat/completions" |
|
}, |
|
"Qwen/Qwen3-235B-A22B (Paid - Nebius)": { |
|
"model": "Qwen/Qwen3-235B-A22B", |
|
"api_key": os.getenv("NEBIUS_API_KEY", "your-nebius-api-key"), |
|
"base_url": "https://api.studio.nebius.com/v1/chat/completions" |
|
}, |
|
"google/gemma-3-27b-it (Paid - Nebius, Vision)": { |
|
"model": "google/gemma-3-27b-it", |
|
"api_key": os.getenv("NEBIUS_API_KEY", "your-nebius-api-key"), |
|
"base_url": "https://api.studio.nebius.com/v1/chat/completions" |
|
} |
|
} |
|
|
|
model_config = model_mapping.get(model, model_mapping["Qwen/Qwen3-8B (Free - SiliconFlow)"]) |
|
|
|
|
|
country_standards = { |
|
"China": "中国广告法和相关法规", |
|
"USA": "美国FTC广告标准", |
|
"UK": "英国ASA广告标准", |
|
"Japan": "日本景品表示法", |
|
"Korea": "韩国公正交易委员会标准", |
|
"Singapore": "新加坡CCCS标准", |
|
"Malaysia": "马来西亚广告标准", |
|
"Other": "国际通用广告标准" |
|
} |
|
|
|
country_names = { |
|
"China": "中国", |
|
"USA": "美国", |
|
"UK": "英国", |
|
"Japan": "日本", |
|
"Korea": "韩国", |
|
"Singapore": "新加坡", |
|
"Malaysia": "马来西亚", |
|
"Other": "其他地区" |
|
} |
|
|
|
standard = country_standards.get(country, "国际通用广告标准") |
|
country_cn = country_names.get(country, country) |
|
keywords_str = ", ".join(keywords) if keywords else "无" |
|
|
|
|
|
if stage == 1: |
|
stage_title = "🔍 Stage 1: Basic Analysis" |
|
user_prompt = f""" |
|
你是专业的内容审核专家,请根据{country_cn}的{standard}对以下文本进行初步分析。 |
|
|
|
文本内容:{text} |
|
关键词:{keywords_str} |
|
审核地区:{country_cn} |
|
回答语言:{language} |
|
|
|
请进行基础分析,包括: |
|
1. 整体风险评估 |
|
2. 主要问题识别 |
|
3. 法规符合性初判 |
|
|
|
请用{language}回答,提供清晰的分析结论。 |
|
""" |
|
|
|
elif stage == 2: |
|
stage_title = "🎨 Stage 2: Detailed Annotation" |
|
user_prompt = f""" |
|
基于前面的分析,现在请对文本进行详细的句子级标注分析,并生成HTML格式的标注版本。 |
|
|
|
请为每个句子标注风险等级: |
|
- 绿色(#4CAF50):无问题,表述规范 |
|
- 黄色(#FFC107):轻微问题,建议优化 |
|
- 橙色(#FF9800):中等问题,需要修改 |
|
- 红色(#F44336):严重问题,必须修改 |
|
|
|
请直接返回HTML代码,格式如下: |
|
```html |
|
<div style="background-color: rgba(255,255,255,0.5); padding: 20px; border-radius: 10px;"> |
|
<div style="margin: 15px 0; padding: 15px; border-left: 5px solid #4CAF50; background-color: rgba(76, 175, 80, 0.1); border-radius: 8px;"> |
|
<p style="color: #333; font-size: 18px; line-height: 1.6; margin: 0 0 10px 0; font-weight: 500;">句子内容</p> |
|
<div style="color: #666; font-size: 14px; margin-top: 10px; padding: 10px; background-color: rgba(255,255,255,0.7); border-radius: 5px;"> |
|
<strong>Risk Level:</strong> <span style="color: #4CAF50; font-weight: bold;">green</span><br> |
|
<strong>Issues:</strong> 具体问题<br> |
|
<strong>Explanation:</strong> 详细解释 |
|
</div> |
|
</div> |
|
</div> |
|
``` |
|
|
|
请为原文的每个句子生成对应的HTML标注块。 |
|
""" |
|
|
|
elif stage == 3: |
|
stage_title = "✨ Stage 3: Revision Suggestions" |
|
user_prompt = f""" |
|
基于前面的分析和标注,现在请提供修改后的文本版本,并生成HTML格式的对比显示。 |
|
|
|
要求: |
|
1. 保持原意的前提下修正所有问题 |
|
2. 确保符合{country_cn}的{standard} |
|
3. 对比显示哪些句子被修改了,哪些保持不变 |
|
|
|
请直接返回HTML代码,格式如下: |
|
```html |
|
<div style="background-color: rgba(255,255,255,0.5); padding: 20px; border-radius: 10px;"> |
|
<!-- 未修改的句子 --> |
|
<div style="margin: 15px 0; padding: 15px; border-left: 4px solid #4CAF50; background-color: rgba(76, 175, 80, 0.1); border-radius: 8px;"> |
|
<p style="color: #4CAF50; font-size: 18px; line-height: 1.6; margin: 0; font-weight: bold;">未修改的句子内容</p> |
|
</div> |
|
|
|
<!-- 修改过的句子 --> |
|
<div style="margin: 15px 0; padding: 15px; border-left: 4px solid #333; background-color: rgba(76, 175, 80, 0.05); border-radius: 8px;"> |
|
<p style="color: #333; font-size: 18px; line-height: 1.6; margin: 0 0 10px 0;">修改后的句子内容</p> |
|
<div style="color: #666; font-size: 14px; margin-top: 10px; padding: 10px; background-color: rgba(255,255,255,0.7); border-radius: 5px;"> |
|
<strong>Original:</strong> 原始句子内容 |
|
</div> |
|
</div> |
|
</div> |
|
``` |
|
|
|
请为每个句子生成对应的HTML显示块。 |
|
""" |
|
|
|
|
|
current_messages = messages.copy() |
|
current_messages.append({"role": "user", "content": user_prompt}) |
|
|
|
|
|
accumulated_content = "" |
|
accumulated_reasoning = "" |
|
|
|
try: |
|
|
|
from openai import OpenAI |
|
|
|
client = OpenAI( |
|
base_url=model_config["base_url"].replace("/chat/completions", "/"), |
|
api_key=model_config["api_key"] |
|
) |
|
|
|
|
|
request_params = { |
|
"model": model_config["model"], |
|
"messages": current_messages, |
|
"stream": True, |
|
"max_tokens": 4096, |
|
"temperature": 0.1 |
|
} |
|
|
|
|
|
|
|
|
|
response = client.chat.completions.create(**request_params) |
|
|
|
|
|
for chunk in response: |
|
if chunk.choices[0].delta.content: |
|
accumulated_content += chunk.choices[0].delta.content |
|
if hasattr(chunk.choices[0].delta, 'reasoning_content') and chunk.choices[0].delta.reasoning_content: |
|
accumulated_reasoning += chunk.choices[0].delta.reasoning_content |
|
|
|
|
|
yield generate_streaming_stage_html(stage, stage_title, accumulated_content, accumulated_reasoning) |
|
|
|
|
|
messages.extend([ |
|
{"role": "user", "content": user_prompt}, |
|
{"role": "assistant", "content": accumulated_content} |
|
]) |
|
|
|
except Exception as e: |
|
print(f"Streaming API call error: {e}") |
|
yield generate_error_card(str(e)) |
|
|
|
|
|
def generate_streaming_stage_html(stage: int, title: str, content: str, reasoning: str) -> str: |
|
"""生成流式输出的阶段HTML""" |
|
|
|
|
|
if stage == 1: |
|
description = "AI is analyzing the overall risk and compliance..." |
|
next_stage = "proceeding to detailed annotation..." |
|
content_title = "📊 Analysis Results" |
|
elif stage == 2: |
|
description = "AI is creating color-coded sentence annotations..." |
|
next_stage = "generating revision suggestions..." |
|
content_title = "🎨 Annotated Content" |
|
else: |
|
description = "AI is providing corrected text and comparisons..." |
|
next_stage = "analysis complete!" |
|
content_title = "✨ Revised Content" |
|
|
|
|
|
is_complete = len(content.strip()) > 50 |
|
|
|
|
|
if stage >= 2 and '<div' in content: |
|
|
|
content_display = clean_ai_html_content(content) |
|
content_style = "" |
|
else: |
|
|
|
content_display = clean_markdown_format(content) |
|
content_style = "white-space: pre-wrap;" |
|
|
|
return f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 25px; |
|
"> |
|
<div style=" |
|
background-color: #F2EDE9; |
|
border-radius: 15px; |
|
padding: 25px; |
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
|
"> |
|
<h2 style=" |
|
color: #5B5B5B; |
|
margin-bottom: 20px; |
|
font-size: 28px; |
|
border-bottom: 3px solid #9A8F8F; |
|
padding-bottom: 10px; |
|
">{title}</h2> |
|
|
|
<!-- 折叠的推理过程 - 移到正文上方,随时可展开 --> |
|
<details style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 15px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<summary style=" |
|
color: #5B5B5B; |
|
font-size: 18px; |
|
font-weight: bold; |
|
cursor: pointer; |
|
padding: 10px; |
|
background-color: rgba(154, 143, 143, 0.1); |
|
border-radius: 5px; |
|
display: block; |
|
">🧠 AI Reasoning Process (Click to expand/collapse)</summary> |
|
<div style=" |
|
color: #5B5B5B; |
|
font-size: 14px; |
|
line-height: 1.6; |
|
padding: 15px; |
|
background-color: rgba(255,255,255,0.3); |
|
border-radius: 8px; |
|
white-space: pre-wrap; |
|
font-family: 'Courier New', monospace; |
|
margin-top: 10px; |
|
min-height: 30px; |
|
">{reasoning if reasoning else 'AI reasoning process will appear here as analysis progresses...'}</div> |
|
</details> |
|
|
|
<!-- 分析内容 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.7); |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
border-left: 4px solid #4CAF50; |
|
"> |
|
<h3 style="color: #5B5B5B; margin-bottom: 15px; font-size: 20px;">{content_title}</h3> |
|
<div style=" |
|
color: #5B5B5B; |
|
font-size: 16px; |
|
line-height: 1.8; |
|
{content_style} |
|
min-height: 50px; |
|
">{content_display}</div> |
|
</div> |
|
|
|
<div style=" |
|
text-align: center; |
|
color: {'#4A7C59' if is_complete else '#FF9800'}; |
|
font-size: 16px; |
|
font-weight: bold; |
|
padding: 15px; |
|
background-color: rgba({'74, 124, 89' if is_complete else '255, 152, 0'}, 0.1); |
|
border-radius: 8px; |
|
">{'✅ Stage ' + str(stage) + ' Complete, ' + next_stage if is_complete else '🔄 ' + description}</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
def clean_ai_html_content(content: str) -> str: |
|
"""清理AI返回的HTML内容,移除markdown标记""" |
|
|
|
if "```html" in content: |
|
content = content.split("```html")[1].split("```")[0] |
|
elif "```" in content: |
|
|
|
parts = content.split("```") |
|
if len(parts) >= 3: |
|
content = parts[1] |
|
|
|
|
|
content = content.strip() |
|
|
|
return content |
|
|
|
|
|
def clean_markdown_format(text: str) -> str: |
|
"""清理文本中的markdown格式标记""" |
|
|
|
text = text.replace('""', '"') |
|
text = text.replace('"', '"') |
|
text = text.replace('"', '"') |
|
|
|
|
|
text = text.replace('**', '') |
|
text = text.replace('__', '') |
|
|
|
|
|
text = text.replace('*', '') |
|
text = text.replace('_', '') |
|
|
|
|
|
text = text.replace('`', '') |
|
text = text.replace('~~', '') |
|
|
|
return text |
|
|
|
|
|
def generate_analysis_summary() -> str: |
|
"""生成分析摘要界面""" |
|
return f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
"> |
|
<div style=" |
|
background-color: #F2EDE9; |
|
border-radius: 15px; |
|
padding: 25px; |
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
|
"> |
|
<h2 style=" |
|
color: #5B5B5B; |
|
margin-bottom: 20px; |
|
font-size: 28px; |
|
border-bottom: 3px solid #9A8F8F; |
|
padding-bottom: 10px; |
|
">📊 Analysis Summary</h2> |
|
|
|
<div style=" |
|
background-color: rgba(255,255,255,0.7); |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<h3 style="color: #5B5B5B; margin-bottom: 15px; font-size: 20px;">🎯 Three-Stage Analysis Complete</h3> |
|
|
|
<div style="margin: 15px 0;"> |
|
<h4 style="color: #4CAF50; margin-bottom: 10px;">🔍 Stage 1: Basic Analysis</h4> |
|
<p style="color: #5B5B5B; margin-left: 20px;">Initial risk assessment and compliance evaluation</p> |
|
</div> |
|
|
|
<div style="margin: 15px 0;"> |
|
<h4 style="color: #FF9800; margin-bottom: 10px;">🎨 Stage 2: Detailed Annotation</h4> |
|
<p style="color: #5B5B5B; margin-left: 20px;">Sentence-level risk marking with color coding</p> |
|
</div> |
|
|
|
<div style="margin: 15px 0;"> |
|
<h4 style="color: #9C27B0; margin-bottom: 10px;">✨ Stage 3: Revision Suggestions</h4> |
|
<p style="color: #5B5B5B; margin-left: 20px;">Corrected text with compliance improvements</p> |
|
</div> |
|
</div> |
|
|
|
<div style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<h3 style="color: #5B5B5B; margin-bottom: 15px; font-size: 20px;">💡 How to Use</h3> |
|
<ul style="color: #5B5B5B; line-height: 1.8;"> |
|
<li><strong>Switch between tabs</strong> to view different analysis stages</li> |
|
<li><strong>Click the reasoning sections</strong> to see AI's thought process</li> |
|
<li><strong>Compare original and revised text</strong> in Stage 3</li> |
|
<li><strong>Use color coding</strong> in Stage 2 to identify risk levels</li> |
|
</ul> |
|
</div> |
|
|
|
<div style=" |
|
text-align: center; |
|
color: #4A7C59; |
|
font-size: 18px; |
|
font-weight: bold; |
|
padding: 20px; |
|
background-color: rgba(74, 124, 89, 0.1); |
|
border-radius: 8px; |
|
">🎉 Content Audit Complete! Review all stages above.</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
def perform_reasoning_analysis(text: str, keywords: List[str], country: str, language: str, model: str, messages: list, stage: int) -> dict: |
|
"""执行带reasoning的AI分析""" |
|
|
|
|
|
model_mapping = { |
|
"Qwen/QwQ-32B (Reasoning - SiliconFlow)": "Qwen/QwQ-32B", |
|
"Pro/deepseek-ai/DeepSeek-R1 (Reasoning - SiliconFlow)": "Pro/deepseek-ai/DeepSeek-R1", |
|
"Qwen/Qwen3-8B (Free - SiliconFlow)": "Qwen/Qwen3-8B", |
|
"deepseek-ai/DeepSeek-V3-0324 (Paid - Nebius)": "deepseek-ai/DeepSeek-V3-0324", |
|
"deepseek-ai/DeepSeek-R1-0528 (Paid - Nebius)": "deepseek-ai/DeepSeek-R1-0528", |
|
"Qwen/Qwen3-235B-A22B (Paid - Nebius)": "Qwen/Qwen3-235B-A22B", |
|
"google/gemma-3-27b-it (Paid - Nebius, Vision)": "google/gemma-3-27b-it" |
|
} |
|
|
|
actual_model = model_mapping.get(model, "Qwen/QwQ-32B") |
|
|
|
|
|
country_standards = { |
|
"China": "中国广告法和相关法规", |
|
"USA": "美国FTC广告标准", |
|
"UK": "英国ASA广告标准", |
|
"Japan": "日本景品表示法", |
|
"Korea": "韩国公正交易委员会标准", |
|
"Singapore": "新加坡CCCS标准", |
|
"Malaysia": "马来西亚广告标准", |
|
"Other": "国际通用广告标准" |
|
} |
|
|
|
country_names = { |
|
"China": "中国", |
|
"USA": "美国", |
|
"UK": "英国", |
|
"Japan": "日本", |
|
"Korea": "韩国", |
|
"Singapore": "新加坡", |
|
"Malaysia": "马来西亚", |
|
"Other": "其他地区" |
|
} |
|
|
|
standard = country_standards.get(country, "国际通用广告标准") |
|
country_cn = country_names.get(country, country) |
|
keywords_str = ", ".join(keywords) if keywords else "无" |
|
|
|
|
|
if stage == 1: |
|
user_prompt = f""" |
|
你是专业的内容审核专家,请根据{country_cn}的{standard}对以下文本进行初步分析。 |
|
|
|
文本内容:{text} |
|
关键词:{keywords_str} |
|
审核地区:{country_cn} |
|
回答语言:{language} |
|
|
|
请进行基础分析,包括: |
|
1. 整体风险评估 |
|
2. 主要问题识别 |
|
3. 法规符合性初判 |
|
|
|
请用{language}回答,提供清晰的分析结论。 |
|
""" |
|
|
|
elif stage == 2: |
|
user_prompt = f""" |
|
基于前面的分析,现在请对文本进行详细的句子级标注分析。 |
|
|
|
请为每个句子标注风险等级: |
|
- 绿色:无问题,表述规范 |
|
- 黄色:轻微问题,建议优化 |
|
- 橙色:中等问题,需要修改 |
|
- 红色:严重问题,必须修改 |
|
|
|
对每个句子说明具体问题和原因。 |
|
""" |
|
|
|
elif stage == 3: |
|
user_prompt = f""" |
|
基于前面的分析和标注,现在请提供修改后的文本版本。 |
|
|
|
要求: |
|
1. 保持原意的前提下修正所有问题 |
|
2. 确保符合{country_cn}的{standard} |
|
3. 对比显示哪些句子被修改了,哪些保持不变 |
|
|
|
请提供完整的修改版本。 |
|
""" |
|
|
|
|
|
current_messages = messages.copy() |
|
current_messages.append({"role": "user", "content": user_prompt}) |
|
|
|
|
|
try: |
|
import requests |
|
|
|
|
|
is_reasoning_model = actual_model in ["Qwen/QwQ-32B", "Pro/deepseek-ai/DeepSeek-R1"] |
|
|
|
api_key = os.getenv("SILICONFLOW_API_KEY", "sk-your-api-key-here") |
|
|
|
payload = { |
|
"model": actual_model, |
|
"messages": current_messages, |
|
"stream": True, |
|
"max_tokens": 4096, |
|
"temperature": 0.1 |
|
} |
|
|
|
|
|
if is_reasoning_model: |
|
if actual_model == "Qwen/QwQ-32B": |
|
payload["enable_thinking"] = True |
|
payload["thinking_budget"] = 4096 |
|
elif actual_model == "Pro/deepseek-ai/DeepSeek-R1": |
|
payload["extra_body"] = {"thinking_budget": 1024} |
|
|
|
headers = { |
|
"Authorization": f"Bearer {api_key}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
response = requests.post( |
|
"https://api.siliconflow.cn/v1/chat/completions", |
|
json=payload, |
|
headers=headers, |
|
stream=True |
|
) |
|
|
|
content = "" |
|
reasoning_content = "" |
|
|
|
|
|
for line in response.iter_lines(): |
|
if line: |
|
line_str = line.decode('utf-8') |
|
if line_str.startswith('data: '): |
|
data_str = line_str[6:] |
|
if data_str == '[DONE]': |
|
break |
|
try: |
|
chunk = json.loads(data_str) |
|
if 'choices' in chunk and len(chunk['choices']) > 0: |
|
delta = chunk['choices'][0].get('delta', {}) |
|
if 'content' in delta and delta['content']: |
|
content += delta['content'] |
|
if 'reasoning_content' in delta and delta['reasoning_content']: |
|
reasoning_content += delta['reasoning_content'] |
|
except json.JSONDecodeError: |
|
continue |
|
|
|
return { |
|
"content": content, |
|
"reasoning_content": reasoning_content, |
|
"user_prompt": user_prompt, |
|
"stage": stage |
|
} |
|
|
|
except Exception as e: |
|
print(f"API call error: {e}") |
|
return { |
|
"content": f"API调用失败: {str(e)}", |
|
"reasoning_content": "无法获取推理过程", |
|
"user_prompt": user_prompt, |
|
"stage": stage |
|
} |
|
|
|
|
|
def generate_stage1_html(result: dict) -> str: |
|
"""生成第一阶段的分析报告HTML""" |
|
content = result.get("content", "") |
|
reasoning = result.get("reasoning_content", "") |
|
|
|
return f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 25px; |
|
"> |
|
<div style=" |
|
background-color: #F2EDE9; |
|
border-radius: 15px; |
|
padding: 25px; |
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
|
"> |
|
<h2 style=" |
|
color: #5B5B5B; |
|
margin-bottom: 20px; |
|
font-size: 28px; |
|
border-bottom: 3px solid #9A8F8F; |
|
padding-bottom: 10px; |
|
">📋 Stage 1: AI Analysis Report</h2> |
|
|
|
<!-- 分析内容 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.7); |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
border-left: 4px solid #4CAF50; |
|
"> |
|
<h3 style="color: #5B5B5B; margin-bottom: 15px; font-size: 20px;">📊 Analysis Results</h3> |
|
<div style=" |
|
color: #5B5B5B; |
|
font-size: 16px; |
|
line-height: 1.8; |
|
white-space: pre-wrap; |
|
">{content}</div> |
|
</div> |
|
|
|
<!-- 折叠的推理过程 --> |
|
<details style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 15px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<summary style=" |
|
color: #5B5B5B; |
|
font-size: 18px; |
|
font-weight: bold; |
|
cursor: pointer; |
|
padding: 10px; |
|
background-color: rgba(154, 143, 143, 0.1); |
|
border-radius: 5px; |
|
margin-bottom: 15px; |
|
">🧠 AI Reasoning Process (Click to expand/collapse)</summary> |
|
<div style=" |
|
color: #5B5B5B; |
|
font-size: 14px; |
|
line-height: 1.6; |
|
padding: 15px; |
|
background-color: rgba(255,255,255,0.3); |
|
border-radius: 8px; |
|
white-space: pre-wrap; |
|
font-family: 'Courier New', monospace; |
|
">{reasoning}</div> |
|
</details> |
|
|
|
<div style=" |
|
text-align: center; |
|
color: #4A7C59; |
|
font-size: 16px; |
|
font-weight: bold; |
|
padding: 15px; |
|
background-color: rgba(74, 124, 89, 0.1); |
|
border-radius: 8px; |
|
">✅ Stage 1 Complete, proceeding to detailed annotation...</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
def generate_stage2_html(result: dict) -> str: |
|
"""生成第二阶段的详细标注HTML""" |
|
content = result.get("content", "") |
|
reasoning = result.get("reasoning_content", "") |
|
|
|
return f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 25px; |
|
"> |
|
<div style=" |
|
background-color: #F2EDE9; |
|
border-radius: 15px; |
|
padding: 25px; |
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
|
"> |
|
<h2 style=" |
|
color: #5B5B5B; |
|
margin-bottom: 20px; |
|
font-size: 28px; |
|
border-bottom: 3px solid #9A8F8F; |
|
padding-bottom: 10px; |
|
">🎨 Stage 2: Detailed Annotation</h2> |
|
|
|
<!-- 颜色图例 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.7); |
|
padding: 15px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<h4 style="color: #5B5B5B; margin-bottom: 10px;">Color Legend:</h4> |
|
<div style="display: flex; flex-wrap: wrap; gap: 15px;"> |
|
<span style="color: #4CAF50; font-weight: bold;">🟢 Green: No Issues</span> |
|
<span style="color: #FFC107; font-weight: bold;">🟡 Yellow: Minor Issues</span> |
|
<span style="color: #FF9800; font-weight: bold;">🟠 Orange: Moderate Issues</span> |
|
<span style="color: #F44336; font-weight: bold;">🔴 Red: Serious Issues</span> |
|
</div> |
|
</div> |
|
|
|
<!-- 标注内容 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<div style=" |
|
color: #5B5B5B; |
|
font-size: 16px; |
|
line-height: 1.8; |
|
white-space: pre-wrap; |
|
">{content}</div> |
|
</div> |
|
|
|
<!-- 折叠的推理过程 --> |
|
<details style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 15px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<summary style=" |
|
color: #5B5B5B; |
|
font-size: 18px; |
|
font-weight: bold; |
|
cursor: pointer; |
|
padding: 10px; |
|
background-color: rgba(154, 143, 143, 0.1); |
|
border-radius: 5px; |
|
margin-bottom: 15px; |
|
">🧠 AI Reasoning Process (Click to expand/collapse)</summary> |
|
<div style=" |
|
color: #5B5B5B; |
|
font-size: 14px; |
|
line-height: 1.6; |
|
padding: 15px; |
|
background-color: rgba(255,255,255,0.3); |
|
border-radius: 8px; |
|
white-space: pre-wrap; |
|
font-family: 'Courier New', monospace; |
|
">{reasoning}</div> |
|
</details> |
|
|
|
<div style=" |
|
text-align: center; |
|
color: #4A7C59; |
|
font-size: 16px; |
|
font-weight: bold; |
|
padding: 15px; |
|
background-color: rgba(74, 124, 89, 0.1); |
|
border-radius: 8px; |
|
">✅ Stage 2 Complete, generating revision suggestions...</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
def generate_stage3_html(result: dict) -> str: |
|
"""生成第三阶段的修改建议HTML""" |
|
content = result.get("content", "") |
|
reasoning = result.get("reasoning_content", "") |
|
|
|
return f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 25px; |
|
"> |
|
<div style=" |
|
background-color: #F2EDE9; |
|
border-radius: 15px; |
|
padding: 25px; |
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
|
"> |
|
<h2 style=" |
|
color: #5B5B5B; |
|
margin-bottom: 20px; |
|
font-size: 28px; |
|
border-bottom: 3px solid #9A8F8F; |
|
padding-bottom: 10px; |
|
">✨ Stage 3: Revision Suggestions</h2> |
|
|
|
<!-- 说明 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.7); |
|
padding: 15px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<h4 style="color: #5B5B5B; margin-bottom: 10px;">Legend:</h4> |
|
<p style="color: #4CAF50; font-weight: bold; margin: 5px 0;">🟢 Green: Unchanged sentences (compliant)</p> |
|
<p style="color: #333; margin: 5px 0;">⚫ Normal: Modified sentences (with explanations)</p> |
|
</div> |
|
|
|
<!-- 修改建议内容 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<div style=" |
|
color: #5B5B5B; |
|
font-size: 16px; |
|
line-height: 1.8; |
|
white-space: pre-wrap; |
|
">{content}</div> |
|
</div> |
|
|
|
<!-- 折叠的推理过程 --> |
|
<details style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 15px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<summary style=" |
|
color: #5B5B5B; |
|
font-size: 18px; |
|
font-weight: bold; |
|
cursor: pointer; |
|
padding: 10px; |
|
background-color: rgba(154, 143, 143, 0.1); |
|
border-radius: 5px; |
|
margin-bottom: 15px; |
|
">🧠 AI Reasoning Process (Click to expand/collapse)</summary> |
|
<div style=" |
|
color: #5B5B5B; |
|
font-size: 14px; |
|
line-height: 1.6; |
|
padding: 15px; |
|
background-color: rgba(255,255,255,0.3); |
|
border-radius: 8px; |
|
white-space: pre-wrap; |
|
font-family: 'Courier New', monospace; |
|
">{reasoning}</div> |
|
</details> |
|
|
|
<div style=" |
|
text-align: center; |
|
color: #4A7C59; |
|
font-size: 18px; |
|
font-weight: bold; |
|
padding: 20px; |
|
background-color: rgba(74, 124, 89, 0.1); |
|
border-radius: 8px; |
|
">🎉 All Stages Complete! Content audit finished.</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
def get_ai_generated_html(text: str, keywords: List[str], country: str, language: str, model: str, stage: int) -> str: |
|
"""获取AI生成的HTML内容""" |
|
ai = AIContentAnalyzer() |
|
|
|
|
|
model_mapping = { |
|
"Qwen/Qwen3-8B (Free - SiliconFlow)": "Qwen/Qwen3-8B", |
|
"deepseek-ai/DeepSeek-V3-0324 (Paid - Nebius)": "deepseek-ai/DeepSeek-V3-0324", |
|
"deepseek-ai/DeepSeek-R1-0528 (Paid - Nebius)": "deepseek-ai/DeepSeek-R1-0528", |
|
"Qwen/Qwen3-235B-A22B (Paid - Nebius)": "Qwen/Qwen3-235B-A22B", |
|
"google/gemma-3-27b-it (Paid - Nebius, Vision)": "google/gemma-3-27b-it" |
|
} |
|
|
|
actual_model = model_mapping.get(model, "Qwen/Qwen3-8B") |
|
ai.set_model(actual_model) |
|
|
|
|
|
country_standards = { |
|
"China": "中国广告法和相关法规", |
|
"USA": "美国FTC广告标准", |
|
"UK": "英国ASA广告标准", |
|
"Japan": "日本景品表示法", |
|
"Korea": "韩国公正交易委员会标准", |
|
"Singapore": "新加坡CCCS标准", |
|
"Malaysia": "马来西亚广告标准", |
|
"Other": "国际通用广告标准" |
|
} |
|
|
|
country_names = { |
|
"China": "中国", |
|
"USA": "美国", |
|
"UK": "英国", |
|
"Japan": "日本", |
|
"Korea": "韩国", |
|
"Singapore": "新加坡", |
|
"Malaysia": "马来西亚", |
|
"Other": "其他地区" |
|
} |
|
|
|
standard = country_standards.get(country, "国际通用广告标准") |
|
country_cn = country_names.get(country, country) |
|
keywords_str = ", ".join(keywords) if keywords else "无" |
|
|
|
|
|
if stage == 1: |
|
prompt = f""" |
|
你是专业的内容审核专家,请根据{country_cn}的{standard}对以下文本进行全面审核分析。 |
|
|
|
文本内容:{text} |
|
关键词:{keywords_str} |
|
审核地区:{country_cn} |
|
回答语言:{language} |
|
|
|
请生成第一阶段的分析报告HTML,包含: |
|
1. 详细的推理过程(放在折叠的思考框中) |
|
2. 分析摘要(发现的问题总数、风险等级、主要问题) |
|
|
|
请直接返回完整的HTML代码,使用以下样式框架,并填入具体内容: |
|
|
|
<div style="max-width: 900px; margin: 0 auto; padding: 30px; background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); border-radius: 20px; font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif;"> |
|
<div style="background-color: #F2EDE9; border-radius: 15px; padding: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);"> |
|
<h2 style="color: #5B5B5B; margin-bottom: 20px; font-size: 28px; border-bottom: 3px solid #9A8F8F; padding-bottom: 10px;">📋 Step 1: AI Analysis Report</h2> |
|
|
|
<!-- 分析摘要 --> |
|
<div style="background-color: rgba(255,255,255,0.7); padding: 20px; border-radius: 10px; margin-bottom: 20px; border-left: 4px solid #FF9800;"> |
|
<h3 style="color: #5B5B5B; margin-bottom: 15px; font-size: 20px;">📊 Analysis Summary</h3> |
|
<p style="color: #5B5B5B; font-size: 18px; margin: 10px 0;"><strong>Issues Found:</strong> [填入具体数字]</p> |
|
<p style="color: #5B5B5B; font-size: 18px; margin: 10px 0;"><strong>Risk Level:</strong> <span style="color: #FF9800; font-weight: bold;">[填入风险等级]</span></p> |
|
<p style="color: #5B5B5B; font-size: 16px; margin: 10px 0;"><strong>Main Concerns:</strong> [填入主要问题]</p> |
|
</div> |
|
|
|
<!-- 折叠的思考过程 --> |
|
<details style="background-color: rgba(255,255,255,0.5); padding: 15px; border-radius: 10px; margin-bottom: 20px;"> |
|
<summary style="color: #5B5B5B; font-size: 18px; font-weight: bold; cursor: pointer; padding: 10px; background-color: rgba(154, 143, 143, 0.1); border-radius: 5px; margin-bottom: 15px;">🧠 AI Reasoning Process (Click to expand/collapse)</summary> |
|
<div style="color: #5B5B5B; font-size: 16px; line-height: 1.6; padding: 15px; background-color: rgba(255,255,255,0.3); border-radius: 8px; white-space: pre-wrap;"> |
|
[在这里填入详细的推理过程,包括: |
|
1. 对文本的初步分析 |
|
2. 发现的具体问题 |
|
3. 风险评估的依据 |
|
4. 法规符合性分析 |
|
等等] |
|
</div> |
|
</details> |
|
|
|
<div style="text-align: center; color: #4A7C59; font-size: 16px; font-weight: bold; padding: 15px; background-color: rgba(74, 124, 89, 0.1); border-radius: 8px;">✅ Step 1 Complete, generating annotated version...</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
elif stage == 2: |
|
prompt = f""" |
|
请为以下文本生成第二阶段的标注版HTML: |
|
|
|
原文:{text} |
|
审核标准:{country_cn}的{standard} |
|
|
|
请生成带颜色标注的HTML版本,要求: |
|
1. 每个句子根据风险等级标注颜色(绿色=无问题,黄色=轻微,橙色=中等,红色=严重) |
|
2. 在句子下方添加问题说明 |
|
3. 包含颜色图例 |
|
|
|
请直接返回完整的HTML代码,使用以下样式框架: |
|
|
|
<div style="max-width: 900px; margin: 0 auto; padding: 30px; background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); border-radius: 20px; font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif;"> |
|
<div style="background-color: #F2EDE9; border-radius: 15px; padding: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);"> |
|
<h2 style="color: #5B5B5B; margin-bottom: 20px; font-size: 28px; border-bottom: 3px solid #9A8F8F; padding-bottom: 10px;">🎨 Step 2: Annotated Version</h2> |
|
|
|
<!-- 颜色图例 --> |
|
<div style="background-color: rgba(255,255,255,0.7); padding: 15px; border-radius: 10px; margin-bottom: 20px;"> |
|
<h4 style="color: #5B5B5B; margin-bottom: 10px;">Color Legend:</h4> |
|
<div style="display: flex; flex-wrap: wrap; gap: 15px;"> |
|
<span style="color: #4CAF50; font-weight: bold;">🟢 Green: No Issues</span> |
|
<span style="color: #FFC107; font-weight: bold;">🟡 Yellow: Minor Issues</span> |
|
<span style="color: #FF9800; font-weight: bold;">🟠 Orange: Moderate Issues</span> |
|
<span style="color: #F44336; font-weight: bold;">🔴 Red: Serious Issues</span> |
|
</div> |
|
</div> |
|
|
|
<!-- 标注内容 --> |
|
<div style="background-color: rgba(255,255,255,0.5); padding: 20px; border-radius: 10px; margin-bottom: 20px;"> |
|
[在这里为每个句子生成标注,格式如下: |
|
|
|
<div style="margin: 15px 0; padding: 15px; border-left: 5px solid #4CAF50; background-color: rgba(76, 175, 80, 0.1); border-radius: 8px;"> |
|
<p style="color: #333; font-size: 18px; line-height: 1.6; margin: 0 0 10px 0; font-weight: 500;">句子内容</p> |
|
<div style="color: #666; font-size: 14px; margin-top: 10px; padding: 10px; background-color: rgba(255,255,255,0.7); border-radius: 5px;"> |
|
<strong>Risk Level:</strong> <span style="color: #4CAF50; font-weight: bold;">green</span><br> |
|
<strong>Issues:</strong> 无问题<br> |
|
<strong>Explanation:</strong> 表述规范,符合法规要求 |
|
</div> |
|
</div> |
|
|
|
请为每个句子生成类似的标注块,根据实际风险等级调整颜色] |
|
</div> |
|
|
|
<div style="text-align: center; color: #4A7C59; font-size: 16px; font-weight: bold; padding: 15px; background-color: rgba(74, 124, 89, 0.1); border-radius: 8px;">✅ Step 2 Complete, generating revised version...</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
elif stage == 3: |
|
prompt = f""" |
|
请为以下文本生成第三阶段的修改版HTML: |
|
|
|
原文:{text} |
|
审核标准:{country_cn}的{standard} |
|
|
|
请生成修改后的文本对比版,要求: |
|
1. 显示修改后的完整文本 |
|
2. 未修改的句子用绿色粗体显示 |
|
3. 修改过的句子正常显示,并在下方显示原文 |
|
|
|
请直接返回完整的HTML代码,使用以下样式框架: |
|
|
|
<div style="max-width: 900px; margin: 0 auto; padding: 30px; background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); border-radius: 20px; font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif;"> |
|
<div style="background-color: #F2EDE9; border-radius: 15px; padding: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);"> |
|
<h2 style="color: #5B5B5B; margin-bottom: 20px; font-size: 28px; border-bottom: 3px solid #9A8F8F; padding-bottom: 10px;">✨ Step 3: Revised Text</h2> |
|
|
|
<!-- 说明 --> |
|
<div style="background-color: rgba(255,255,255,0.7); padding: 15px; border-radius: 10px; margin-bottom: 20px;"> |
|
<h4 style="color: #5B5B5B; margin-bottom: 10px;">Legend:</h4> |
|
<p style="color: #4CAF50; font-weight: bold; margin: 5px 0;">🟢 Green Bold: Unchanged sentences (compliant)</p> |
|
<p style="color: #333; margin: 5px 0;">⚫ Normal: Modified sentences (with original shown below)</p> |
|
</div> |
|
|
|
<!-- 修改后的文本 --> |
|
<div style="background-color: rgba(255,255,255,0.5); padding: 20px; border-radius: 10px; margin-bottom: 20px;"> |
|
[在这里生成修改后的文本对比,格式如下: |
|
|
|
未修改的句子: |
|
<div style="margin: 15px 0; padding: 15px; border-left: 4px solid #4CAF50; background-color: rgba(76, 175, 80, 0.1); border-radius: 8px;"> |
|
<p style="color: #4CAF50; font-size: 18px; line-height: 1.6; margin: 0; font-weight: bold;">句子内容</p> |
|
</div> |
|
|
|
修改过的句子: |
|
<div style="margin: 15px 0; padding: 15px; border-left: 4px solid #333; background-color: rgba(76, 175, 80, 0.05); border-radius: 8px;"> |
|
<p style="color: #333; font-size: 18px; line-height: 1.6; margin: 0 0 10px 0;">修改后的句子</p> |
|
<div style="color: #666; font-size: 14px; margin-top: 10px; padding: 10px; background-color: rgba(255,255,255,0.7); border-radius: 5px;"> |
|
<strong>Original:</strong> 原始句子 |
|
</div> |
|
</div> |
|
|
|
请为每个句子生成对应的显示块] |
|
</div> |
|
|
|
<div style="text-align: center; color: #4A7C59; font-size: 18px; font-weight: bold; padding: 20px; background-color: rgba(74, 124, 89, 0.1); border-radius: 8px;">🎉 All Steps Complete! Content audit finished.</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
try: |
|
loop = asyncio.new_event_loop() |
|
asyncio.set_event_loop(loop) |
|
|
|
try: |
|
result = loop.run_until_complete(call_ai_api(ai, prompt)) |
|
return result |
|
finally: |
|
loop.close() |
|
|
|
except Exception as e: |
|
print(f"AI API error: {e}") |
|
return generate_error_card(str(e)) |
|
|
|
|
|
async def call_ai_api(ai: AIContentAnalyzer, prompt: str) -> str: |
|
"""调用AI API获取HTML响应""" |
|
try: |
|
headers = { |
|
"Authorization": f"Bearer {ai.api_key}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
data = { |
|
"model": ai.model, |
|
"messages": [ |
|
{"role": "system", "content": "你是专业的内容审核专家,请直接返回完整的HTML代码,不要添加任何markdown标记或其他格式。"}, |
|
{"role": "user", "content": prompt} |
|
], |
|
"temperature": 0.1, |
|
"max_tokens": 4000 |
|
} |
|
|
|
async with aiohttp.ClientSession() as session: |
|
async with session.post(ai.base_url, headers=headers, json=data) as response: |
|
if response.status == 200: |
|
result = await response.json() |
|
ai_response = result["choices"][0]["message"]["content"] |
|
|
|
|
|
if "```html" in ai_response: |
|
ai_response = ai_response.split("```html")[1].split("```")[0] |
|
elif "```" in ai_response: |
|
ai_response = ai_response.split("```")[1].split("```")[0] |
|
|
|
return ai_response.strip() |
|
else: |
|
return generate_error_card("AI API调用失败") |
|
|
|
except Exception as e: |
|
print(f"AI API call error: {e}") |
|
return generate_error_card(str(e)) |
|
|
|
|
|
async def get_complete_ai_analysis(text: str, keywords: List[str], country: str, language: str, model: str): |
|
"""获取完整的AI分析结果""" |
|
ai = AIContentAnalyzer() |
|
|
|
|
|
model_mapping = { |
|
"Qwen/Qwen3-8B (Free - SiliconFlow)": "Qwen/Qwen3-8B", |
|
"deepseek-ai/DeepSeek-V3-0324 (Paid - Nebius)": "deepseek-ai/DeepSeek-V3-0324", |
|
"deepseek-ai/DeepSeek-R1-0528 (Paid - Nebius)": "deepseek-ai/DeepSeek-R1-0528", |
|
"Qwen/Qwen3-235B-A22B (Paid - Nebius)": "Qwen/Qwen3-235B-A22B", |
|
"google/gemma-3-27b-it (Paid - Nebius, Vision)": "google/gemma-3-27b-it" |
|
} |
|
|
|
actual_model = model_mapping.get(model, "Qwen/Qwen3-8B") |
|
ai.set_model(actual_model) |
|
|
|
|
|
country_standards = { |
|
"China": "中国广告法和相关法规", |
|
"USA": "美国FTC广告标准", |
|
"UK": "英国ASA广告标准", |
|
"Japan": "日本景品表示法", |
|
"Korea": "韩国公正交易委员会标准", |
|
"Singapore": "新加坡CCCS标准", |
|
"Malaysia": "马来西亚广告标准", |
|
"Other": "国际通用广告标准" |
|
} |
|
|
|
country_names = { |
|
"China": "中国", |
|
"USA": "美国", |
|
"UK": "英国", |
|
"Japan": "日本", |
|
"Korea": "韩国", |
|
"Singapore": "新加坡", |
|
"Malaysia": "马来西亚", |
|
"Other": "其他地区" |
|
} |
|
|
|
standard = country_standards.get(country, "国际通用广告标准") |
|
country_cn = country_names.get(country, country) |
|
keywords_str = ", ".join(keywords) if keywords else "无" |
|
|
|
prompt = f""" |
|
你是专业的内容审核专家,请根据{country_cn}的{standard}对以下文本进行全面审核分析。 |
|
|
|
文本内容:{text} |
|
关键词:{keywords_str} |
|
审核地区:{country_cn} |
|
回答语言:{language} |
|
|
|
请按照以下JSON格式返回分析结果: |
|
{{ |
|
"reasoning_content": "详细的推理过程和分析思路", |
|
"sentence_analysis": [ |
|
{{ |
|
"sentence": "具体句子内容", |
|
"risk_level": "green/yellow/orange/red", |
|
"issues": ["具体问题1", "具体问题2"], |
|
"explanation": "为什么认为有问题的详细解释" |
|
}} |
|
], |
|
"revised_text": "修改后的完整文本", |
|
"summary": {{ |
|
"total_issues": 问题总数, |
|
"risk_level": "整体风险等级", |
|
"main_concerns": ["主要问题1", "主要问题2"] |
|
}} |
|
}} |
|
|
|
风险等级说明: |
|
- green: 无问题,表述规范 |
|
- yellow: 轻微问题,建议优化 |
|
- orange: 中等问题,需要修改 |
|
- red: 严重问题,必须修改 |
|
|
|
请确保: |
|
1. 每个句子都要分析并标注风险等级 |
|
2. 问题解释要具体明确 |
|
3. 修改后的文本要保持原意但符合法规 |
|
4. 返回标准的JSON格式 |
|
""" |
|
|
|
try: |
|
headers = { |
|
"Authorization": f"Bearer {ai.api_key}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
data = { |
|
"model": ai.model, |
|
"messages": [ |
|
{"role": "system", "content": f"你是专业的内容审核专家,请根据{country_cn}的法规标准进行分析,并用{language}回答。"}, |
|
{"role": "user", "content": prompt} |
|
], |
|
"temperature": 0.1, |
|
"max_tokens": 4000 |
|
} |
|
|
|
async with aiohttp.ClientSession() as session: |
|
async with session.post(ai.base_url, headers=headers, json=data) as response: |
|
if response.status == 200: |
|
result = await response.json() |
|
ai_response = result["choices"][0]["message"]["content"] |
|
|
|
|
|
try: |
|
|
|
if "```json" in ai_response: |
|
ai_response = ai_response.split("```json")[1].split("```")[0] |
|
elif "```" in ai_response: |
|
ai_response = ai_response.split("```")[1].split("```")[0] |
|
|
|
analysis_result = json.loads(ai_response.strip()) |
|
return analysis_result |
|
except json.JSONDecodeError: |
|
|
|
return create_fallback_structured_analysis(text) |
|
else: |
|
return create_fallback_structured_analysis(text) |
|
|
|
except Exception as e: |
|
print(f"AI analysis error: {e}") |
|
return create_fallback_structured_analysis(text) |
|
|
|
def create_fallback_structured_analysis(text: str) -> Dict: |
|
"""创建备用的结构化分析结果""" |
|
sentences = text.split('。') |
|
sentence_analysis = [] |
|
|
|
for sentence in sentences: |
|
if sentence.strip(): |
|
sentence_analysis.append({ |
|
"sentence": sentence.strip() + "。", |
|
"risk_level": "green", |
|
"issues": [], |
|
"explanation": "AI分析服务暂时不可用,无法进行详细分析" |
|
}) |
|
|
|
return { |
|
"reasoning_content": "AI分析服务暂时不可用,请稍后重试或检查网络连接。", |
|
"sentence_analysis": sentence_analysis, |
|
"revised_text": text, |
|
"summary": { |
|
"total_issues": 0, |
|
"risk_level": "unknown", |
|
"main_concerns": ["AI分析服务暂时不可用"] |
|
} |
|
} |
|
|
|
|
|
def generate_loading_card(title: str, message: str) -> str: |
|
"""生成加载中的卡片""" |
|
return f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
text-align: center; |
|
"> |
|
<div style=" |
|
background-color: #F2EDE9; |
|
border-radius: 15px; |
|
padding: 40px; |
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
|
"> |
|
<h2 style="color: #5B5B5B; margin-bottom: 20px; font-size: 24px;">{title}</h2> |
|
<p style="color: #8C8C8C; margin-bottom: 30px; font-size: 16px;">{message}</p> |
|
<div style=" |
|
width: 200px; |
|
height: 4px; |
|
background: #D1CBC3; |
|
border-radius: 2px; |
|
margin: 0 auto; |
|
overflow: hidden; |
|
"> |
|
<div style=" |
|
width: 30%; |
|
height: 100%; |
|
background: #9A8F8F; |
|
border-radius: 2px; |
|
animation: loading 2s infinite; |
|
"></div> |
|
</div> |
|
</div> |
|
<style> |
|
@keyframes loading {{ |
|
0% {{ transform: translateX(-100%); }} |
|
100% {{ transform: translateX(400%); }} |
|
}} |
|
</style> |
|
</div> |
|
""" |
|
|
|
|
|
def generate_analysis_report_card(analysis_result: Dict) -> str: |
|
"""生成分析报告卡片,reasoning_content放在折叠栏中""" |
|
summary = analysis_result.get("summary", {}) |
|
reasoning = analysis_result.get("reasoning_content", "") |
|
|
|
total_issues = summary.get("total_issues", 0) |
|
risk_level = summary.get("risk_level", "unknown") |
|
main_concerns = summary.get("main_concerns", []) |
|
|
|
|
|
risk_colors = { |
|
"low": "#4CAF50", |
|
"medium": "#FF9800", |
|
"high": "#F44336", |
|
"unknown": "#9E9E9E" |
|
} |
|
|
|
risk_color = risk_colors.get(risk_level, "#9E9E9E") |
|
|
|
return f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 25px; |
|
"> |
|
<div style=" |
|
background-color: #F2EDE9; |
|
border-radius: 15px; |
|
padding: 25px; |
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
|
"> |
|
<h2 style=" |
|
color: #5B5B5B; |
|
margin-bottom: 20px; |
|
font-size: 28px; |
|
border-bottom: 3px solid #9A8F8F; |
|
padding-bottom: 10px; |
|
">📋 Step 1: AI Analysis Report</h2> |
|
|
|
<!-- 摘要信息 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.7); |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
border-left: 4px solid {risk_color}; |
|
"> |
|
<h3 style="color: #5B5B5B; margin-bottom: 15px; font-size: 20px;">📊 Analysis Summary</h3> |
|
<p style="color: #5B5B5B; font-size: 18px; margin: 10px 0;"> |
|
<strong>Issues Found:</strong> {total_issues} |
|
</p> |
|
<p style="color: #5B5B5B; font-size: 18px; margin: 10px 0;"> |
|
<strong>Risk Level:</strong> <span style="color: {risk_color}; font-weight: bold;">{risk_level}</span> |
|
</p> |
|
{f'<p style="color: #5B5B5B; font-size: 16px; margin: 10px 0;"><strong>Main Concerns:</strong> {", ".join(main_concerns)}</p>' if main_concerns else ''} |
|
</div> |
|
|
|
<!-- 折叠的推理过程 --> |
|
<details style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 15px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<summary style=" |
|
color: #5B5B5B; |
|
font-size: 18px; |
|
font-weight: bold; |
|
cursor: pointer; |
|
padding: 10px; |
|
background-color: rgba(154, 143, 143, 0.1); |
|
border-radius: 5px; |
|
margin-bottom: 15px; |
|
">🧠 AI Reasoning Process (Click to expand/collapse)</summary> |
|
<div style=" |
|
color: #5B5B5B; |
|
font-size: 16px; |
|
line-height: 1.6; |
|
padding: 15px; |
|
background-color: rgba(255,255,255,0.3); |
|
border-radius: 8px; |
|
white-space: pre-wrap; |
|
">{reasoning}</div> |
|
</details> |
|
|
|
<div style=" |
|
text-align: center; |
|
color: #4A7C59; |
|
font-size: 16px; |
|
font-weight: bold; |
|
padding: 15px; |
|
background-color: rgba(74, 124, 89, 0.1); |
|
border-radius: 8px; |
|
">✅ Step 1 Complete, generating annotated version...</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
def generate_annotated_html_card(original_text: str, analysis_result: Dict) -> str: |
|
"""生成原文标注版HTML""" |
|
sentence_analysis = analysis_result.get("sentence_analysis", []) |
|
|
|
|
|
risk_colors = { |
|
"green": "#4CAF50", |
|
"yellow": "#FFC107", |
|
"orange": "#FF9800", |
|
"red": "#F44336" |
|
} |
|
|
|
|
|
annotated_html = "" |
|
|
|
for item in sentence_analysis: |
|
sentence = item.get("sentence", "") |
|
risk_level = item.get("risk_level", "green") |
|
issues = item.get("issues", []) |
|
explanation = item.get("explanation", "") |
|
|
|
color = risk_colors.get(risk_level, "#4CAF50") |
|
|
|
|
|
annotated_html += f""" |
|
<div style=" |
|
margin: 15px 0; |
|
padding: 15px; |
|
border-left: 5px solid {color}; |
|
background-color: rgba({','.join([str(int(color[i:i+2], 16)) for i in (1, 3, 5)])}, 0.1); |
|
border-radius: 8px; |
|
"> |
|
<p style=" |
|
color: #333; |
|
font-size: 18px; |
|
line-height: 1.6; |
|
margin: 0 0 10px 0; |
|
font-weight: 500; |
|
">{sentence}</p> |
|
|
|
{f''' |
|
<div style=" |
|
color: #666; |
|
font-size: 14px; |
|
margin-top: 10px; |
|
padding: 10px; |
|
background-color: rgba(255,255,255,0.7); |
|
border-radius: 5px; |
|
"> |
|
<strong>Risk Level:</strong> <span style="color: {color}; font-weight: bold;">{risk_level}</span><br> |
|
{f"<strong>Issues:</strong> {', '.join(issues)}<br>" if issues else ""} |
|
<strong>Explanation:</strong> {explanation} |
|
</div> |
|
''' if explanation or issues else ''} |
|
</div> |
|
""" |
|
|
|
return f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 25px; |
|
"> |
|
<div style=" |
|
background-color: #F2EDE9; |
|
border-radius: 15px; |
|
padding: 25px; |
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
|
"> |
|
<h2 style=" |
|
color: #5B5B5B; |
|
margin-bottom: 20px; |
|
font-size: 28px; |
|
border-bottom: 3px solid #9A8F8F; |
|
padding-bottom: 10px; |
|
">🎨 Step 2: Annotated Version</h2> |
|
|
|
<!-- 颜色说明 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.7); |
|
padding: 15px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<h4 style="color: #5B5B5B; margin-bottom: 10px;">Color Legend:</h4> |
|
<div style="display: flex; flex-wrap: wrap; gap: 15px;"> |
|
<span style="color: #4CAF50; font-weight: bold;">🟢 Green: No Issues</span> |
|
<span style="color: #FFC107; font-weight: bold;">🟡 Yellow: Minor Issues</span> |
|
<span style="color: #FF9800; font-weight: bold;">🟠 Orange: Moderate Issues</span> |
|
<span style="color: #F44336; font-weight: bold;">🔴 Red: Serious Issues</span> |
|
</div> |
|
</div> |
|
|
|
<!-- 标注内容 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
{annotated_html} |
|
</div> |
|
|
|
<div style=" |
|
text-align: center; |
|
color: #4A7C59; |
|
font-size: 16px; |
|
font-weight: bold; |
|
padding: 15px; |
|
background-color: rgba(74, 124, 89, 0.1); |
|
border-radius: 8px; |
|
">✅ Step 2 Complete, generating revised version...</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
def generate_revised_text_card(original_text: str, analysis_result: Dict) -> str: |
|
"""生成整改后的文本对比版""" |
|
revised_text = analysis_result.get("revised_text", original_text) |
|
|
|
|
|
original_sentences = [s.strip() + "。" for s in original_text.split("。") if s.strip()] |
|
revised_sentences = [s.strip() + "。" for s in revised_text.split("。") if s.strip()] |
|
|
|
|
|
comparison_html = "" |
|
|
|
max_len = max(len(original_sentences), len(revised_sentences)) |
|
|
|
for i in range(max_len): |
|
orig_sentence = original_sentences[i] if i < len(original_sentences) else "" |
|
rev_sentence = revised_sentences[i] if i < len(revised_sentences) else "" |
|
|
|
|
|
is_changed = orig_sentence != rev_sentence |
|
sentence_color = "#4CAF50" if not is_changed else "#333" |
|
|
|
if rev_sentence: |
|
comparison_html += f""" |
|
<div style=" |
|
margin: 15px 0; |
|
padding: 15px; |
|
border-left: 4px solid {sentence_color}; |
|
background-color: rgba(76, 175, 80, {'0.1' if not is_changed else '0.05'}); |
|
border-radius: 8px; |
|
"> |
|
<p style=" |
|
color: {sentence_color}; |
|
font-size: 18px; |
|
line-height: 1.6; |
|
margin: 0; |
|
font-weight: {'bold' if not is_changed else 'normal'}; |
|
">{rev_sentence}</p> |
|
|
|
{f''' |
|
<div style=" |
|
color: #666; |
|
font-size: 14px; |
|
margin-top: 10px; |
|
padding: 10px; |
|
background-color: rgba(255,255,255,0.7); |
|
border-radius: 5px; |
|
"> |
|
<strong>Original:</strong> {orig_sentence} |
|
</div> |
|
''' if is_changed else ''} |
|
</div> |
|
""" |
|
|
|
return f""" |
|
<div style=" |
|
max-width: 900px; |
|
margin: 0 auto; |
|
padding: 30px; |
|
background: linear-gradient(135deg, #E8E3DE 0%, #F2EDE9 100%); |
|
border-radius: 20px; |
|
font-family: 'Microsoft YaHei', 'Noto Sans SC', sans-serif; |
|
margin-bottom: 25px; |
|
"> |
|
<div style=" |
|
background-color: #F2EDE9; |
|
border-radius: 15px; |
|
padding: 25px; |
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1); |
|
"> |
|
<h2 style=" |
|
color: #5B5B5B; |
|
margin-bottom: 20px; |
|
font-size: 28px; |
|
border-bottom: 3px solid #9A8F8F; |
|
padding-bottom: 10px; |
|
">✨ Step 3: Revised Text</h2> |
|
|
|
<!-- 说明 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.7); |
|
padding: 15px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
<h4 style="color: #5B5B5B; margin-bottom: 10px;">Legend:</h4> |
|
<p style="color: #4CAF50; font-weight: bold; margin: 5px 0;">🟢 Green Bold: Unchanged sentences (compliant)</p> |
|
<p style="color: #333; margin: 5px 0;">⚫ Normal: Modified sentences (with original shown below)</p> |
|
</div> |
|
|
|
<!-- 修改后的文本 --> |
|
<div style=" |
|
background-color: rgba(255,255,255,0.5); |
|
padding: 20px; |
|
border-radius: 10px; |
|
margin-bottom: 20px; |
|
"> |
|
{comparison_html} |
|
</div> |
|
|
|
<div style=" |
|
text-align: center; |
|
color: #4A7C59; |
|
font-size: 18px; |
|
font-weight: bold; |
|
padding: 20px; |
|
background-color: rgba(74, 124, 89, 0.1); |
|
border-radius: 8px; |
|
">🎉 All Steps Complete! Content audit finished.</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
|
|
|
|
with gr.Blocks( |
|
title="🛡️ AdGuardian - AI Advertising Compliance Agent", |
|
theme=gr.themes.Soft() |
|
) as demo: |
|
gr.Markdown(""" |
|
# 🛡️ AdGuardian - AI Advertising Compliance Agent |
|
**Multi-Country · Multi-Language · Three-Stage Analysis · AI-Driven Version** |
|
|
|
This system uses advanced AI analysis to ensure advertising compliance across different countries and languages. |
|
Powered by multiple language models for intelligent advertising content understanding and regulatory compliance. |
|
""") |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
|
|
text_input = gr.Textbox( |
|
label="Text to Audit", |
|
placeholder="Please enter the text content to be audited...", |
|
lines=8 |
|
) |
|
|
|
keywords_input = gr.Textbox( |
|
label="Keywords (Optional)", |
|
placeholder="Please enter keywords to mark, separated by commas", |
|
lines=2 |
|
) |
|
|
|
|
|
with gr.Row(): |
|
country_select = gr.Dropdown( |
|
label="Regulatory Standards", |
|
choices=["China", "USA", "UK", "Japan", "Korea", "Singapore", "Malaysia", "Other"], |
|
value="USA" |
|
) |
|
|
|
language_select = gr.Dropdown( |
|
label="Response Language", |
|
choices=["简体中文", "繁体中文", "English", "日本語", "한국어"], |
|
value="English" |
|
) |
|
|
|
with gr.Row(): |
|
model_select = gr.Dropdown( |
|
label="AI Model", |
|
choices=[ |
|
"Qwen/Qwen3-8B (Free - SiliconFlow)", |
|
"deepseek-ai/DeepSeek-V3-0324 (Paid - Nebius)", |
|
"deepseek-ai/DeepSeek-R1-0528 (Paid - Nebius)", |
|
"Qwen/Qwen3-235B-A22B (Paid - Nebius)", |
|
"google/gemma-3-27b-it (Paid - Nebius, Vision)" |
|
], |
|
value="Qwen/Qwen3-8B (Free - SiliconFlow)" |
|
) |
|
|
|
|
|
with gr.Row(): |
|
submit_btn = gr.Button("🚀 Start AI Analysis", variant="primary", scale=2) |
|
clear_btn = gr.Button("🗑️ Clear", variant="secondary", scale=1) |
|
|
|
with gr.Column(scale=2): |
|
|
|
with gr.Tabs(): |
|
with gr.TabItem("🔍 Stage 1: Basic Analysis"): |
|
stage1_output = gr.HTML( |
|
value="<div style='text-align: center; padding: 50px; color: #888;'>Stage 1 results will appear here...</div>", |
|
label="Stage 1 Results" |
|
) |
|
|
|
with gr.TabItem("🎨 Stage 2: Detailed Annotation"): |
|
stage2_output = gr.HTML( |
|
value="<div style='text-align: center; padding: 50px; color: #888;'>Stage 2 results will appear here...</div>", |
|
label="Stage 2 Results" |
|
) |
|
|
|
with gr.TabItem("✨ Stage 3: Revision Suggestions"): |
|
stage3_output = gr.HTML( |
|
value="<div style='text-align: center; padding: 50px; color: #888;'>Stage 3 results will appear here...</div>", |
|
label="Stage 3 Results" |
|
) |
|
|
|
with gr.TabItem("📊 Summary"): |
|
summary_output = gr.HTML( |
|
value="<div style='text-align: center; padding: 50px; color: #888;'>Analysis summary will appear here...</div>", |
|
label="Summary" |
|
) |
|
|
|
|
|
gr.Markdown("### 📋 Examples") |
|
with gr.Row(): |
|
example1_btn = gr.Button("📝 Example 1: Beauty Product (EN)", size="sm") |
|
example2_btn = gr.Button("⚡ Example 2: Energy Drink (EN)", size="sm") |
|
example3_btn = gr.Button("🏠 Example 3: Real Estate (中文)", size="sm") |
|
|
|
|
|
submit_btn.click( |
|
fn=comprehensive_text_audit_streaming, |
|
inputs=[text_input, keywords_input, country_select, language_select, model_select], |
|
outputs=[stage1_output, stage2_output, stage3_output, summary_output] |
|
) |
|
|
|
clear_btn.click( |
|
fn=lambda: ("", "", "USA", "English", "Qwen/Qwen3-8B (Free - SiliconFlow)", |
|
"<div style='text-align: center; padding: 50px; color: #888;'>Stage 1 results will appear here...</div>", |
|
"<div style='text-align: center; padding: 50px; color: #888;'>Stage 2 results will appear here...</div>", |
|
"<div style='text-align: center; padding: 50px; color: #888;'>Stage 3 results will appear here...</div>", |
|
"<div style='text-align: center; padding: 50px; color: #888;'>Analysis summary will appear here...</div>"), |
|
outputs=[text_input, keywords_input, country_select, language_select, model_select, stage1_output, stage2_output, stage3_output, summary_output] |
|
) |
|
|
|
|
|
example1_btn.click( |
|
fn=lambda: ("Revolutionary Anti-Aging Serum - Guaranteed to eliminate all wrinkles within 24 hours! Our scientifically proven formula contains 100% natural ingredients that will make you look 20 years younger instantly. Thousands of customers have experienced miraculous results overnight. This breakthrough serum is the ultimate solution for aging skin and will completely transform your appearance forever. No other product can match our incredible effectiveness.", |
|
"anti-aging, wrinkles, natural, guaranteed", "USA", "English"), |
|
outputs=[text_input, keywords_input, country_select, language_select] |
|
) |
|
|
|
example2_btn.click( |
|
fn=lambda: ("SuperBoost Energy Drink - The most powerful energy formula ever created! Increases your energy levels by 800% and keeps you alert for 48 hours straight without any crash. Our secret blend of natural caffeine and exotic herbs will revolutionize your productivity forever. Professional athletes choose our drink because it's the only one that delivers real results. Completely safe and approved by leading sports scientists worldwide. One sip will change your life permanently.", |
|
"energy drink, caffeine, performance, athletes", "USA", "English"), |
|
outputs=[text_input, keywords_input, country_select, language_select] |
|
) |
|
|
|
example3_btn.click( |
|
fn=lambda: ("豪华市中心公寓 - 史上最佳投资机会!该地区房产价值在过去一年中增长了1000%,绝对会继续上涨。这是千载难逢的致富机会,我们的独家开发项目保证年回报率50%。不要错过这个令人难以置信的交易,它将让您超越梦想变得富有!限时优惠,仅剩50个名额。立即加入,马上开始每周赚取10万元。成功有保障,我们的系统绝对可靠。", |
|
"房地产, 投资, 保证回报, 豪华", "China", "简体中文"), |
|
outputs=[text_input, keywords_input, country_select, language_select] |
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
demo.launch( |
|
max_threads=5, |
|
show_error=True, |
|
share=False |
|
) |
|
|