diff --git a/geo_tool.py b/geo_tool.py
index e51db3b..429cce0 100644
--- a/geo_tool.py
+++ b/geo_tool.py
@@ -168,9 +168,12 @@ def load_default_cfg():
for key in ["brand", "advantages", "competitors", "temperature"]:
if key in app_config and app_config[key]:
base_cfg[key] = app_config[key]
- except Exception:
+ except FileNotFoundError:
# secrets.toml 不存在时静默忽略,用户可通过侧边栏配置
pass
+ except Exception as e:
+ import logging
+ logging.warning(f"读取 secrets.toml 失败: {e}")
return base_cfg
@@ -230,28 +233,11 @@ ss_init("image_descriptions", []) # 图片描述列表
ss_init("detail_tab_active", "🎨 增强工具") # 保存当前激活的详情Tab
# ------------------- 工具函数 -------------------
-from modules.ui.components import INVALID_FS_CHARS
-
-
-def sanitize_filename(name: str, max_len: int = 80) -> str:
- from modules.ui.components import sanitize_filename as _sanitize_filename
- return _sanitize_filename(name, max_len)
-
-
-def safe_decode_uploaded(uploaded) -> str:
- from modules.ui.components import safe_decode_uploaded as _safe_decode_uploaded
- return _safe_decode_uploaded(uploaded)
-
-
-def extract_json_array(text: str):
- """从模型输出中抽取 JSON 数组(JsonOutputParser 失败时兜底)。"""
- from modules.ui.components import extract_json_array as _extract_json_array
- return _extract_json_array(text)
-
-
def validate_cfg(cfg: dict):
- """保留你原本的"必须填写所有 API Key"约束,但不 st.stop:改为禁用按钮 + 提示。"""
+ """验证配置完整性,返回 (是否有效, 错误列表)。"""
errors = []
+ warnings = []
+
if not cfg.get("gen_api_key", "").strip():
errors.append("生成&优化 LLM 的 API Key 未填写")
@@ -263,8 +249,13 @@ def validate_cfg(cfg: dict):
for vp in verify_providers:
if not verify_keys.get(vp, "").strip():
errors.append(f"验证模型 {vp} 的 API Key 未填写")
+
+ if not cfg.get("brand", "").strip():
+ warnings.append("品牌名称未填写(部分功能需要)")
+ if not cfg.get("advantages", "").strip():
+ warnings.append("核心优势未填写(部分功能需要)")
- return (len(errors) == 0), errors
+ return (len(errors) == 0), errors + warnings
def model_defaults(provider: str) -> str:
@@ -408,9 +399,14 @@ with st.sidebar:
st.session_state.cfg_applied = False
if not st.session_state.cfg_valid:
- st.warning("配置未满足运行条件:\n- " + "\n- ".join(st.session_state.cfg_errors))
+ with st.container(border=True):
+ st.markdown("**⚠️ 完成配置后即可使用全部功能**")
+ for err in st.session_state.cfg_errors:
+ st.markdown(f"• {err}")
else:
- st.success("配置已就绪,可运行全部模块。")
+ with st.container(border=True):
+ st.markdown("**✅ 配置已就绪**")
+ st.caption("所有功能已解锁,可以开始使用")
st.markdown("---")
if st.button("重置全部结果(不删除配置)", use_container_width=True, key="sb_reset_all"):
diff --git a/modules/resource_recommender.py b/modules/resource_recommender.py
index 882d4a1..506e693 100644
--- a/modules/resource_recommender.py
+++ b/modules/resource_recommender.py
@@ -10,31 +10,47 @@ class ResourceRecommender:
"""GEO 资源推荐器"""
def __init__(self):
- # GEO 代理列表
+ # GEO 代理/服务列表
self.agents = [
{
- "name": "KrillinAI",
- "description": "专业的 GEO 代理服务,提供高质量的内容生成和优化",
- "url": "https://krillin.ai",
- "category": "代理服务",
+ "name": "Perplexity AI",
+ "description": "AI 搜索引擎,可用于验证 GEO 效果",
+ "url": "https://www.perplexity.ai",
+ "category": "AI 搜索",
"rating": "⭐⭐⭐⭐⭐",
- "features": ["内容生成", "SEO 优化", "多平台支持"]
+ "features": ["实时搜索", "引用来源", "多模型支持"]
},
{
- "name": "AutoGPT",
- "description": "自动化 AI 代理,支持 GEO 内容创作",
- "url": "https://autogpt.net",
- "category": "代理服务",
- "rating": "⭐⭐⭐⭐",
- "features": ["自动化", "多任务", "API 集成"]
+ "name": "ChatGPT Search",
+ "description": "OpenAI 的搜索功能,验证品牌在 AI 搜索中的表现",
+ "url": "https://chat.openai.com",
+ "category": "AI 搜索",
+ "rating": "⭐⭐⭐⭐⭐",
+ "features": ["GPT-4", "实时联网", "引用分析"]
},
{
- "name": "AgentGPT",
- "description": "基于 GPT 的智能代理,支持 GEO 策略执行",
- "url": "https://agentgpt.reworkd.ai",
- "category": "代理服务",
+ "name": "Google SGE",
+ "description": "Google 搜索生成体验,了解 AI 搜索趋势",
+ "url": "https://search.google",
+ "category": "AI 搜索",
"rating": "⭐⭐⭐⭐",
- "features": ["策略规划", "任务执行", "结果分析"]
+ "features": ["AI 摘要", "来源引用", "搜索结果"]
+ },
+ {
+ "name": "Jasper AI",
+ "description": "AI 内容创作平台,支持 SEO 优化内容生成",
+ "url": "https://www.jasper.ai",
+ "category": "内容生成",
+ "rating": "⭐⭐⭐⭐",
+ "features": ["模板丰富", "品牌声音", "SEO 优化"]
+ },
+ {
+ "name": "Surfer SEO",
+ "description": "SEO 内容优化工具,支持 SERP 分析",
+ "url": "https://surferseo.com",
+ "category": "SEO 工具",
+ "rating": "⭐⭐⭐⭐",
+ "features": ["内容评分", "关键词分析", "SERP 分析"]
}
]
@@ -42,9 +58,9 @@ class ResourceRecommender:
self.tools = [
{
"name": "Google Search Console",
- "description": "监控网站搜索表现,优化 GEO 效果",
+ "description": "监控网站在 Google 搜索中的表现",
"url": "https://search.google.com/search-console",
- "category": "SEO 工具",
+ "category": "搜索引擎工具",
"rating": "⭐⭐⭐⭐⭐",
"features": ["搜索分析", "索引监控", "性能报告"]
},
@@ -52,44 +68,84 @@ class ResourceRecommender:
"name": "Bing Webmaster Tools",
"description": "Bing 搜索引擎的网站管理工具",
"url": "https://www.bing.com/webmasters",
- "category": "SEO 工具",
+ "category": "搜索引擎工具",
"rating": "⭐⭐⭐⭐",
"features": ["索引提交", "搜索分析", "URL 检查"]
},
{
"name": "Schema.org Validator",
- "description": "验证 JSON-LD Schema 标记",
+ "description": "验证 JSON-LD Schema 标记是否正确",
"url": "https://validator.schema.org",
- "category": "技术工具",
+ "category": "结构化数据",
"rating": "⭐⭐⭐⭐⭐",
"features": ["Schema 验证", "结构化数据测试", "错误检测"]
},
{
- "name": "Rich Results Test",
- "description": "Google 富媒体结果测试工具",
+ "name": "Google Rich Results Test",
+ "description": "测试网页是否支持 Google 富媒体搜索结果",
"url": "https://search.google.com/test/rich-results",
- "category": "技术工具",
+ "category": "结构化数据",
"rating": "⭐⭐⭐⭐⭐",
"features": ["富媒体测试", "预览效果", "错误诊断"]
},
{
"name": "PageSpeed Insights",
- "description": "网站性能分析工具,影响 GEO 排名",
+ "description": "分析网页性能,Core Web Vitals 指标",
"url": "https://pagespeed.web.dev",
"category": "性能工具",
"rating": "⭐⭐⭐⭐⭐",
"features": ["性能分析", "优化建议", "移动端测试"]
+ },
+ {
+ "name": "Ahrefs",
+ "description": "SEO 工具套件,关键词研究和竞品分析",
+ "url": "https://ahrefs.com",
+ "category": "SEO 工具",
+ "rating": "⭐⭐⭐⭐⭐",
+ "features": ["关键词研究", "反向链接分析", "竞品分析"]
+ },
+ {
+ "name": "SEMrush",
+ "description": "数字营销工具,SEO 和内容营销分析",
+ "url": "https://www.semrush.com",
+ "category": "SEO 工具",
+ "rating": "⭐⭐⭐⭐⭐",
+ "features": ["关键词研究", "站点审计", "内容优化"]
+ },
+ {
+ "name": "Clearscope",
+ "description": "AI 内容优化工具,提升内容相关性",
+ "url": "https://www.clearscope.io",
+ "category": "内容优化",
+ "rating": "⭐⭐⭐⭐",
+ "features": ["内容评分", "关键词建议", "竞品分析"]
}
]
# 论文/指南链接
self.papers = [
+ {
+ "title": "GEO: Generative Engine Optimization (arXiv)",
+ "description": "GEO 原始研究论文,定义了生成式引擎优化的概念和方法",
+ "url": "https://arxiv.org/abs/2311.09735",
+ "category": "学术论文",
+ "date": "2023",
+ "importance": "高"
+ },
{
"title": "Google E-E-A-T Guidelines",
"description": "Google 官方 E-E-A-T 指南,GEO 核心原则",
"url": "https://developers.google.com/search/docs/fundamentals/creating-helpful-content",
"category": "官方指南",
- "date": "2023",
+ "date": "2024",
+ "importance": "高"
+ },
+ {
+ "title": "Google Search Quality Rater Guidelines",
+ "description": "Google 搜索质量评估指南,详细的 E-E-A-T 标准",
+ "url": "static.googleusercontent.com/media/guidelines.raterhub.com/en//searchqualityevaluatorguidelines.pdf",
+ "category": "官方指南",
+ "date": "2024",
"importance": "高"
},
{
@@ -101,88 +157,92 @@ class ResourceRecommender:
"importance": "高"
},
{
- "title": "GEO Strategy Guide",
- "description": "GEO(Generative Engine Optimization)策略指南",
- "url": "https://github.com/mprimi/portable-seed",
- "category": "策略指南",
+ "title": "Google Structured Data Guidelines",
+ "description": "Google 结构化数据指南和最佳实践",
+ "url": "https://developers.google.com/search/docs/appearance/structured-data",
+ "category": "技术文档",
"date": "2024",
"importance": "高"
},
{
- "title": "AI Search Optimization",
- "description": "AI 搜索引擎优化最佳实践",
+ "title": "AI Search Optimization Guide",
+ "description": "AI 搜索引擎优化最佳实践指南",
"url": "https://www.searchenginejournal.com/ai-search-optimization",
"category": "最佳实践",
"date": "2024",
"importance": "中"
},
{
- "title": "LLM Prompt Engineering",
- "description": "大语言模型提示工程指南",
+ "title": "LLM Prompt Engineering Guide",
+ "description": "大语言模型提示工程完整指南",
"url": "https://www.promptingguide.ai",
"category": "技术指南",
"date": "持续更新",
"importance": "中"
+ },
+ {
+ "title": "Content Quality Guidelines",
+ "description": "高质量内容创作指南",
+ "url": "https://developers.google.com/search/docs/fundamentals/creating-helpful-content",
+ "category": "内容指南",
+ "date": "2024",
+ "importance": "中"
}
]
# 社区资源
self.communities = [
{
- "name": "GEO Reddit Community",
- "description": "GEO 相关讨论和经验分享",
+ "name": "r/SEO (Reddit)",
+ "description": "Reddit SEO 社区,讨论 SEO 和 GEO 策略",
"url": "https://www.reddit.com/r/SEO",
- "category": "社区论坛",
+ "category": "论坛社区",
+ "rating": "⭐⭐⭐⭐⭐"
+ },
+ {
+ "name": "r/ChatGPT (Reddit)",
+ "description": "ChatGPT 社区,讨论 AI 搜索和 GEO 应用",
+ "url": "https://www.reddit.com/r/ChatGPT",
+ "category": "论坛社区",
"rating": "⭐⭐⭐⭐"
},
{
- "name": "AI SEO Discord",
- "description": "AI SEO 和 GEO 技术交流社区",
- "url": "https://discord.gg/ai-seo",
- "category": "社区论坛",
+ "name": "SEO Twitter/X Community",
+ "description": "SEO 和 GEO 从业者 Twitter 社区",
+ "url": "https://twitter.com/search?q=SEO%20GEO",
+ "category": "社交媒体",
+ "rating": "⭐⭐⭐⭐"
+ },
+ {
+ "name": "Google Search Central Community",
+ "description": "Google 官方搜索社区",
+ "url": "https://support.google.com/webmasters/community",
+ "category": "官方社区",
+ "rating": "⭐⭐⭐⭐⭐"
+ },
+ {
+ "name": "Moz Community",
+ "description": "Moz SEO 社区,丰富的 SEO 资源",
+ "url": "https://moz.com/community",
+ "category": "论坛社区",
"rating": "⭐⭐⭐⭐"
}
]
def get_agents(self, category: Optional[str] = None) -> List[Dict]:
- """
- 获取 GEO 代理列表
-
- Args:
- category: 分类筛选(可选)
-
- Returns:
- 代理列表
- """
+ """获取 GEO 代理列表"""
if category:
return [agent for agent in self.agents if agent.get("category") == category]
return self.agents
def get_tools(self, category: Optional[str] = None) -> List[Dict]:
- """
- 获取工具推荐列表
-
- Args:
- category: 分类筛选(可选)
-
- Returns:
- 工具列表
- """
+ """获取工具推荐列表"""
if category:
return [tool for tool in self.tools if tool.get("category") == category]
return self.tools
def get_papers(self, category: Optional[str] = None, importance: Optional[str] = None) -> List[Dict]:
- """
- 获取论文/指南列表
-
- Args:
- category: 分类筛选(可选)
- importance: 重要性筛选(可选:高、中、低)
-
- Returns:
- 论文/指南列表
- """
+ """获取论文/指南列表"""
result = self.papers
if category:
result = [p for p in result if p.get("category") == category]
@@ -191,81 +251,58 @@ class ResourceRecommender:
return result
def get_communities(self) -> List[Dict]:
- """
- 获取社区资源列表
-
- Returns:
- 社区列表
- """
+ """获取社区资源列表"""
return self.communities
- def search_resources(self, query: str, resource_type: Optional[str] = None) -> List[Dict]:
- """
- 搜索资源(简单文本匹配)
-
- Args:
- query: 搜索关键词
- resource_type: 资源类型(agents, tools, papers, communities)
-
- Returns:
- 匹配的资源列表
- """
- query_lower = query.lower()
- results = []
-
- if resource_type is None or resource_type == "agents":
- for agent in self.agents:
- if (query_lower in agent["name"].lower() or
- query_lower in agent["description"].lower() or
- any(query_lower in f.lower() for f in agent.get("features", []))):
- results.append({**agent, "type": "agent"})
-
- if resource_type is None or resource_type == "tools":
- for tool in self.tools:
- if (query_lower in tool["name"].lower() or
- query_lower in tool["description"].lower() or
- any(query_lower in f.lower() for f in tool.get("features", []))):
- results.append({**tool, "type": "tool"})
-
- if resource_type is None or resource_type == "papers":
- for paper in self.papers:
- if (query_lower in paper["title"].lower() or
- query_lower in paper["description"].lower()):
- results.append({**paper, "type": "paper"})
-
- if resource_type is None or resource_type == "communities":
- for community in self.communities:
- if (query_lower in community["name"].lower() or
- query_lower in community["description"].lower()):
- results.append({**community, "type": "community"})
-
- return results
-
- def get_categories(self) -> Dict[str, List[str]]:
- """
- 获取所有分类
-
- Returns:
- 分类字典
- """
- return {
- "agents": list(set(agent["category"] for agent in self.agents)),
- "tools": list(set(tool["category"] for tool in self.tools)),
- "papers": list(set(paper["category"] for paper in self.papers)),
- "communities": list(set(community["category"] for community in self.communities))
- }
-
- def get_resource_summary(self) -> Dict[str, int]:
- """
- 获取资源统计摘要
-
- Returns:
- 统计字典
- """
+ def get_resource_summary(self) -> Dict:
+ """获取资源统计摘要"""
return {
+ "total": len(self.agents) + len(self.tools) + len(self.papers) + len(self.communities),
"agents": len(self.agents),
"tools": len(self.tools),
"papers": len(self.papers),
- "communities": len(self.communities),
- "total": len(self.agents) + len(self.tools) + len(self.papers) + len(self.communities)
+ "communities": len(self.communities)
}
+
+ def search_resources(self, query: str, resource_type: Optional[str] = None) -> List[Dict]:
+ """搜索资源"""
+ query_lower = query.lower()
+ results = []
+
+ # 搜索所有资源
+ all_resources = []
+
+ if resource_type is None or resource_type == "agents":
+ for agent in self.agents:
+ agent["type"] = "agent"
+ all_resources.append(agent)
+
+ if resource_type is None or resource_type == "tools":
+ for tool in self.tools:
+ tool["type"] = "tool"
+ all_resources.append(tool)
+
+ if resource_type is None or resource_type == "papers":
+ for paper in self.papers:
+ paper["type"] = "paper"
+ all_resources.append(paper)
+
+ if resource_type is None or resource_type == "communities":
+ for community in self.communities:
+ community["type"] = "community"
+ all_resources.append(community)
+
+ # 搜索匹配
+ for resource in all_resources:
+ name = resource.get("name", resource.get("title", "")).lower()
+ description = resource.get("description", "").lower()
+ category = resource.get("category", "").lower()
+ features = " ".join(resource.get("features", [])).lower()
+
+ if (query_lower in name or
+ query_lower in description or
+ query_lower in category or
+ query_lower in features):
+ results.append(resource)
+
+ return results
diff --git a/modules/ui/state.py b/modules/ui/state.py
index f202440..362e234 100644
--- a/modules/ui/state.py
+++ b/modules/ui/state.py
@@ -28,7 +28,7 @@ def init_session_state():
# 关键词模块
ss_init("keywords", [])
- ss_init("kw_last_num", 40)
+ ss_init("kw_last_num", 20)
ss_init("kw_generation_mode", "AI生成")
ss_init("wordbanks", None)
diff --git a/modules/ui/tab_autowrite.py b/modules/ui/tab_autowrite.py
index 59b6bdb..62ee741 100644
--- a/modules/ui/tab_autowrite.py
+++ b/modules/ui/tab_autowrite.py
@@ -92,7 +92,7 @@ def render_tab_autowrite(
keywords_to_generate = []
if mode == "单篇生成":
- col1, col2 = st.columns([2, 1])
+ col1, col2 = st.columns(2)
with col1:
selected_keyword = st.selectbox(
"选择关键词",
@@ -110,7 +110,7 @@ def render_tab_autowrite(
if selected_keyword:
keywords_to_generate = [(selected_keyword, platform)]
else:
- col1, col2 = st.columns([3, 1])
+ col1, col2 = st.columns(2)
with col1:
selected_keywords = st.multiselect(
"选择关键词(可多选)",
diff --git a/modules/ui/tab_config_optimizer.py b/modules/ui/tab_config_optimizer.py
index 862258d..588040e 100644
--- a/modules/ui/tab_config_optimizer.py
+++ b/modules/ui/tab_config_optimizer.py
@@ -64,13 +64,13 @@ def render_tab_config_optimizer(
st.markdown(f"**竞品列表**:{', '.join(competitor_list[:5])}{'...' if len(competitor_list) > 5 else ''}")
# 分析按钮
- col1, col2 = st.columns([1, 3])
+ col1, col2 = st.columns([1, 1])
with col1:
analyze_btn = st.button("🔍 分析配置优化", type="primary", use_container_width=True, key="tab10_optimize_config")
with col2:
if st.session_state.config_optimization_result:
- st.success("✅ 已有优化结果,可直接查看下方建议")
+ st.success("✅ 已有优化结果")
# 执行分析
if analyze_btn:
diff --git a/modules/ui/tab_keywords.py b/modules/ui/tab_keywords.py
index cb2afbc..6ff01dc 100644
--- a/modules/ui/tab_keywords.py
+++ b/modules/ui/tab_keywords.py
@@ -140,7 +140,6 @@ def render_tab_keywords(storage, ss_init, gen_llm, brand: str, advantages: str)
st.rerun()
# 统一更新所有词库按钮
- st.markdown("---")
if st.button(
"💾 更新所有词库",
use_container_width=True,
@@ -179,8 +178,6 @@ def render_tab_keywords(storage, ss_init, gen_llm, brand: str, advantages: str)
key="kw_export_json",
)
- st.markdown("---")
-
# 导入
uploaded_wordbanks = st.file_uploader(
"导入词库(JSON)",
@@ -199,8 +196,6 @@ def render_tab_keywords(storage, ss_init, gen_llm, brand: str, advantages: str)
except Exception as e:
st.error(f"导入失败:{e}")
- st.markdown("---")
-
# 重置为默认词库
if st.button(
"重置为默认词库",
@@ -213,8 +208,6 @@ def render_tab_keywords(storage, ss_init, gen_llm, brand: str, advantages: str)
st.success("已重置为默认词库")
st.rerun()
- st.markdown("---")
-
# ========== 区域 3:生成控制 ==========
with st.container(border=True):
st.markdown("**⚙️ 生成控制**")
@@ -1251,7 +1244,7 @@ def render_tab_keywords(storage, ss_init, gen_llm, brand: str, advantages: str)
search_col, filter_col = st.columns([3, 1])
with search_col:
search_term = st.text_input(
- "🔍 搜索关键词", key="kw_search", placeholder="输入关键词搜索..."
+ "搜索关键词", key="kw_search", placeholder="🔍 输入关键词搜索...", label_visibility="collapsed"
)
with filter_col:
show_original = st.checkbox(
diff --git a/modules/ui/tab_knowledge.py b/modules/ui/tab_knowledge.py
index 55a0958..1ecc520 100644
--- a/modules/ui/tab_knowledge.py
+++ b/modules/ui/tab_knowledge.py
@@ -79,7 +79,18 @@ def _render_upload_section(kb: KnowledgeBase):
)
if uploaded_file:
- content = uploaded_file.read().decode("utf-8")
+ # 安全解码文件内容
+ b = uploaded_file.read()
+ content = None
+ for enc in ("utf-8-sig", "utf-8", "gb18030", "gbk"):
+ try:
+ content = b.decode(enc)
+ break
+ except Exception:
+ pass
+ if content is None:
+ content = b.decode("utf-8", errors="replace")
+
st.text_area("文件预览", content[:1000] + "..." if len(content) > 1000 else content,
height=150, disabled=True)
@@ -169,7 +180,7 @@ def _render_search_test(kb: KnowledgeBase):
st.markdown("#### 搜索测试")
st.caption("测试知识库检索效果,验证文档是否被正确索引")
- query = st.text_input("输入测试查询", placeholder="例如:产品有什么优势?")
+ query = st.text_input("输入测试查询", placeholder="🔍 例如:产品有什么优势?", label_visibility="collapsed")
col1, col2 = st.columns(2)
with col1:
diff --git a/modules/ui/tab_optimize.py b/modules/ui/tab_optimize.py
index 23ec4c5..5c25a55 100644
--- a/modules/ui/tab_optimize.py
+++ b/modules/ui/tab_optimize.py
@@ -38,7 +38,6 @@ def render_tab_optimize(
)
# === 文章优化功能(主流程) ===
- st.markdown("---")
st.markdown("**✏️ 文章内容优化**")
with st.container(border=True):
diff --git a/modules/ui/tab_platform_sync.py b/modules/ui/tab_platform_sync.py
index 998e6d9..421063f 100644
--- a/modules/ui/tab_platform_sync.py
+++ b/modules/ui/tab_platform_sync.py
@@ -49,7 +49,7 @@ def render_tab_platform_sync(storage, brand: str) -> None:
key="github_repo_name"
)
- col1, col2 = st.columns([1, 4])
+ col1, col2 = st.columns([1, 1])
with col1:
if st.button("💾 保存配置", type="primary", use_container_width=True):
if github_api_key and github_repo_owner and github_repo_name:
diff --git a/modules/ui/tab_reports.py b/modules/ui/tab_reports.py
index fe769ea..de8cfaa 100644
--- a/modules/ui/tab_reports.py
+++ b/modules/ui/tab_reports.py
@@ -148,7 +148,6 @@ def render_tab_reports(
st.info("📊 暂无验证数据。请先运行自动验证任务或手动验证。")
else:
# 数据概览
- st.markdown("---")
st.markdown("#### 📈 数据概览")
col1, col2, col3, col4 = st.columns(4)
@@ -173,7 +172,6 @@ def render_tab_reports(
# 1. 提及率趋势图
if "验证时间" in verify_df.columns and len(verify_df) > 0:
- st.markdown("---")
st.markdown("#### 📊 提及率趋势图")
# 按日期聚合数据
@@ -196,7 +194,6 @@ def render_tab_reports(
st.plotly_chart(fig_trend, use_container_width=True)
# 2. 平台贡献度分析(基于文章平台)
- st.markdown("---")
st.markdown("#### 🌐 平台贡献度分析")
articles = storage.get_articles(brand=brand)
@@ -223,7 +220,6 @@ def render_tab_reports(
st.info("暂无文章数据。")
# 话题集群分析模块
- st.markdown("---")
st.markdown("#### 🎯 话题集群分析")
st.caption("基于历史关键词生成话题集群,分析内容覆盖情况,发现内容盲区")
@@ -390,10 +386,8 @@ def render_tab_reports(
ideas = suggestion.get('content_ideas', [])
if ideas:
st.markdown(f"- **内容创意**:{', '.join(ideas[:3])}")
- st.markdown("---")
# ROI 分析与成本优化模块
- st.markdown("---")
st.markdown("#### 💰 ROI 分析与成本优化")
st.caption("量化 GEO 投入产出比,优化成本结构,数据驱动决策")
@@ -621,7 +615,6 @@ def render_tab_reports(
)
# 3. 内容质量指标分析
- st.markdown("---")
st.markdown("#### 📈 内容质量指标分析")
st.caption("分析内容的信任度、权威性、参与度等关键指标,量化内容质量")
@@ -797,7 +790,6 @@ def render_tab_reports(
st.error(f"获取内容质量指标失败:{e}")
# 4. 关键词效果排名
- st.markdown("---")
st.markdown("#### 🎯 关键词效果排名")
brand_verify = verify_df[verify_df["品牌"] == brand].copy()
@@ -828,7 +820,6 @@ def render_tab_reports(
st.info("暂无品牌验证数据。")
# 4. 竞品对比分析
- st.markdown("---")
st.markdown("#### ⚔️ 竞品对比分析")
if len(competitor_list) > 0:
@@ -870,7 +861,6 @@ def render_tab_reports(
st.info("💡 提示:在侧边栏配置竞品品牌后,可查看竞品对比分析。")
# 5. 负面防护监控报告
- st.markdown("---")
st.markdown("#### 🛡️ 负面防护监控报告")
st.caption("分析负面查询中的品牌提及情况,提供风险预警和优化建议")
@@ -981,7 +971,6 @@ def render_tab_reports(
st.error(f"生成负面监控报告失败:{e}")
# 6. 数据导出
- st.markdown("---")
st.markdown("#### 💾 数据导出")
col1, col2 = st.columns(2)
diff --git a/modules/ui/tab_resources.py b/modules/ui/tab_resources.py
index 7ef8e31..e9b4c8e 100644
--- a/modules/ui/tab_resources.py
+++ b/modules/ui/tab_resources.py
@@ -3,93 +3,86 @@
import streamlit as st
from modules.resource_recommender import ResourceRecommender
+from modules.ui.components import render_tab_top_with_clear
def render_tab_resources(storage, brand: str) -> None:
"""渲染 Tab8:GEO 资源库。由主入口在 with tab8 内调用。"""
- st.markdown("### 📚 GEO 资源库")
- st.caption("发现 GEO 相关工具、代理、论文和社区资源,增强工具生态")
+ render_tab_top_with_clear(
+ title="📚 GEO 资源库",
+ caption="发现 GEO 相关工具、代理、论文和社区资源,增强工具生态",
+ clear_key="resources_clear",
+ on_clear=lambda: None, # 资源库无需清空
+ )
resource_recommender = ResourceRecommender()
# 资源统计概览
summary = resource_recommender.get_resource_summary()
- stat_col1, stat_col2, stat_col3, stat_col4, stat_col5 = st.columns(5)
- with stat_col1:
- st.metric("总资源数", summary['total'])
- with stat_col2:
- st.metric("代理服务", summary['agents'])
- with stat_col3:
- st.metric("工具推荐", summary['tools'])
- with stat_col4:
- st.metric("论文/指南", summary['papers'])
- with stat_col5:
- st.metric("社区资源", summary['communities'])
-
- st.markdown("---")
+ stat_cols = st.columns(5)
+ stats = [
+ ("总资源数", summary['total']),
+ ("AI 搜索", summary['agents']),
+ ("工具推荐", summary['tools']),
+ ("论文指南", summary['papers']),
+ ("社区资源", summary['communities']),
+ ]
+ for col, (label, value) in zip(stat_cols, stats):
+ with col:
+ st.metric(label, value)
# 搜索功能
- search_col1, search_col2 = st.columns([3, 1])
+ search_col1, search_col2 = st.columns([4, 1])
with search_col1:
search_query = st.text_input(
- "🔍 搜索资源",
+ "搜索资源",
key="resource_search",
- placeholder="输入关键词搜索代理、工具、论文、社区...",
- help="支持搜索资源名称、描述、功能特性等"
+ placeholder="🔍 输入关键词搜索资源名称、描述、功能特性...",
+ label_visibility="collapsed"
)
with search_col2:
- clear_search = st.button("清除搜索", use_container_width=True, key="clear_resource_search")
- if clear_search:
+ if st.button("清除", use_container_width=True, key="clear_resource_search"):
st.session_state.resource_search = ""
st.rerun()
# 资源分类标签
- resource_tab1, resource_tab2, resource_tab3, resource_tab4 = st.tabs(["🤖 GEO 代理", "🛠️ 工具推荐", "📄 论文/指南", "👥 社区资源"])
+ resource_tab1, resource_tab2, resource_tab3, resource_tab4 = st.tabs([
+ "🤖 AI 搜索", "🛠️ 工具推荐", "📄 论文指南", "👥 社区资源"
+ ])
- # GEO 代理
+ # AI 搜索/代理
with resource_tab1:
- st.markdown("#### 🤖 GEO 代理服务")
- st.caption("专业的 GEO 代理服务,提供高质量的内容生成和优化")
+ st.caption("AI 搜索引擎和内容生成服务,用于验证和优化 GEO 效果")
if search_query:
agents = resource_recommender.search_resources(search_query, "agents")
- if agents:
- st.info(f"🔍 找到 {len(agents)} 个匹配的代理服务")
else:
agents = resource_recommender.get_agents()
if agents:
- for i, agent in enumerate(agents, 1):
+ for agent in agents:
with st.container(border=True):
col1, col2 = st.columns([3, 1])
with col1:
- st.markdown(f"##### {i}. {agent['name']} {agent.get('rating', '')}")
+ st.markdown(f"**{agent['name']}** {agent.get('rating', '')}")
with col2:
if agent.get('url'):
- st.markdown(f"[🔗 访问]({agent['url']})")
+ st.link_button("访问", agent['url'], use_container_width=True)
- st.markdown(f"**{agent['description']}**")
- st.markdown(f"**分类**:{agent.get('category', 'N/A')}")
+ st.caption(agent['description'])
if agent.get('features'):
- st.markdown("**功能特性**:")
- features_text = " | ".join([f"✓ {f}" for f in agent['features']])
- st.markdown(features_text)
-
- if agent.get('url'):
- st.markdown(f"**链接**:{agent['url']}")
+ features_text = " · ".join([f"✓ {f}" for f in agent['features']])
+ st.markdown(f"{features_text}", unsafe_allow_html=True)
else:
- st.info("💡 暂无匹配的代理资源。尝试使用其他关键词搜索。")
+ st.info("💡 暂无匹配的资源,尝试其他关键词搜索。")
# 工具推荐
with resource_tab2:
- st.markdown("#### 🛠️ 工具推荐")
st.caption("GEO 相关的工具和服务,帮助优化内容效果")
if search_query:
tools = resource_recommender.search_resources(search_query, "tools")
- if tools:
- st.info(f"🔍 找到 {len(tools)} 个匹配的工具")
else:
tools = resource_recommender.get_tools()
@@ -103,144 +96,77 @@ def render_tab_resources(storage, brand: str) -> None:
categories[cat].append(tool)
for category, category_tools in categories.items():
- st.markdown(f"##### 📁 {category}")
- for i, tool in enumerate(category_tools, 1):
+ st.markdown(f"**{category}**")
+ for tool in category_tools:
with st.container(border=True):
col1, col2 = st.columns([3, 1])
with col1:
st.markdown(f"**{tool['name']}** {tool.get('rating', '')}")
with col2:
if tool.get('url'):
- st.markdown(f"[🔗 访问]({tool['url']})")
+ st.link_button("访问", tool['url'], use_container_width=True)
- st.markdown(f"*{tool['description']}*")
+ st.caption(tool['description'])
if tool.get('features'):
- st.markdown("**功能**:")
- features_text = " | ".join([f"✓ {f}" for f in tool['features']])
- st.markdown(features_text)
-
- if tool.get('url'):
- st.markdown(f"**链接**:{tool['url']}")
+ features_text = " · ".join([f"✓ {f}" for f in tool['features']])
+ st.markdown(f"{features_text}", unsafe_allow_html=True)
else:
- st.info("💡 暂无匹配的工具资源。尝试使用其他关键词搜索。")
+ st.info("💡 暂无匹配的工具,尝试其他关键词搜索。")
# 论文/指南
with resource_tab3:
- st.markdown("#### 📄 论文/指南")
st.caption("GEO 相关的论文、指南、文档,深入学习 GEO 策略")
if search_query:
papers = resource_recommender.search_resources(search_query, "papers")
- if papers:
- st.info(f"🔍 找到 {len(papers)} 个匹配的论文/指南")
else:
papers = resource_recommender.get_papers()
if papers:
- # 按重要性排序
+ # 按重要性分组显示
importance_order = {"高": 3, "中": 2, "低": 1}
papers_sorted = sorted(papers, key=lambda x: importance_order.get(x.get('importance', '低'), 1), reverse=True)
- # 按重要性分组显示
- high_importance = [p for p in papers_sorted if p.get('importance') == '高']
- medium_importance = [p for p in papers_sorted if p.get('importance') == '中']
- low_importance = [p for p in papers_sorted if p.get('importance') == '低']
+ importance_groups = {
+ "🔥 必读": [p for p in papers_sorted if p.get('importance') == '高'],
+ "⭐ 推荐": [p for p in papers_sorted if p.get('importance') == '中'],
+ }
- if high_importance:
- st.markdown("##### 🔥 高重要性(必读)")
- for paper in high_importance:
- with st.container(border=True):
- st.markdown(f"**🔥 {paper['title']}**")
- st.markdown(f"*{paper['description']}*")
- st.markdown(f"**分类**:{paper.get('category', 'N/A')} | **日期**:{paper.get('date', 'N/A')}")
- if paper.get('url'):
- st.markdown(f"🔗 [{paper['url']}]({paper['url']})")
-
- if medium_importance:
- st.markdown("##### ⭐ 中重要性(推荐阅读)")
- for paper in medium_importance:
- with st.container(border=True):
- st.markdown(f"**⭐ {paper['title']}**")
- st.markdown(f"*{paper['description']}*")
- st.markdown(f"**分类**:{paper.get('category', 'N/A')} | **日期**:{paper.get('date', 'N/A')}")
- if paper.get('url'):
- st.markdown(f"🔗 [{paper['url']}]({paper['url']})")
-
- if low_importance:
- st.markdown("##### 📌 低重要性(参考阅读)")
- for paper in low_importance:
- with st.container(border=True):
- st.markdown(f"**📌 {paper['title']}**")
- st.markdown(f"*{paper['description']}*")
- st.markdown(f"**分类**:{paper.get('category', 'N/A')} | **日期**:{paper.get('date', 'N/A')}")
- if paper.get('url'):
- st.markdown(f"🔗 [{paper['url']}]({paper['url']})")
+ for group_name, group_papers in importance_groups.items():
+ if group_papers:
+ st.markdown(f"**{group_name}**")
+ for paper in group_papers:
+ with st.container(border=True):
+ st.markdown(f"**{paper['title']}**")
+ st.caption(paper['description'])
+ meta = f"分类:{paper.get('category', 'N/A')} · 日期:{paper.get('date', 'N/A')}"
+ st.markdown(f"{meta}", unsafe_allow_html=True)
+ if paper.get('url'):
+ st.link_button("查看", paper['url'], use_container_width=True)
else:
- st.info("💡 暂无匹配的论文/指南资源。尝试使用其他关键词搜索。")
+ st.info("💡 暂无匹配的论文/指南,尝试其他关键词搜索。")
# 社区资源
with resource_tab4:
- st.markdown("#### 👥 社区资源")
st.caption("GEO 相关的社区和论坛,与其他用户交流经验")
if search_query:
communities = resource_recommender.search_resources(search_query, "communities")
- if communities:
- st.info(f"🔍 找到 {len(communities)} 个匹配的社区")
else:
communities = resource_recommender.get_communities()
if communities:
- for i, community in enumerate(communities, 1):
+ for community in communities:
with st.container(border=True):
col1, col2 = st.columns([3, 1])
with col1:
- st.markdown(f"##### {i}. {community['name']} {community.get('rating', '')}")
+ st.markdown(f"**{community['name']}** {community.get('rating', '')}")
with col2:
if community.get('url'):
- st.markdown(f"[🔗 访问]({community['url']})")
+ st.link_button("访问", community['url'], use_container_width=True)
- st.markdown(f"*{community['description']}*")
- st.markdown(f"**分类**:{community.get('category', 'N/A')}")
-
- if community.get('url'):
- st.markdown(f"**链接**:{community['url']}")
+ st.caption(community['description'])
+ st.markdown(f"分类:{community.get('category', 'N/A')}", unsafe_allow_html=True)
else:
- st.info("💡 暂无匹配的社区资源。尝试使用其他关键词搜索。")
-
- # 搜索结果显示(跨分类)
- if search_query:
- all_results = resource_recommender.search_resources(search_query)
- if all_results:
- st.markdown("---")
- st.markdown("#### 🔍 搜索结果汇总")
- st.info(f"共找到 {len(all_results)} 个匹配资源(跨所有分类)")
-
- # 按类型分组显示
- results_by_type = {}
- for result in all_results:
- res_type = result.get('type', 'unknown')
- if res_type not in results_by_type:
- results_by_type[res_type] = []
- results_by_type[res_type].append(result)
-
- type_names = {
- 'agent': '🤖 代理服务',
- 'tool': '🛠️ 工具',
- 'paper': '📄 论文/指南',
- 'community': '👥 社区'
- }
-
- for res_type, results in results_by_type.items():
- if results:
- st.markdown(f"##### {type_names.get(res_type, res_type)} ({len(results)} 个)")
- for result in results:
- with st.container(border=True):
- name_key = 'name' if 'name' in result else 'title'
- st.markdown(f"**{result.get(name_key, 'N/A')}**")
- st.caption(result.get('description', ''))
- if result.get('url'):
- st.markdown(f"🔗 [{result['url']}]({result['url']})")
-
- # =======================
+ st.info("💡 暂无匹配的社区资源,尝试其他关键词搜索。")
diff --git a/modules/ui/tab_validation.py b/modules/ui/tab_validation.py
index 6355966..ed00606 100644
--- a/modules/ui/tab_validation.py
+++ b/modules/ui/tab_validation.py
@@ -91,8 +91,6 @@ def render_tab_validation(
st.success("✅ 负面查询已添加到验证查询中")
st.rerun()
- st.markdown("---")
-
with st.container(border=True):
with st.form("verify_form", clear_on_submit=False):
test_queries = st.text_area(
@@ -104,7 +102,7 @@ def render_tab_validation(
st.session_state.verify_last_queries = test_queries
run_verify_disabled = (not st.session_state.cfg_valid) or (not verify_llms) or (not test_queries.strip())
- run_verify = st.form_submit_button("开始验证", use_container_width=True, disabled=run_verify_disabled)
+ run_verify = st.form_submit_button("🔍 开始验证", use_container_width=True, disabled=run_verify_disabled)
# 获取负面监控开关状态
negative_monitor_enabled = st.session_state.get("negative_monitor_enabled", False)
diff --git a/modules/ui/tab_workflow.py b/modules/ui/tab_workflow.py
index d38b4b3..16d1873 100644
--- a/modules/ui/tab_workflow.py
+++ b/modules/ui/tab_workflow.py
@@ -58,7 +58,8 @@ def render_tab_workflow(
st.rerun()
with col3:
- if st.button("▶️ 执行", key=f"run_{workflow['id']}", use_container_width=True):
+ if st.button("▶️ 执行", key=f"run_{workflow['id']}", use_container_width=True,
+ disabled=gen_llm is None):
# 创建回调函数
def generate_keywords_callback(num_keywords, generation_mode, brand, advantages):
"""关键词生成回调函数"""
diff --git a/modules/ui/theme.py b/modules/ui/theme.py
index d2def08..7e673e7 100644
--- a/modules/ui/theme.py
+++ b/modules/ui/theme.py
@@ -2,250 +2,524 @@ import streamlit as st
def inject_global_theme():
- """注入全局 CSS 主题,极简克制的样式优化"""
+ """注入全局 CSS 主题 - 紧凑、高信息密度的 C 端产品 UI"""
st.markdown(
"""
""",