✨ feat(config): 更新模型配置与LLM提示词指南

- 将默认LLM模型从gemini-2.0-flash升级为gemini-3-flash-preview
- 将博主人设从"性感福利主播"更改为"二次元coser"
- 优化LLM生成SD提示词的指南,新增中国审美人物描述规则
- 为各SD模型添加颜值核心词、示范prompt和禁止使用的关键词
- 新增三维人物描述法(眼睛/肤色/气质)和专属光线词指导

📦 build(openspec): 归档旧规范并创建新规范

- 将improve-maintainability规范归档至2026-02-25目录
- 新增2026-02-26-improve-ui-layout规范,包含UI布局优化设计
- 新增2026-02-26-optimize-image-generation规范,包含图片生成优化设计
- 在根目录openspec/specs下新增图片质量、后处理、中国审美和LLM提示词规范

♻️ refactor(sd_service): 优化SD模型配置和图片后处理

- 为各SD模型添加中国审美特征词和欧美面孔排除词
- 新增高画质预设档,SDXL模型启用Hires Fix参数
- 将后处理拆分为beauty_enhance和anti_detect_postprocess两个独立函数
- 新增美化增强功能,支持通过enhance_level参数控制强度

♻️ refactor(services): 更新内容生成服务以支持美化增强

- 在generate_images函数中新增enhance_level参数
- 将美化强度参数传递至SDService.txt2img调用

♻️ refactor(ui): 优化UI布局和添加美化强度控件

- 注入自定义CSS主题层,优化字体、按钮和卡片样式
- 将全局设置迁移至独立的"⚙️ 配置"Tab,优化Tab顺序
- 在内容创作Tab的高级设置中添加美化强度滑块控件
- 优化自动运营Tab布局,改为2列卡片网格展示
This commit is contained in:
2026-02-26 22:58:05 +08:00
parent b635108b89
commit b5deafa2cc
53 changed files with 1578 additions and 387 deletions
+375 -332
View File
@@ -57,7 +57,34 @@ from publish_queue import STATUS_LABELS
logger = logging.getLogger("autobot")
_GRADIO_CSS = None # 可在此处添加自定义 CSS
_GRADIO_CSS = """
/* ── Autobot 主题层 ── */
body, .gradio-container {
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif !important;
}
/* 按钮圆角统一 ≥8px */
.gr-button, button.lg, button.sm, button.md {
border-radius: 8px !important;
}
/* gr.Group 卡片轻阴影 */
.gr-group {
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.07) !important;
border-radius: 10px !important;
}
/* 标题层级优化 */
.gradio-container h3 { font-size: 1.05rem; font-weight: 600; }
.gradio-container h4 { font-size: 0.95rem; font-weight: 600; }
/* Tab 导航条优化 */
.tab-nav {
border-bottom: 2px solid #f0f0f0;
gap: 2px;
}
.tab-nav button {
border-radius: 6px 6px 0 0 !important;
font-weight: 500;
padding: 6px 14px;
}
"""
def build_app(cfg: "ConfigManager", analytics: "AnalyticsService") -> gr.Blocks:
@@ -66,6 +93,7 @@ def build_app(cfg: "ConfigManager", analytics: "AnalyticsService") -> gr.Blocks:
with gr.Blocks(
title="小红书 AI 爆文工坊 V2.0",
css=_GRADIO_CSS,
) as app:
gr.Markdown(
"# 🍒 小红书 AI 爆文生产工坊 V2.0\n"
@@ -75,118 +103,121 @@ def build_app(cfg: "ConfigManager", analytics: "AnalyticsService") -> gr.Blocks:
# 全局状态
state_search_result = gr.State("")
# ============ 全局设置栏 ============
with gr.Accordion("⚙️ 全局设置 (自动保存)", open=False):
gr.Markdown("#### 🤖 LLM 提供商 (支持所有 OpenAI 兼容接口)")
with gr.Row():
llm_provider = gr.Dropdown(
label="选择 LLM 提供商",
choices=cfg.get_llm_provider_names(),
value=cfg.get("active_llm", ""),
interactive=True, scale=2,
)
btn_connect_llm = gr.Button("🔗 连接 LLM", size="sm", scale=1)
with gr.Row():
llm_model = gr.Dropdown(
label="LLM 模型", value=config["model"],
allow_custom_value=True, interactive=True, scale=2,
)
llm_provider_info = gr.Markdown(
value="*选择提供商后显示详情*",
)
with gr.Accordion("➕ 添加 / 管理 LLM 提供商", open=False):
# ============ Tab 页面 ============
# ⚙️ 配置 Tab 声明在最前以确保共享组件变量先于 build_tab() 定义
# selected=1 令「✨ 内容创作」为默认激活 Tab
with gr.Tabs(selected=1):
# -------- Tab 0: ⚙️ 配置 --------
with gr.Tab("⚙️ 配置"):
gr.Markdown("#### 🤖 LLM 提供商 (支持所有 OpenAI 兼容接口)")
with gr.Row():
new_provider_name = gr.Textbox(
label="名称", placeholder="如: DeepSeek / GPT-4o / 通义千问",
llm_provider = gr.Dropdown(
label="选择 LLM 提供商",
choices=cfg.get_llm_provider_names(),
value=cfg.get("active_llm", ""),
interactive=True, scale=2,
)
btn_connect_llm = gr.Button("🔗 连接 LLM", variant="primary", size="sm", scale=1)
with gr.Row():
llm_model = gr.Dropdown(
label="LLM 模型", value=config["model"],
allow_custom_value=True, interactive=True, scale=2,
)
llm_provider_info = gr.Markdown(
value="*选择提供商后显示详情*",
)
with gr.Accordion("➕ 添加 / 管理 LLM 提供商", open=False):
with gr.Row():
new_provider_name = gr.Textbox(
label="名称", placeholder="如: DeepSeek / GPT-4o / 通义千问",
scale=1,
)
new_provider_key = gr.Textbox(
label="API Key", type="password", scale=2,
)
new_provider_url = gr.Textbox(
label="Base URL", placeholder="https://api.openai.com/v1",
value="https://api.openai.com/v1", scale=2,
)
with gr.Row():
btn_add_provider = gr.Button("✅ 添加提供商", variant="primary", size="sm")
btn_del_provider = gr.Button("🗑️ 删除当前提供商", variant="stop", size="sm")
provider_mgmt_status = gr.Markdown("")
gr.Markdown("---")
gr.Markdown("#### 🔗 服务连接")
with gr.Row():
mcp_url = gr.Textbox(
label="MCP Server URL", value=config["mcp_url"], scale=2,
)
sd_url = gr.Textbox(
label="SD WebUI URL", value=config["sd_url"], scale=2,
)
with gr.Row():
persona = gr.Dropdown(
label="博主人设(评论/回复/自动运营通用)",
choices=[RANDOM_PERSONA_LABEL] + DEFAULT_PERSONAS,
value=config.get("persona", RANDOM_PERSONA_LABEL),
allow_custom_value=True,
interactive=True,
scale=5,
)
with gr.Row():
btn_connect_sd = gr.Button("🎨 连接 SD", variant="primary", size="sm")
btn_check_mcp = gr.Button("📡 检查 MCP", size="sm")
with gr.Row():
sd_model = gr.Dropdown(
label="SD 模型", allow_custom_value=True,
interactive=True, scale=2,
)
sd_model_info = gr.Markdown("选择模型后显示适配信息", elem_id="sd_model_info")
status_bar = gr.Markdown("🔄 等待连接...")
gr.Markdown("---")
gr.Markdown("#### 🎭 AI 换脸 (ReActor)")
gr.Markdown(
"> 上传你的头像,生成含人物的图片时自动替换为你的脸\n"
"> 需要 SD WebUI 已安装 [ReActor](https://github.com/Gourieff/sd-webui-reactor) 扩展"
)
with gr.Row():
face_image_input = gr.Image(
label="上传头像 (正面清晰照片效果最佳)",
type="pil",
height=180,
scale=1,
)
new_provider_key = gr.Textbox(
label="API Key", type="password", scale=2,
)
new_provider_url = gr.Textbox(
label="Base URL", placeholder="https://api.openai.com/v1",
value="https://api.openai.com/v1", scale=2,
face_image_preview = gr.Image(
label="当前头像",
type="pil",
height=180,
interactive=False,
value=SDService.load_face_image(),
scale=1,
)
with gr.Row():
btn_add_provider = gr.Button("✅ 添加提供商", variant="primary", size="sm")
btn_del_provider = gr.Button("🗑️ 删除当前提供商", variant="stop", size="sm")
provider_mgmt_status = gr.Markdown("")
gr.Markdown("---")
with gr.Row():
mcp_url = gr.Textbox(
label="MCP Server URL", value=config["mcp_url"], scale=2,
)
sd_url = gr.Textbox(
label="SD WebUI URL", value=config["sd_url"], scale=2,
)
with gr.Row():
persona = gr.Dropdown(
label="博主人设(评论/回复/自动运营通用)",
choices=[RANDOM_PERSONA_LABEL] + DEFAULT_PERSONAS,
value=config.get("persona", RANDOM_PERSONA_LABEL),
allow_custom_value=True,
interactive=True,
scale=5,
)
with gr.Row():
btn_connect_sd = gr.Button("🎨 连接 SD", size="sm")
btn_check_mcp = gr.Button("📡 检查 MCP", size="sm")
with gr.Row():
sd_model = gr.Dropdown(
label="SD 模型", allow_custom_value=True,
interactive=True, scale=2,
)
sd_model_info = gr.Markdown("选择模型后显示适配信息", elem_id="sd_model_info")
status_bar = gr.Markdown("🔄 等待连接...")
gr.Markdown("---")
gr.Markdown("#### 🎭 AI 换脸 (ReActor)")
gr.Markdown(
"> 上传你的头像,生成含人物的图片时自动替换为你的脸\n"
"> 需要 SD WebUI 已安装 [ReActor](https://github.com/Gourieff/sd-webui-reactor) 扩展"
)
with gr.Row():
face_image_input = gr.Image(
label="上传头像 (正面清晰照片效果最佳)",
type="pil",
height=180,
scale=1,
)
face_image_preview = gr.Image(
label="当前头像",
type="pil",
height=180,
interactive=False,
value=SDService.load_face_image(),
scale=1,
)
with gr.Row():
btn_save_face = gr.Button("💾 保存头像", variant="primary", size="sm")
face_swap_toggle = gr.Checkbox(
label="🎭 生成图片时启用 AI 换脸",
value=os.path.isfile(FACE_IMAGE_PATH),
interactive=True,
)
face_status = gr.Markdown(
"✅ 头像已就绪" if os.path.isfile(FACE_IMAGE_PATH) else "ℹ️ 尚未设置头像"
)
gr.Markdown("---")
gr.Markdown("#### 🖥️ 系统设置")
with gr.Row():
autostart_toggle = gr.Checkbox(
label="🚀 Windows 开机自启(静默后台运行)",
value=is_autostart_enabled(),
interactive=(platform.system() == "Windows"),
)
autostart_status = gr.Markdown(
value="✅ 已启用" if is_autostart_enabled() else "⚪ 未启用",
btn_save_face = gr.Button("💾 保存头像", variant="primary", size="sm")
face_swap_toggle = gr.Checkbox(
label="🎭 生成图片时启用 AI 换脸",
value=os.path.isfile(FACE_IMAGE_PATH),
interactive=True,
)
face_status = gr.Markdown(
"✅ 头像已就绪" if os.path.isfile(FACE_IMAGE_PATH) else "ℹ️ 尚未设置头像"
)
# ============ Tab 页面 ============
with gr.Tabs():
# -------- Tab 1: 内容创作(迁移至 ui/tab_create.py)--------
gr.Markdown("---")
gr.Markdown("#### 🖥️ 系统设置")
with gr.Row():
autostart_toggle = gr.Checkbox(
label="🚀 Windows 开机自启(静默后台运行)",
value=is_autostart_enabled(),
interactive=(platform.system() == "Windows"),
)
autostart_status = gr.Markdown(
value="✅ 已启用" if is_autostart_enabled() else "⚪ 未启用",
)
# -------- Tab 1: ✨ 内容创作(默认激活)--------
_tab1 = build_tab(
config=config,
styles=DEFAULT_STYLES,
@@ -217,7 +248,101 @@ def build_app(cfg: "ConfigManager", analytics: "AnalyticsService") -> gr.Blocks:
cfg_scale = _tab1["cfg_scale"]
neg_prompt = _tab1["neg_prompt"]
# -------- Tab 2: 热点探测 --------
# -------- Tab 2: 📅 内容排期 --------
with gr.Tab("📅 内容排期"):
gr.Markdown(
"### 📅 内容排期日历 + 发布队列\n"
"> 批量生成内容 → 预览审核 → 排期定时 → 自动发布,内容创作全流程管控\n\n"
"**工作流**: 生成内容 → 📝草稿 → ✅审核通过 → 🕐排期/立即发布 → 🚀自动发布"
)
with gr.Row():
# ===== 左栏: 生成 & 队列控制 =====
with gr.Column(scale=1):
gr.Markdown("#### 🔧 批量生成到队列")
queue_gen_topics = gr.Textbox(
label="主题池 (逗号分隔,随人设自动切换)",
value=", ".join(get_persona_topics(config.get("persona", ""))),
placeholder="会从池中随机选取,切换人设自动更新",
)
with gr.Row():
queue_gen_count = gr.Number(
label="生成数量", value=config.get("queue_gen_count", 3), minimum=1, maximum=10,
)
queue_gen_schedule = gr.Textbox(
label="排期时间 (可选)",
placeholder="如 2026-02-10 18:00:00,留空=仅草稿",
)
btn_queue_generate = gr.Button(
"📝 批量生成 → 加入队列", variant="primary", size="lg",
)
queue_gen_result = gr.Markdown("")
gr.Markdown("---")
gr.Markdown("#### ⚙️ 队列处理器")
queue_processor_status = gr.Markdown(
value=queue_get_status(),
)
with gr.Row():
btn_queue_start = gr.Button(
"▶️ 启动队列处理", variant="primary",
)
btn_queue_stop = gr.Button(
"⏹️ 停止队列处理", variant="stop",
)
queue_processor_result = gr.Markdown("")
gr.Markdown("---")
gr.Markdown("#### 🔍 操作单个队列项")
queue_item_id = gr.Textbox(
label="队列项 ID", placeholder="输入 # 号,如 1",
)
with gr.Row():
btn_queue_preview = gr.Button("👁️ 预览", size="sm")
btn_queue_approve = gr.Button("✅ 通过", size="sm", variant="primary")
btn_queue_reject = gr.Button("🚫 拒绝", size="sm", variant="stop")
with gr.Row():
btn_queue_publish_now = gr.Button("🚀 立即发布", size="sm", variant="primary")
btn_queue_retry = gr.Button("🔄 重试", size="sm")
btn_queue_delete = gr.Button("🗑️ 删除", size="sm", variant="stop")
queue_schedule_time = gr.Textbox(
label="排期时间 (审核通过时可指定)",
placeholder="如 2026-02-10 20:00:00,留空=立即待发布",
)
btn_queue_batch_approve = gr.Button(
"✅ 批量通过所有草稿", variant="secondary",
)
queue_op_result = gr.Markdown("")
# ===== 右栏: 队列列表 & 日历 =====
with gr.Column(scale=2):
gr.Markdown("#### 📋 发布队列")
with gr.Row():
queue_filter = gr.Dropdown(
label="状态筛选",
choices=["全部"] + list(STATUS_LABELS.values()),
value="全部",
)
btn_queue_refresh = gr.Button("🔄 刷新", size="sm")
queue_table = gr.Markdown(
value=queue_format_table(),
label="队列列表",
)
gr.Markdown("---")
gr.Markdown("#### 📅 排期日历")
queue_calendar = gr.Markdown(
value=queue_format_calendar(),
label="日历视图",
)
gr.Markdown("---")
gr.Markdown("#### 👁️ 内容预览")
queue_preview_display = gr.Markdown(
value="*选择队列项 ID 后点击预览*",
)
# -------- Tab 3: 🔥 热点探测 --------
with gr.Tab("🔥 热点探测"):
gr.Markdown("### 搜索热门内容 → AI 分析趋势 → 一键借鉴创作")
with gr.Row():
@@ -380,67 +505,7 @@ def build_app(cfg: "ConfigManager", analytics: "AnalyticsService") -> gr.Blocks:
)
my_reply_status = gr.Markdown("")
# -------- Tab 4: 账号登录 --------
with gr.Tab("🔐 账号登录"):
gr.Markdown(
"### 小红书账号登录\n"
"> 扫码登录后自动获取 xsec_token,配合用户 ID 即可使用所有功能"
)
with gr.Row():
with gr.Column(scale=1):
gr.Markdown(
"**操作步骤:**\n"
"1. 确保 MCP 服务已启动\n"
"2. 点击「获取登录二维码」→ 用小红书 App 扫码\n"
"3. 点击「检查登录状态」→ 自动获取并保存 xsec_token\n"
"4. 首次使用请填写你的用户 ID 并点击保存\n\n"
"⚠️ 登录后不要在其他网页端登录同一账号,否则会被踢出"
)
btn_get_qrcode = gr.Button(
"📱 获取登录二维码", variant="primary", size="lg",
)
btn_check_login = gr.Button(
"🔍 检查登录状态 (自动获取 Token)",
variant="secondary", size="lg",
)
btn_logout = gr.Button(
"🚪 退出登录 (重新扫码)",
variant="stop", size="lg",
)
login_status = gr.Markdown("🔄 等待操作...")
gr.Markdown("---")
gr.Markdown(
"#### 📌 我的账号信息\n"
"> **注意**: 小红书号 ≠ 用户 ID\n"
"> - **小红书号 (redId)**: 如 `18688457507`,是你在 App 个人页看到的\n"
"> - **用户 ID (userId)**: 如 `5a695db6e8ac2b72e8af2a53`,24位十六进制字符串\n\n"
"💡 **如何获取 userId?**\n"
"1. 用浏览器打开你的小红书主页\n"
"2. 网址格式为: `xiaohongshu.com/user/profile/xxxxxxxx`\n"
"3. `profile/` 后面的就是你的 userId"
)
login_user_id = gr.Textbox(
label="我的用户 ID (24位 userId, 非小红书号)",
value=config.get("my_user_id", ""),
placeholder="如: 5a695db6e8ac2b72e8af2a53",
)
login_xsec_token = gr.Textbox(
label="xsec_token (登录后自动获取)",
value=config.get("xsec_token", ""),
interactive=False,
)
btn_save_uid = gr.Button(
"💾 保存用户 ID", variant="secondary",
)
save_uid_status = gr.Markdown("")
with gr.Column(scale=1):
qr_image = gr.Image(
label="扫码登录", height=350, width=350,
)
# -------- Tab 5: 数据看板 --------
# -------- Tab 5: 📊 数据看板 --------
with gr.Tab("📊 数据看板"):
gr.Markdown(
"### 我的账号数据看板\n"
@@ -657,94 +722,106 @@ def build_app(cfg: "ConfigManager", analytics: "AnalyticsService") -> gr.Blocks:
)
auto_publish_result = gr.Markdown("")
# 右栏: 定时自动化
with gr.Column(scale=1):
# 右栏: 定时自动化 (2列卡片网格)
with gr.Column(scale=2):
gr.Markdown("#### ⏰ 随机定时自动化")
gr.Markdown(
"> 设置时间间隔后启动,系统将在随机时间自动执行\n"
"> 模拟真人操作节奏,降低被检测风险"
"> 设置时间间隔后启动,系统将在随机时间自动执行 · 模拟真人操作节奏,降低被检测风险"
)
sched_status = gr.Markdown("⚪ **调度器未运行**")
# 运营时段设置
with gr.Group():
gr.Markdown("##### ⏰ 运营时段")
with gr.Row():
sched_start_hour = gr.Number(
label="开始时间(整点)", value=config.get("sched_start_hour", 8), minimum=0, maximum=23,
)
sched_end_hour = gr.Number(
label="结束时间(整点)", value=config.get("sched_end_hour", 23), minimum=1, maximum=24,
)
# ── 第1行: 时段 + 评论 ──
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("##### ⏰ 运营时段")
with gr.Row():
sched_start_hour = gr.Number(
label="开始(整点)", value=config.get("sched_start_hour", 8), minimum=0, maximum=23,
)
sched_end_hour = gr.Number(
label="结束(整点)", value=config.get("sched_end_hour", 23), minimum=1, maximum=24,
)
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("##### 💬 自动评论")
sched_comment_on = gr.Checkbox(
label="启用", value=config.get("sched_comment_on", True),
)
with gr.Row():
sched_c_min = gr.Number(
label="最小间隔(分钟)", value=config.get("sched_c_min", 15), minimum=5,
)
sched_c_max = gr.Number(
label="最大间隔(分钟)", value=config.get("sched_c_max", 45), minimum=10,
)
with gr.Group():
sched_comment_on = gr.Checkbox(
label="✅ 启用自动评论", value=config.get("sched_comment_on", True),
)
with gr.Row():
sched_c_min = gr.Number(
label="评论最小间隔(分钟)", value=config.get("sched_c_min", 15), minimum=5,
)
sched_c_max = gr.Number(
label="评论最大间隔(分钟)", value=config.get("sched_c_max", 45), minimum=10,
)
# ── 第2行: 点赞 + 收藏 ──
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("##### 👍 自动点赞")
sched_like_on = gr.Checkbox(
label="启用", value=config.get("sched_like_on", True),
)
with gr.Row():
sched_l_min = gr.Number(
label="最小间隔(分钟)", value=config.get("sched_l_min", 10), minimum=3,
)
sched_l_max = gr.Number(
label="最大间隔(分钟)", value=config.get("sched_l_max", 30), minimum=5,
)
sched_like_count = gr.Number(
label="每轮点赞数", value=config.get("sched_like_count", 5), minimum=1, maximum=15,
)
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("##### ⭐ 自动收藏")
sched_fav_on = gr.Checkbox(
label="启用", value=config.get("sched_fav_on", True),
)
with gr.Row():
sched_fav_min = gr.Number(
label="最小间隔(分钟)", value=config.get("sched_fav_min", 12), minimum=3,
)
sched_fav_max = gr.Number(
label="最大间隔(分钟)", value=config.get("sched_fav_max", 35), minimum=5,
)
sched_fav_count = gr.Number(
label="每轮收藏数", value=config.get("sched_fav_count", 3), minimum=1, maximum=10,
)
with gr.Group():
sched_like_on = gr.Checkbox(
label="✅ 启用自动点赞", value=config.get("sched_like_on", True),
)
with gr.Row():
sched_l_min = gr.Number(
label="点赞最小间隔(分钟)", value=config.get("sched_l_min", 10), minimum=3,
)
sched_l_max = gr.Number(
label="点赞最大间隔(分钟)", value=config.get("sched_l_max", 30), minimum=5,
)
sched_like_count = gr.Number(
label="每轮点赞数量", value=config.get("sched_like_count", 5), minimum=1, maximum=15,
)
with gr.Group():
sched_fav_on = gr.Checkbox(
label="✅ 启用自动收藏", value=config.get("sched_fav_on", True),
)
with gr.Row():
sched_fav_min = gr.Number(
label="收藏最小间隔(分钟)", value=config.get("sched_fav_min", 12), minimum=3,
)
sched_fav_max = gr.Number(
label="收藏最大间隔(分钟)", value=config.get("sched_fav_max", 35), minimum=5,
)
sched_fav_count = gr.Number(
label="每轮收藏数量", value=config.get("sched_fav_count", 3), minimum=1, maximum=10,
)
with gr.Group():
sched_reply_on = gr.Checkbox(
label="✅ 启用自动回复评论", value=config.get("sched_reply_on", True),
)
with gr.Row():
sched_r_min = gr.Number(
label="回复最小间隔(分钟)", value=config.get("sched_r_min", 20), minimum=5,
)
sched_r_max = gr.Number(
label="回复最大间隔(分钟)", value=config.get("sched_r_max", 60), minimum=10,
)
sched_reply_max = gr.Number(
label="每轮最多回复条数", value=config.get("sched_reply_max", 3), minimum=1, maximum=10,
)
with gr.Group():
sched_publish_on = gr.Checkbox(
label="✅ 启用自动发布", value=config.get("sched_publish_on", True),
)
with gr.Row():
sched_p_min = gr.Number(
label="发布最小间隔(分钟)", value=config.get("sched_p_min", 60), minimum=30,
)
sched_p_max = gr.Number(
label="发布最大间隔(分钟)", value=config.get("sched_p_max", 180), minimum=60,
)
# ── 第3行: 回复 + 发布 ──
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("##### 💌 自动回复")
sched_reply_on = gr.Checkbox(
label="启用", value=config.get("sched_reply_on", True),
)
with gr.Row():
sched_r_min = gr.Number(
label="最小间隔(分钟)", value=config.get("sched_r_min", 20), minimum=5,
)
sched_r_max = gr.Number(
label="最大间隔(分钟)", value=config.get("sched_r_max", 60), minimum=10,
)
sched_reply_max = gr.Number(
label="每轮最多回复", value=config.get("sched_reply_max", 3), minimum=1, maximum=10,
)
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("##### 🚀 自动发布")
sched_publish_on = gr.Checkbox(
label="启用", value=config.get("sched_publish_on", True),
)
with gr.Row():
sched_p_min = gr.Number(
label="最小间隔(分钟)", value=config.get("sched_p_min", 60), minimum=30,
)
sched_p_max = gr.Number(
label="最大间隔(分钟)", value=config.get("sched_p_max", 180), minimum=60,
)
with gr.Row():
btn_start_sched = gr.Button(
@@ -761,7 +838,7 @@ def build_app(cfg: "ConfigManager", analytics: "AnalyticsService") -> gr.Blocks:
gr.Markdown("#### 📋 运行日志")
with gr.Row():
btn_refresh_log = gr.Button("🔄 刷新日志", size="sm")
btn_clear_log = gr.Button("🗑️ 清空日志", size="sm")
btn_clear_log = gr.Button("🗑️ 清空日志", size="sm", variant="stop")
btn_refresh_stats = gr.Button("📊 刷新统计", size="sm")
auto_log_display = gr.TextArea(
label="自动化运行日志",
@@ -775,98 +852,64 @@ def build_app(cfg: "ConfigManager", analytics: "AnalyticsService") -> gr.Blocks:
value=_get_stats_summary(),
)
# -------- Tab 8: 内容排期 📅 --------
with gr.Tab("📅 内容排期"):
# -------- Tab 8: 🔐 账号登录 --------
with gr.Tab("🔐 账号登录"):
gr.Markdown(
"### 📅 内容排期日历 + 发布队列\n"
"> 批量生成内容 → 预览审核 → 排期定时 → 自动发布,内容创作全流程管控\n\n"
"**工作流**: 生成内容 → 📝草稿 → ✅审核通过 → 🕐排期/立即发布 → 🚀自动发布"
"### 小红书账号登录\n"
"> 扫码登录后自动获取 xsec_token,配合用户 ID 即可使用所有功能"
)
with gr.Row():
# ===== 左栏: 生成 & 队列控制 =====
with gr.Column(scale=1):
gr.Markdown("#### 🔧 批量生成到队列")
queue_gen_topics = gr.Textbox(
label="主题池 (逗号分隔,随人设自动切换)",
value=", ".join(get_persona_topics(config.get("persona", ""))),
placeholder="会从池中随机选取,切换人设自动更新",
gr.Markdown(
"**操作步骤:**\n"
"1. 确保 MCP 服务已启动\n"
"2. 点击「获取登录二维码」→ 用小红书 App 扫码\n"
"3. 点击「检查登录状态」→ 自动获取并保存 xsec_token\n"
"4. 首次使用请填写你的用户 ID 并点击保存\n\n"
"⚠️ 登录后不要在其他网页端登录同一账号,否则会被踢出"
)
with gr.Row():
queue_gen_count = gr.Number(
label="生成数量", value=config.get("queue_gen_count", 3), minimum=1, maximum=10,
)
queue_gen_schedule = gr.Textbox(
label="排期时间 (可选)",
placeholder="如 2026-02-10 18:00:00,留空=仅草稿",
)
btn_queue_generate = gr.Button(
"📝 批量生成 → 加入队列", variant="primary", size="lg",
btn_get_qrcode = gr.Button(
"📱 获取登录二维码", variant="primary", size="lg",
)
queue_gen_result = gr.Markdown("")
btn_check_login = gr.Button(
"🔍 检查登录状态 (自动获取 Token)",
variant="secondary", size="lg",
)
btn_logout = gr.Button(
"🚪 退出登录 (重新扫码)",
variant="stop", size="lg",
)
login_status = gr.Markdown("🔄 等待操作...")
gr.Markdown("---")
gr.Markdown("#### ⚙️ 队列处理器")
queue_processor_status = gr.Markdown(
value=queue_get_status(),
gr.Markdown(
"#### 📌 我的账号信息\n"
"> **注意**: 小红书号 ≠ 用户 ID\n"
"> - **小红书号 (redId)**: 如 `18688457507`,是你在 App 个人页看到的\n"
"> - **用户 ID (userId)**: 如 `5a695db6e8ac2b72e8af2a53`,24位十六进制字符串\n\n"
"💡 **如何获取 userId?**\n"
"1. 用浏览器打开你的小红书主页\n"
"2. 网址格式为: `xiaohongshu.com/user/profile/xxxxxxxx`\n"
"3. `profile/` 后面的就是你的 userId"
)
with gr.Row():
btn_queue_start = gr.Button(
"▶️ 启动队列处理", variant="primary",
)
btn_queue_stop = gr.Button(
"⏹️ 停止队列处理", variant="stop",
)
queue_processor_result = gr.Markdown("")
login_user_id = gr.Textbox(
label="我的用户 ID (24位 userId, 非小红书号)",
value=config.get("my_user_id", ""),
placeholder="如: 5a695db6e8ac2b72e8af2a53",
)
login_xsec_token = gr.Textbox(
label="xsec_token (登录后自动获取)",
value=config.get("xsec_token", ""),
interactive=False,
)
btn_save_uid = gr.Button(
"💾 保存用户 ID", variant="secondary",
)
save_uid_status = gr.Markdown("")
gr.Markdown("---")
gr.Markdown("#### 🔍 操作单个队列项")
queue_item_id = gr.Textbox(
label="队列项 ID", placeholder="输入 # 号,如 1",
)
with gr.Row():
btn_queue_preview = gr.Button("👁️ 预览", size="sm")
btn_queue_approve = gr.Button("✅ 通过", size="sm", variant="primary")
btn_queue_reject = gr.Button("🚫 拒绝", size="sm")
with gr.Row():
btn_queue_publish_now = gr.Button("🚀 立即发布", size="sm", variant="primary")
btn_queue_retry = gr.Button("🔄 重试", size="sm")
btn_queue_delete = gr.Button("🗑️ 删除", size="sm", variant="stop")
queue_schedule_time = gr.Textbox(
label="排期时间 (审核通过时可指定)",
placeholder="如 2026-02-10 20:00:00,留空=立即待发布",
)
btn_queue_batch_approve = gr.Button(
"✅ 批量通过所有草稿", variant="secondary",
)
queue_op_result = gr.Markdown("")
# ===== 右栏: 队列列表 & 日历 =====
with gr.Column(scale=2):
gr.Markdown("#### 📋 发布队列")
with gr.Row():
queue_filter = gr.Dropdown(
label="状态筛选",
choices=["全部"] + list(STATUS_LABELS.values()),
value="全部",
)
btn_queue_refresh = gr.Button("🔄 刷新", size="sm")
queue_table = gr.Markdown(
value=queue_format_table(),
label="队列列表",
)
gr.Markdown("---")
gr.Markdown("#### 📅 排期日历")
queue_calendar = gr.Markdown(
value=queue_format_calendar(),
label="日历视图",
)
gr.Markdown("---")
gr.Markdown("#### 👁️ 内容预览")
queue_preview_display = gr.Markdown(
value="*选择队列项 ID 后点击预览*",
with gr.Column(scale=1):
qr_image = gr.Image(
label="扫码登录", height=350, width=350,
)
# ==================================================
+14 -4
View File
@@ -50,7 +50,7 @@ def build_tab(
with gr.Row():
# ---- 左栏:输入 ----
with gr.Column(scale=1):
with gr.Column(scale=3):
gr.Markdown("### 💡 构思")
topic = gr.Textbox(label="笔记主题", placeholder="例如:优衣库早春穿搭")
style = gr.Dropdown(
@@ -75,10 +75,17 @@ def build_tab(
)
steps = gr.Slider(8, 50, value=config.get("sd_steps", 20), step=1, label="步数")
cfg_scale = gr.Slider(1, 15, value=config.get("sd_cfg_scale", 5.5), step=0.5, label="CFG Scale")
enhance_level = gr.Slider(
0.0, 2.0,
value=config.get("enhance_level", 1.0),
step=0.1,
label="美化强度",
info="0=关闭 1=默认 2=强化(锐化+皮肤校色+通透感)",
)
btn_gen_img = gr.Button("🎨 第二步:生成图片", variant="primary")
# ---- 中栏:文案编辑 ----
with gr.Column(scale=1):
with gr.Column(scale=4):
gr.Markdown("### 📝 文案编辑")
res_title = gr.Textbox(
label="标题",
@@ -97,7 +104,7 @@ def build_tab(
)
# ---- 右栏:预览 & 发布 ----
with gr.Column(scale=1):
with gr.Column(scale=3):
gr.Markdown("### 🖼️ 视觉预览")
gallery = gr.Gallery(label="AI 生成图片", columns=2, height=300)
local_images = gr.File(
@@ -138,11 +145,13 @@ def build_tab(
steps.change(fn=lambda s: fn_cfg_set("sd_steps", s), inputs=[steps], outputs=[])
cfg_scale.change(fn=lambda c: fn_cfg_set("sd_cfg_scale", c), inputs=[cfg_scale], outputs=[])
neg_prompt.change(fn=lambda n: fn_cfg_set("sd_negative_prompt", n), inputs=[neg_prompt], outputs=[])
enhance_level.change(fn=lambda v: fn_cfg_set("enhance_level", v), inputs=[enhance_level], outputs=[])
btn_gen_img.click(
fn=fn_gen_img,
inputs=[sd_url, res_prompt, neg_prompt, sd_model, steps, cfg_scale,
face_swap_toggle, face_image_preview, quality_mode, persona],
face_swap_toggle, face_image_preview, quality_mode, persona,
enhance_level],
outputs=[gallery, state_images, status_bar],
)
@@ -169,4 +178,5 @@ def build_tab(
"steps": steps,
"cfg_scale": cfg_scale,
"neg_prompt": neg_prompt,
"enhance_level": enhance_level,
}