(all):增加桌面程序
This commit is contained in:
+145
-109
@@ -22,10 +22,10 @@ from modules.ui.tab_knowledge import render_tab_knowledge
|
||||
from modules.ui.state import ss_init, init_session_state
|
||||
from modules.ui.theme import inject_global_theme
|
||||
|
||||
APP_TITLE = "GEO 智能内容优化平台"
|
||||
APP_TITLE = "丑橘GEO内容优化平台"
|
||||
|
||||
# ------------------- 页面配置 & 极简美学 CSS(产品级精修,仍然克制) -------------------
|
||||
st.set_page_config(page_title="GEO 智能内容优化平台", layout="wide", initial_sidebar_state="expanded")
|
||||
st.set_page_config(page_title="丑橘GEO内容优化平台", layout="wide", initial_sidebar_state="collapsed")
|
||||
|
||||
inject_global_theme()
|
||||
init_session_state()
|
||||
@@ -62,62 +62,8 @@ def record_api_cost(operation_type: str, provider: str, model: str, input_text:
|
||||
import logging
|
||||
logging.warning(f"记录 API 成本失败: {e}")
|
||||
|
||||
with st.expander("📖 关于 GEO(Generative Engine Optimization)", expanded=False):
|
||||
st.markdown("""
|
||||
### 🎯 什么是 GEO?
|
||||
|
||||
**GEO(Generative Engine Optimization,生成式引擎优化)** 是针对 AI 搜索引擎的内容优化策略。
|
||||
|
||||
传统 SEO 优化的是 Google、百度等传统搜索引擎的排名;而 GEO 优化的是 ChatGPT、Perplexity、Google SGE 等 AI 搜索引擎在回答用户问题时,**是否会引用您的品牌和内容**。
|
||||
|
||||
### 💡 为什么需要 GEO?
|
||||
|
||||
当用户向 AI 提问时(例如"最好的 XX 软件是什么?"),AI 会从互联网内容中检索信息并生成回答。如果您的品牌没有出现在 AI 可检索的高质量内容中,就会在 AI 时代失去曝光机会。
|
||||
|
||||
**GEO 的目标**:让您的品牌在 AI 回答中被优先、准确、可信地提及。
|
||||
|
||||
---
|
||||
|
||||
### 🔄 GEO 优化工作流
|
||||
|
||||
本工具提供完整的 GEO 优化闭环:
|
||||
|
||||
| 阶段 | 功能 | 说明 |
|
||||
|------|------|------|
|
||||
| 1️⃣ 关键词策略 | 关键词蒸馏 | 生成针对 AI 搜索的口语化、长尾关键词 |
|
||||
| 2️⃣ 内容创作 | 自动创作 | 基于知识库生成结构化、专业的内容 |
|
||||
| 3️⃣ 内容优化 | 文章优化 | E-E-A-T 强化、事实密度增强、Schema 生成 |
|
||||
| 4️⃣ 效果验证 | 多模型验证 | 用多个 AI 模型验证品牌是否被提及 |
|
||||
| 5️⃣ 数据分析 | AI 数据报表 | 提及率趋势、ROI 分析、竞品对比 |
|
||||
| 6️⃣ 内容分发 | 平台同步 | 多平台发布,扩大 AI 可检索内容 |
|
||||
|
||||
---
|
||||
|
||||
### 📊 GEO 核心指标
|
||||
|
||||
| 指标 | 说明 |
|
||||
|------|------|
|
||||
| **品牌提及率** | AI 回答中提及品牌的频率 |
|
||||
| **E-E-A-T 评分** | 专业性、经验性、权威性、可信度 |
|
||||
| **事实密度** | 内容中可验证信息的密度 |
|
||||
| **引用位置** | 品牌在 AI 回答中的位置(前 1/3 优先) |
|
||||
|
||||
---
|
||||
|
||||
### 🌐 支持平台
|
||||
|
||||
**内容发布平台(20+)**:知乎、小红书、CSDN、B站、GitHub、微信公众号等
|
||||
|
||||
**AI 验证平台(7)**:DeepSeek、OpenAI、通义千问、Groq、Moonshot、豆包、文心一言
|
||||
|
||||
---
|
||||
|
||||
### 📚 更多资源
|
||||
|
||||
- [GEO 学术论文](https://arxiv.org/abs/2311.09735) - GEO 原始研究
|
||||
- [项目文档](DOCS.md) - 完整功能文档
|
||||
- [快速开始](docs/guides/QUICK_START_GUIDE.md) - 新手入门指南
|
||||
""")
|
||||
# =================== 函数定义:配置管理(在 expander 之前定义,因为 expander 内使用了它们) ===================
|
||||
|
||||
def load_default_cfg():
|
||||
"""
|
||||
@@ -153,7 +99,6 @@ def load_default_cfg():
|
||||
# 从 st.secrets 读取敏感信息(优先级更高)
|
||||
try:
|
||||
if hasattr(st, 'secrets') and st.secrets:
|
||||
# 读取 API Keys
|
||||
if "api_keys" in st.secrets:
|
||||
api_keys = st.secrets["api_keys"]
|
||||
if "deepseek" in api_keys and api_keys["deepseek"]:
|
||||
@@ -161,15 +106,13 @@ def load_default_cfg():
|
||||
base_cfg["verify_keys"]["DeepSeek"] = api_keys["deepseek"]
|
||||
if "tongyi_wanxiang" in api_keys and api_keys["tongyi_wanxiang"]:
|
||||
base_cfg["tongyi_wanxiang_api_key"] = api_keys["tongyi_wanxiang"]
|
||||
|
||||
# 读取应用配置(如果存在)
|
||||
|
||||
if "app_config" in st.secrets:
|
||||
app_config = st.secrets["app_config"]
|
||||
for key in ["brand", "advantages", "competitors", "temperature"]:
|
||||
if key in app_config and app_config[key]:
|
||||
base_cfg[key] = app_config[key]
|
||||
except FileNotFoundError:
|
||||
# secrets.toml 不存在时静默忽略,用户可通过侧边栏配置
|
||||
pass
|
||||
except Exception as e:
|
||||
import logging
|
||||
@@ -180,9 +123,14 @@ def load_default_cfg():
|
||||
|
||||
def save_cfg_to_file(cfg: dict) -> None:
|
||||
"""
|
||||
将当前生效的非敏感配置写入本地 config.json。
|
||||
敏感信息(API Keys)不会保存到此文件,仅保存到 .streamlit/secrets.toml。
|
||||
将配置持久化到本地文件:
|
||||
- 非敏感配置 → config.json
|
||||
- API Keys + 品牌信息 → .streamlit/secrets.toml
|
||||
"""
|
||||
import tomllib
|
||||
import tomli_w # type: ignore
|
||||
|
||||
# ── 1. 非敏感配置 → config.json ──
|
||||
config_path = Path(__file__).with_name("config.json")
|
||||
try:
|
||||
data = {}
|
||||
@@ -194,50 +142,132 @@ def save_cfg_to_file(cfg: dict) -> None:
|
||||
data.update(loaded)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"读取配置文件失败: {e}")
|
||||
logging.warning(f"读取 config.json 失败: {e}")
|
||||
data = {}
|
||||
|
||||
# 只保存非敏感配置
|
||||
sensitive_keys = {"gen_api_key", "verify_keys", "tongyi_wanxiang_api_key"}
|
||||
|
||||
for key in ["gen_provider", "verify_providers", "brand", "advantages", "competitors", "temperature"]:
|
||||
if key in cfg:
|
||||
data[key] = cfg[key]
|
||||
|
||||
|
||||
with config_path.open("w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 提示用户如何保存 API Keys
|
||||
if any(key in cfg for key in sensitive_keys):
|
||||
try:
|
||||
st.info("💡 API Keys 需要在 `.streamlit/secrets.toml` 文件中手动配置。")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.error(f"保存配置文件失败: {e}")
|
||||
logging.error(f"保存 config.json 失败: {e}")
|
||||
|
||||
# ── 2. API Keys + 品牌信息 → .streamlit/secrets.toml ──
|
||||
secrets_path = Path(__file__).parent / ".streamlit" / "secrets.toml"
|
||||
try:
|
||||
# 读取现有的 secrets.toml(如果存在)
|
||||
existing = {}
|
||||
if secrets_path.exists():
|
||||
try:
|
||||
with secrets_path.open("rb") as f:
|
||||
existing = tomllib.load(f)
|
||||
except Exception:
|
||||
existing = {}
|
||||
|
||||
# 构建新的 api_keys 段
|
||||
new_api_keys = existing.get("api_keys", {})
|
||||
|
||||
# gen_api_key → deepseek
|
||||
if "gen_api_key" in cfg and cfg["gen_api_key"]:
|
||||
new_api_keys["deepseek"] = cfg["gen_api_key"]
|
||||
|
||||
# verify_keys → 各 provider 的 key
|
||||
verify_keys = cfg.get("verify_keys", {})
|
||||
provider_map = {
|
||||
"DeepSeek": "deepseek",
|
||||
"OpenAI (GPT)": "openai",
|
||||
"Groq": "groq",
|
||||
"Moonshot (Kimi)": "moonshot",
|
||||
"Tongyi (通义千问)": "tongyi",
|
||||
"豆包(字节跳动)": "doubao",
|
||||
"文心一言(百度)": "wenxin",
|
||||
}
|
||||
for display_name, key_name in provider_map.items():
|
||||
if display_name in verify_keys and verify_keys[display_name]:
|
||||
new_api_keys[key_name] = verify_keys[display_name]
|
||||
|
||||
# tongyi_wanxiang_api_key
|
||||
if "tongyi_wanxiang_api_key" in cfg and cfg["tongyi_wanxiang_api_key"]:
|
||||
new_api_keys["tongyi_wanxiang"] = cfg["tongyi_wanxiang_api_key"]
|
||||
|
||||
# 构建新的 app_config 段
|
||||
new_app_config = existing.get("app_config", {})
|
||||
for key in ["brand", "advantages", "competitors"]:
|
||||
if key in cfg and cfg[key]:
|
||||
new_app_config[key] = cfg[key]
|
||||
if "temperature" in cfg:
|
||||
new_app_config["temperature"] = cfg["temperature"]
|
||||
|
||||
# 写出 TOML
|
||||
secrets_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with secrets_path.open("wb") as f:
|
||||
content = {"api_keys": new_api_keys, "app_config": new_app_config}
|
||||
tomli_w.dump(content, f)
|
||||
|
||||
except ImportError:
|
||||
# tomli_w 未安装 — 回退到手动写 TOML 字符串
|
||||
_save_secrets_fallback(secrets_path, cfg)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.error(f"保存 secrets.toml 失败: {e}")
|
||||
try:
|
||||
st.warning("⚠️ 无法将配置写入本地 config.json,但当前会话已生效。请检查文件权限。")
|
||||
st.warning("⚠️ 无法将 API Key 写入 .streamlit/secrets.toml,但当前会话已生效。")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
ss_init("cfg", load_default_cfg())
|
||||
def _save_secrets_fallback(secrets_path: Path, cfg: dict) -> None:
|
||||
"""tomli_w 不可用时的纯文本备选方案"""
|
||||
try:
|
||||
secrets_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 模块1:关键词(补充 init_session_state 中未包含的)
|
||||
ss_init("keyword_tool", KeywordTool()) # 托词工具实例
|
||||
lines = ["# Streamlit Secrets(自动保存)\n"]
|
||||
|
||||
lines.append("\n[api_keys]\n")
|
||||
if cfg.get("gen_api_key"):
|
||||
lines.append(f'deepseek = "{cfg["gen_api_key"]}"\n')
|
||||
|
||||
verify_keys = cfg.get("verify_keys", {})
|
||||
provider_map = {
|
||||
"DeepSeek": "deepseek",
|
||||
"OpenAI (GPT)": "openai",
|
||||
"Groq": "groq",
|
||||
"Moonshot (Kimi)": "moonshot",
|
||||
"Tongyi (通义千问)": "tongyi",
|
||||
"豆包(字节跳动)": "doubao",
|
||||
"文心一言(百度)": "wenxin",
|
||||
}
|
||||
for display_name, key_name in provider_map.items():
|
||||
if display_name in verify_keys and verify_keys[display_name]:
|
||||
lines.append(f'{key_name} = "{verify_keys[display_name]}"\n')
|
||||
|
||||
if cfg.get("tongyi_wanxiang_api_key"):
|
||||
lines.append(f'tongyi_wanxiang = "{cfg["tongyi_wanxiang_api_key"]}"\n')
|
||||
|
||||
lines.append("\n[app_config]\n")
|
||||
for key in ["brand", "advantages", "competitors"]:
|
||||
if cfg.get(key):
|
||||
lines.append(f'{key} = "{cfg[key]}"\n')
|
||||
if "temperature" in cfg:
|
||||
lines.append(f"temperature = {cfg['temperature']}\n")
|
||||
|
||||
with secrets_path.open("w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.error(f"保存 secrets.toml(备选方案)失败: {e}")
|
||||
|
||||
# 模块2:内容(补充 init_session_state 中未包含的)
|
||||
ss_init("multimodal_descriptions", {}) # 多模态描述(配图描述、视频脚本等)
|
||||
ss_init("image_descriptions", []) # 图片描述列表
|
||||
ss_init("detail_tab_active", "🎨 增强工具") # 保存当前激活的详情Tab
|
||||
|
||||
# ------------------- 工具函数 -------------------
|
||||
def validate_cfg(cfg: dict):
|
||||
"""验证配置完整性,返回 (是否有效, 错误列表)。"""
|
||||
errors = []
|
||||
warnings = []
|
||||
|
||||
|
||||
if not cfg.get("gen_api_key", "").strip():
|
||||
errors.append("生成&优化 LLM 的 API Key 未填写")
|
||||
|
||||
@@ -249,7 +279,7 @@ 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():
|
||||
@@ -258,28 +288,12 @@ def validate_cfg(cfg: dict):
|
||||
return (len(errors) == 0), errors + warnings
|
||||
|
||||
|
||||
def model_defaults(provider: str) -> str:
|
||||
from modules.llm_factory import get_default_model
|
||||
return get_default_model(provider)
|
||||
# 初始化默认配置(要在 expander 之前,因为 expander 内访问了 cfg)
|
||||
ss_init("cfg", load_default_cfg())
|
||||
|
||||
|
||||
# ------------------- 缓存 LLM 客户端(显著降低“频繁 Loading”) -------------------
|
||||
@st.cache_resource(show_spinner=False)
|
||||
def build_llm(provider: str, api_key: str, model: str, temperature: float):
|
||||
"""
|
||||
- 使用 cache_resource 缓存客户端,避免每次 rerun 重建
|
||||
- 统一使用 llm_factory 模块构建 LLM
|
||||
"""
|
||||
from modules.llm_factory import build_llm as _build_llm
|
||||
return _build_llm(provider, api_key, model, temperature)
|
||||
|
||||
|
||||
# ------------------- 侧边栏:全局配置(分组折叠) -------------------
|
||||
with st.sidebar:
|
||||
st.header("⚙️ 全局配置")
|
||||
with st.expander("配置", expanded=False):
|
||||
|
||||
# LLM 配置组
|
||||
with st.expander("🤖 LLM 配置", expanded=True):
|
||||
with st.expander("LLM 配置", expanded=True):
|
||||
PROVIDER_LIST = ["DeepSeek", "OpenAI (GPT)", "Tongyi (通义千问)", "Groq", "Moonshot (Kimi)", "豆包(字节跳动)", "文心一言(百度)"]
|
||||
|
||||
gen_provider = st.selectbox(
|
||||
@@ -423,6 +437,28 @@ with st.sidebar:
|
||||
|
||||
st.caption("闭环:关键词 → 创作 → 优化 → 验证")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def model_defaults(provider: str) -> str:
|
||||
from modules.llm_factory import get_default_model
|
||||
return get_default_model(provider)
|
||||
|
||||
|
||||
# ------------------- 缓存 LLM 客户端(显著降低“频繁 Loading”) -------------------
|
||||
@st.cache_resource(show_spinner=False)
|
||||
def build_llm(provider: str, api_key: str, model: str, temperature: float):
|
||||
"""
|
||||
- 使用 cache_resource 缓存客户端,避免每次 rerun 重建
|
||||
- 统一使用 llm_factory 模块构建 LLM
|
||||
"""
|
||||
from modules.llm_factory import build_llm as _build_llm
|
||||
return _build_llm(provider, api_key, model, temperature)
|
||||
|
||||
|
||||
# ------------------- 侧边栏:全局配置(分组折叠) -------------------
|
||||
|
||||
cfg = st.session_state.cfg
|
||||
brand = cfg["brand"]
|
||||
advantages = cfg["advantages"]
|
||||
@@ -620,4 +656,4 @@ with tab10:
|
||||
with tab11:
|
||||
render_tab_knowledge(kb)
|
||||
|
||||
st.caption("最完整版:GitHub模板 + 真实多模型验证 + 现有文章优化 + RAG知识库 • GEO全闭环,专注AI品牌影响力")
|
||||
st.caption("一站式GEO优化平台| 多模型验证 + 文章优化 + RAG知识库 • GEO全闭环,专注AI品牌影响力")
|
||||
|
||||
Reference in New Issue
Block a user