✨ 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:
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user