299 lines
15 KiB
Python
299 lines
15 KiB
Python
|
|
# Tab9:平台同步(从 geo_tool.py 迁移,通过 render_tab_platform_sync() 供主入口调用。)
|
|||
|
|
|
|||
|
|
import pandas as pd
|
|||
|
|
import streamlit as st
|
|||
|
|
|
|||
|
|
|
|||
|
|
def render_tab_platform_sync(storage, brand: str) -> None:
|
|||
|
|
"""渲染 Tab9:平台同步。由主入口在 with tab9 内调用。"""
|
|||
|
|
st.markdown("### 📤 平台文章同步")
|
|||
|
|
st.caption("将生成的文章自动发布到各平台,支持API发布和一键复制")
|
|||
|
|
|
|||
|
|
# 品牌信息:优先使用主入口传入的 brand(来自侧边栏 cfg),与其它 Tab 一致
|
|||
|
|
brand_to_use = (st.session_state.get("brand") or brand or "").strip()
|
|||
|
|
if not brand_to_use:
|
|||
|
|
st.info("请先在侧边栏设置品牌信息")
|
|||
|
|
else:
|
|||
|
|
# 平台账号配置
|
|||
|
|
st.markdown("---")
|
|||
|
|
st.markdown("#### 🔐 平台账号配置")
|
|||
|
|
|
|||
|
|
platform_config_tabs = st.tabs(["GitHub", "其他平台(开发中)"])
|
|||
|
|
|
|||
|
|
with platform_config_tabs[0]:
|
|||
|
|
st.markdown("##### GitHub 配置")
|
|||
|
|
st.caption("配置GitHub账号信息,用于自动发布文章到GitHub仓库")
|
|||
|
|
|
|||
|
|
# 检查是否已有配置
|
|||
|
|
existing_config = storage.get_platform_account("GitHub", brand_to_use)
|
|||
|
|
|
|||
|
|
github_api_key = st.text_input(
|
|||
|
|
"GitHub Personal Access Token",
|
|||
|
|
value=existing_config.get('api_key', '') if existing_config else '',
|
|||
|
|
type="password",
|
|||
|
|
help="在 https://github.com/settings/tokens 创建Token,需要 repo 权限",
|
|||
|
|
key="github_api_key"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
github_repo_owner = st.text_input(
|
|||
|
|
"仓库所有者(用户名)",
|
|||
|
|
value=existing_config.get('config', {}).get('repo_owner', '') if existing_config else '',
|
|||
|
|
help="GitHub用户名或组织名",
|
|||
|
|
key="github_repo_owner"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
github_repo_name = st.text_input(
|
|||
|
|
"仓库名称",
|
|||
|
|
value=existing_config.get('config', {}).get('repo_name', '') if existing_config else '',
|
|||
|
|
help="要发布到的仓库名称",
|
|||
|
|
key="github_repo_name"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
col1, col2 = st.columns([1, 4])
|
|||
|
|
with col1:
|
|||
|
|
if st.button("💾 保存配置", type="primary", use_container_width=True):
|
|||
|
|
if github_api_key and github_repo_owner and github_repo_name:
|
|||
|
|
try:
|
|||
|
|
# 验证账号
|
|||
|
|
from platform_sync.github_publisher import GitHubPublisher
|
|||
|
|
publisher = GitHubPublisher(github_api_key, github_repo_owner, github_repo_name)
|
|||
|
|
if publisher.validate_account():
|
|||
|
|
storage.save_platform_account(
|
|||
|
|
platform="GitHub",
|
|||
|
|
account_config={
|
|||
|
|
'account_type': 'api',
|
|||
|
|
'api_key': github_api_key,
|
|||
|
|
'config': {
|
|||
|
|
'repo_owner': github_repo_owner,
|
|||
|
|
'repo_name': github_repo_name
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
brand=brand_to_use
|
|||
|
|
)
|
|||
|
|
st.success("✅ GitHub配置已保存并验证成功!")
|
|||
|
|
else:
|
|||
|
|
st.error("❌ GitHub Token验证失败,请检查Token是否正确")
|
|||
|
|
except Exception as e:
|
|||
|
|
st.error(f"❌ 配置保存失败: {str(e)}")
|
|||
|
|
else:
|
|||
|
|
st.error("请填写完整信息")
|
|||
|
|
|
|||
|
|
with col2:
|
|||
|
|
if existing_config:
|
|||
|
|
st.info("✅ 已配置GitHub账号")
|
|||
|
|
|
|||
|
|
# 发布功能
|
|||
|
|
st.markdown("---")
|
|||
|
|
st.markdown("#### 📝 发布文章")
|
|||
|
|
|
|||
|
|
# 选择文章
|
|||
|
|
articles = storage.get_articles(brand=brand_to_use)
|
|||
|
|
if articles:
|
|||
|
|
# 文章选择
|
|||
|
|
article_options = {}
|
|||
|
|
for article in articles:
|
|||
|
|
display_name = f"{article.get('keyword', 'N/A')} - {article.get('platform', 'N/A')}"
|
|||
|
|
article_options[display_name] = article.get('id')
|
|||
|
|
|
|||
|
|
if article_options:
|
|||
|
|
selected_article_key = st.selectbox(
|
|||
|
|
"选择要发布的文章",
|
|||
|
|
list(article_options.keys()),
|
|||
|
|
key="publish_article_select"
|
|||
|
|
)
|
|||
|
|
selected_article_id = article_options[selected_article_key]
|
|||
|
|
|
|||
|
|
# 选择平台
|
|||
|
|
# 定义平台列表
|
|||
|
|
api_platforms = ["GitHub"]
|
|||
|
|
copy_platforms = [
|
|||
|
|
"头条号(资讯软文)", "小红书(生活种草)", "抖音图文(短内容)", "简书(文艺)",
|
|||
|
|
"QQ空间(社交)", "新浪博客(博客)", "新浪新闻(资讯)", "搜狐号(资讯)",
|
|||
|
|
"一点号(资讯)", "东方财富(财经)", "邦阅网(外贸)", "原创力文档(文档)"
|
|||
|
|
]
|
|||
|
|
all_publish_platforms = api_platforms + copy_platforms
|
|||
|
|
|
|||
|
|
publish_platform = st.selectbox(
|
|||
|
|
"选择发布平台",
|
|||
|
|
all_publish_platforms,
|
|||
|
|
key="publish_platform_select"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if publish_platform == "GitHub":
|
|||
|
|
# 检查配置
|
|||
|
|
account_config = storage.get_platform_account("GitHub", brand_to_use)
|
|||
|
|
if not account_config:
|
|||
|
|
st.warning("⚠️ 请先配置GitHub账号")
|
|||
|
|
else:
|
|||
|
|
# 获取文章
|
|||
|
|
article = next((a for a in articles if a.get('id') == selected_article_id), None)
|
|||
|
|
if article:
|
|||
|
|
# 显示文章预览
|
|||
|
|
with st.expander("📄 文章预览", expanded=False):
|
|||
|
|
st.markdown(f"**关键词**: {article.get('keyword', 'N/A')}")
|
|||
|
|
st.markdown(f"**平台**: {article.get('platform', 'N/A')}")
|
|||
|
|
st.markdown(f"**内容长度**: {len(article.get('content', ''))} 字符")
|
|||
|
|
st.markdown("---")
|
|||
|
|
st.text_area("内容", article.get('content', ''), height=200, disabled=True)
|
|||
|
|
|
|||
|
|
# 发布选项
|
|||
|
|
file_path = st.text_input(
|
|||
|
|
"文件路径(可选)",
|
|||
|
|
value=f"content/{article.get('keyword', 'article').replace(' ', '_')[:50]}.md",
|
|||
|
|
help="GitHub仓库中的文件路径,留空使用默认路径",
|
|||
|
|
key="github_file_path"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if st.button("🚀 发布到GitHub", type="primary", use_container_width=True):
|
|||
|
|
try:
|
|||
|
|
from platform_sync.github_publisher import GitHubPublisher
|
|||
|
|
publisher = GitHubPublisher(
|
|||
|
|
api_key=account_config['api_key'],
|
|||
|
|
repo_owner=account_config['config']['repo_owner'],
|
|||
|
|
repo_name=account_config['config']['repo_name']
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
with st.spinner("正在发布到GitHub..."):
|
|||
|
|
result = publisher.publish(
|
|||
|
|
content=article.get('content', ''),
|
|||
|
|
title=article.get('keyword', 'Untitled'),
|
|||
|
|
file_path=file_path if file_path else None
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 保存发布记录
|
|||
|
|
storage.save_publish_record(
|
|||
|
|
article_id=selected_article_id,
|
|||
|
|
platform="GitHub",
|
|||
|
|
publish_method="api",
|
|||
|
|
publish_status="success" if result['success'] else "failed",
|
|||
|
|
publish_url=result.get('publish_url', ''),
|
|||
|
|
publish_id=result.get('publish_id', ''),
|
|||
|
|
error_message=result.get('error', '')
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 显示结果
|
|||
|
|
if result['success']:
|
|||
|
|
st.success(f"✅ 发布成功!")
|
|||
|
|
st.markdown(f"**发布链接**: [{result['publish_url']}]({result['publish_url']})")
|
|||
|
|
st.balloons()
|
|||
|
|
else:
|
|||
|
|
st.error(f"❌ 发布失败: {result.get('error', '未知错误')}")
|
|||
|
|
except Exception as e:
|
|||
|
|
st.error(f"❌ 发布过程出错: {str(e)}")
|
|||
|
|
storage.save_publish_record(
|
|||
|
|
article_id=selected_article_id,
|
|||
|
|
platform="GitHub",
|
|||
|
|
publish_method="api",
|
|||
|
|
publish_status="failed",
|
|||
|
|
error_message=str(e)
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
# 一键复制平台
|
|||
|
|
article = next((a for a in articles if a.get('id') == selected_article_id), None)
|
|||
|
|
if article:
|
|||
|
|
from platform_sync.copy_manager import CopyManager
|
|||
|
|
copy_manager = CopyManager()
|
|||
|
|
|
|||
|
|
# 格式化内容
|
|||
|
|
formatted_content = copy_manager.format_for_platform(
|
|||
|
|
platform=publish_platform,
|
|||
|
|
content=article.get('content', ''),
|
|||
|
|
title=article.get('keyword', 'Untitled'),
|
|||
|
|
keyword=article.get('keyword', ''),
|
|||
|
|
brand=brand_to_use
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 显示格式化后的内容
|
|||
|
|
with st.expander("📄 格式化后的内容(已复制到剪贴板)", expanded=True):
|
|||
|
|
st.text_area(
|
|||
|
|
"内容",
|
|||
|
|
formatted_content,
|
|||
|
|
height=300,
|
|||
|
|
key="formatted_content_display"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 发布指南
|
|||
|
|
guide = copy_manager.generate_publish_guide(publish_platform)
|
|||
|
|
with st.expander("📋 发布指南", expanded=True):
|
|||
|
|
st.markdown(guide)
|
|||
|
|
|
|||
|
|
# 复制按钮
|
|||
|
|
col1, col2 = st.columns([1, 1])
|
|||
|
|
with col1:
|
|||
|
|
if st.button("📋 复制到剪贴板", type="primary", use_container_width=True):
|
|||
|
|
if copy_manager.copy_to_clipboard(formatted_content):
|
|||
|
|
st.success("✅ 内容已复制到剪贴板!")
|
|||
|
|
st.info("💡 请按照上方发布指南,将内容粘贴到对应平台发布")
|
|||
|
|
|
|||
|
|
# 保存发布记录(标记为已复制)
|
|||
|
|
storage.save_publish_record(
|
|||
|
|
article_id=selected_article_id,
|
|||
|
|
platform=publish_platform,
|
|||
|
|
publish_method="copy",
|
|||
|
|
publish_status="copied",
|
|||
|
|
error_message=""
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
st.error("❌ 复制失败,请手动复制内容")
|
|||
|
|
|
|||
|
|
with col2:
|
|||
|
|
if st.button("📥 下载内容", use_container_width=True):
|
|||
|
|
# 生成下载文件
|
|||
|
|
safe_title = article.get('keyword', 'article').replace(' ', '_')[:50]
|
|||
|
|
filename = f"{publish_platform.replace('(', '_').replace(')', '')}_{safe_title}.txt"
|
|||
|
|
st.download_button(
|
|||
|
|
label="⬇️ 下载",
|
|||
|
|
data=formatted_content,
|
|||
|
|
file_name=filename,
|
|||
|
|
mime="text/plain",
|
|||
|
|
key="download_formatted_content"
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
st.info("📝 请先在【2 自动创作】中生成文章")
|
|||
|
|
|
|||
|
|
# 发布记录
|
|||
|
|
st.markdown("---")
|
|||
|
|
st.markdown("#### 📊 发布记录")
|
|||
|
|
|
|||
|
|
publish_records = storage.get_publish_records(brand=brand_to_use)
|
|||
|
|
if publish_records:
|
|||
|
|
# 统计信息
|
|||
|
|
total_records = len(publish_records)
|
|||
|
|
success_records = len([r for r in publish_records if r.get('publish_status') == 'success'])
|
|||
|
|
copied_records = len([r for r in publish_records if r.get('publish_status') == 'copied'])
|
|||
|
|
failed_records = len([r for r in publish_records if r.get('publish_status') == 'failed'])
|
|||
|
|
|
|||
|
|
stat_col1, stat_col2, stat_col3, stat_col4 = st.columns(4)
|
|||
|
|
with stat_col1:
|
|||
|
|
st.metric("总发布数", total_records)
|
|||
|
|
with stat_col2:
|
|||
|
|
st.metric("API成功", success_records, delta=f"{success_records/total_records*100:.1f}%" if total_records > 0 else "0%")
|
|||
|
|
with stat_col3:
|
|||
|
|
st.metric("已复制", copied_records, delta=f"{copied_records/total_records*100:.1f}%" if total_records > 0 else "0%")
|
|||
|
|
with stat_col4:
|
|||
|
|
st.metric("失败", failed_records)
|
|||
|
|
|
|||
|
|
# 记录列表
|
|||
|
|
st.markdown("##### 最近发布记录")
|
|||
|
|
records_df = pd.DataFrame(publish_records[:20]) # 显示最近20条
|
|||
|
|
if not records_df.empty:
|
|||
|
|
# 格式化显示
|
|||
|
|
display_df = records_df[['platform', 'publish_method', 'publish_status', 'publish_url', 'published_at', 'created_at']].copy()
|
|||
|
|
display_df.columns = ['平台', '发布方式', '状态', '链接', '发布时间', '创建时间']
|
|||
|
|
display_df['状态'] = display_df['状态'].map({
|
|||
|
|
'success': '✅ 成功',
|
|||
|
|
'failed': '❌ 失败',
|
|||
|
|
'pending': '⏳ 待发布',
|
|||
|
|
'copied': '📋 已复制'
|
|||
|
|
})
|
|||
|
|
display_df['发布方式'] = display_df['发布方式'].map({
|
|||
|
|
'api': 'API',
|
|||
|
|
'copy': '一键复制'
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
st.dataframe(display_df, use_container_width=True, hide_index=True)
|
|||
|
|
else:
|
|||
|
|
st.info("暂无发布记录")
|
|||
|
|
|
|||
|
|
# =======================
|