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( """ """,