""" ui/app.py Gradio 应用界面构建函数 — 包含全部 Tab UI 组件和事件绑定 """ import os import platform import logging import gradio as gr from services.config_manager import ConfigManager from services.sd_service import SDService, DEFAULT_NEGATIVE, FACE_IMAGE_PATH, SD_PRESET_NAMES, get_sd_preset, SD_MODEL_PROFILES from services.analytics_service import AnalyticsService from ui.tab_create import build_tab from services.connection import ( connect_llm, add_llm_provider, remove_llm_provider, on_provider_selected, connect_sd, on_sd_model_change, check_mcp_status, get_login_qrcode, logout_xhs, check_login, save_my_user_id, upload_face_image, load_saved_face_image, ) from services.persona import ( DEFAULT_PERSONAS, RANDOM_PERSONA_LABEL, DEFAULT_TOPICS, DEFAULT_STYLES, get_persona_topics, get_persona_keywords, on_persona_changed, ) from services.hotspot import ( search_hotspots, analyze_and_suggest, generate_from_hotspot, fetch_proactive_notes, on_proactive_note_selected, get_last_analysis, feed_hotspot_to_engine, ) from services.engagement import ( load_note_for_comment, ai_generate_comment, send_comment, fetch_my_notes, on_my_note_selected, fetch_my_note_comments, ai_reply_comment, send_reply, ) from services.profile import fetch_my_profile from services.scheduler import ( _auto_log, get_auto_log, get_scheduler_status, start_scheduler, stop_scheduler, analytics_collect_data, analytics_calculate_weights, analytics_llm_deep_analysis, analytics_get_report, analytics_get_weighted_topics, _auto_comment_with_log, _auto_like_with_log, _auto_favorite_with_log, _auto_publish_with_log, _auto_reply_with_log, start_learn_scheduler, stop_learn_scheduler, start_hotspot_collector, stop_hotspot_collector, _get_stats_summary, ) from services.queue_ops import ( generate_to_queue, queue_refresh_table, queue_refresh_calendar, queue_preview_item, queue_approve_item, queue_reject_item, queue_delete_item, queue_retry_item, queue_publish_now, queue_start_processor, queue_stop_processor, queue_get_status, queue_batch_approve, queue_generate_and_refresh, queue_format_table, queue_format_calendar, ) from services.autostart import is_autostart_enabled, toggle_autostart from services.content import generate_copy, generate_images, one_click_export, publish_to_xhs, batch_generate_copy from services.publish_queue import PublishQueue, STATUS_LABELS from services.topic_engine import TopicEngine logger = logging.getLogger("autobot") # ========== 新增回调: 选题推荐 / 批量创作 / 图文匹配 ========== def _fn_topic_recommend(model_name): """获取智能选题推荐列表(自动注入热点数据)""" analytics = AnalyticsService() engine = TopicEngine(analytics) return feed_hotspot_to_engine(engine) def _fn_batch_generate(model_name, topics, style, sd_model_name, persona_text, template_name): """批量生成文案并入草稿队列""" pq = PublishQueue("xhs_workspace") return batch_generate_copy( model=model_name, topics=topics, style=style, sd_model_name=sd_model_name, persona_text=persona_text, template_name=template_name, publish_queue=pq, ) def _fn_evaluate_match(model_name, content, sd_prompt): """评估图文匹配度""" from services.llm_service import LLMService from services.connection import _get_llm_config api_key, base_url, _ = _get_llm_config() if not api_key: return {"match_score": -1, "suggestions": [], "skipped": True} svc = LLMService(api_key, base_url, model_name) return svc.evaluate_image_text_match(content, sd_prompt) _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: """构建并返回 Gradio 应用(包含所有 Tab 和事件绑定)""" config = cfg.all with gr.Blocks( title="小红书 AI 爆文工坊 V2.0", css=_GRADIO_CSS, ) as app: gr.Markdown( "# 🍒 小红书 AI 爆文生产工坊 V2.0\n" "> 灵感 → 文案 → 绘图 → 发布 → 运营,一站式全闭环" ) # 全局状态 state_search_result = gr.State("") # ============ Tab 页面 ============ # ⚙️ 配置 Tab 声明在最前以确保共享组件变量先于 build_tab() 定义 # selected=1 令「✨ 内容创作」为默认激活 Tab with gr.Tabs(selected=1): # -------- Tab 0: ⚙️ 配置 -------- with gr.Tab("⚙️ 配置"): 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", 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, ) 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 "⚪ 未启用", ) # -------- Tab 1: ✨ 内容创作(默认激活)-------- _tab1 = build_tab( config=config, styles=DEFAULT_STYLES, sd_preset_names=SD_PRESET_NAMES, default_negative=DEFAULT_NEGATIVE, llm_model=llm_model, sd_model=sd_model, sd_url=sd_url, persona=persona, status_bar=status_bar, face_swap_toggle=face_swap_toggle, face_image_preview=face_image_preview, mcp_url=mcp_url, fn_gen_copy=generate_copy, fn_gen_img=generate_images, fn_export=one_click_export, fn_publish=publish_to_xhs, fn_get_sd_preset=get_sd_preset, fn_cfg_set=cfg.set, fn_cfg_update=cfg.update, fn_batch_generate=_fn_batch_generate, fn_topic_recommend=_fn_topic_recommend, fn_evaluate_match=_fn_evaluate_match, ) res_title = _tab1["res_title"] res_content = _tab1["res_content"] res_prompt = _tab1["res_prompt"] res_tags = _tab1["res_tags"] quality_mode = _tab1["quality_mode"] steps = _tab1["steps"] cfg_scale = _tab1["cfg_scale"] neg_prompt = _tab1["neg_prompt"] # -------- 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,留空=仅草稿", ) queue_auto_schedule = gr.Checkbox( label="🤖 自动排期(基于历史数据智能分配最优发布时段)", value=False, ) 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("#### � 推荐发布时段") def _get_time_weights_display(): a = AnalyticsService() tw = a.get_time_weights() if not tw: return "暂无时段数据" sorted_tw = sorted(tw.items(), key=lambda x: x[1] if isinstance(x[1], (int, float)) else x[1].get("weight", 0), reverse=True) lines = ["| 时段 | 权重 | 推荐度 |"] lines.append("|------|:----:|--------|") for slot, info in sorted_tw: w = info if isinstance(info, (int, float)) else info.get("weight", 0) bar = "█" * (w // 10) + "░" * (10 - w // 10) lines.append(f"| {slot} | {w} | {bar} |") return "\n".join(lines) queue_time_recommend = gr.Markdown( value=_get_time_weights_display(), ) gr.Markdown("---") gr.Markdown("#### �👁️ 内容预览") queue_preview_display = gr.Markdown( value="*选择队列项 ID 后点击预览*", ) # -------- Tab 3: 🔥 热点探测 -------- with gr.Tab("🔥 热点探测"): gr.Markdown("### 搜索热门内容 → AI 分析趋势 → 一键借鉴创作") with gr.Row(): with gr.Column(scale=1): hot_keyword = gr.Textbox( label="搜索关键词", placeholder="如:春季穿搭", ) hot_sort = gr.Dropdown( ["综合", "最新", "最多点赞", "最多评论", "最多收藏"], label="排序", value="综合", ) btn_search = gr.Button("🔍 搜索", variant="primary") search_status = gr.Markdown("") with gr.Column(scale=2): search_output = gr.TextArea( label="搜索结果", lines=12, interactive=False, ) with gr.Row(): btn_analyze = gr.Button("🧠 AI 分析热点趋势", variant="primary") analysis_status = gr.Markdown("") analysis_output = gr.Markdown(label="分析报告") with gr.Row(): hot_topic_dropdown = gr.Dropdown( choices=[], label="💡 推荐选题 (从分析结果自动填充)", interactive=True, allow_custom_value=True, ) topic_from_hot = gr.Textbox( label="选择/输入创作选题", placeholder="基于分析选一个方向", ) with gr.Row(): hot_style = gr.Dropdown( ["好物种草", "干货教程", "情绪共鸣", "生活Vlog", "测评避雷"], label="风格", value="好物种草", ) btn_gen_from_hot = gr.Button("✨ 基于热点生成文案", variant="primary") with gr.Row(): hot_title = gr.Textbox(label="生成的标题", interactive=True) hot_content = gr.TextArea(label="生成的正文", lines=8, interactive=True) with gr.Row(): hot_prompt = gr.TextArea(label="绘图提示词", lines=3, interactive=True) hot_tags = gr.Textbox(label="标签", interactive=True) hot_gen_status = gr.Markdown("") btn_sync_to_create = gr.Button( "📋 同步到「内容创作」Tab → 绘图 & 发布", variant="primary", ) gr.Markdown("---") gr.Markdown("#### 🔥 热点自动采集") gr.Markdown("> 后台定时搜索热门内容并 AI 分析,自动更新热点缓存供选题使用") with gr.Row(): hotspot_collect_keywords = gr.Textbox( label="采集关键词 (逗号分隔)", value=", ".join(config.get("hotspot_auto_collect", {}).get("keywords", ["穿搭", "美妆", "好物"])), placeholder="穿搭, 美妆, 好物", ) hotspot_collect_interval = gr.Number( label="采集间隔 (小时)", value=config.get("hotspot_auto_collect", {}).get("interval_hours", 4), minimum=1, maximum=48, ) with gr.Row(): btn_hotspot_collect_start = gr.Button( "▶ 启动自动采集", variant="primary", size="sm", ) btn_hotspot_collect_stop = gr.Button( "⏹ 停止", variant="stop", size="sm", ) hotspot_collect_status = gr.Markdown("⚪ 热点自动采集未启动") # -------- Tab 3: 评论管家 -------- with gr.Tab("💬 评论管家"): gr.Markdown("### 智能评论管理:主动评论引流 & 自动回复粉丝") with gr.Tabs(): # ======== 子 Tab A: 主动评论他人 ======== with gr.Tab("✍️ 主动评论引流"): gr.Markdown( "> **流程**:搜索/浏览笔记 → 选择目标 → 加载内容 → " "AI 分析笔记+已有评论自动生成高质量评论 → 一键发送" ) # 笔记选择器 with gr.Row(): pro_keyword = gr.Textbox( label="🔍 搜索关键词 (留空则获取推荐)", placeholder="穿搭、美食、旅行…", ) btn_pro_fetch = gr.Button("🔍 获取笔记", variant="primary") with gr.Row(): pro_selector = gr.Dropdown( label="📋 选择目标笔记", choices=[], interactive=True, ) pro_fetch_status = gr.Markdown("") # 隐藏字段 with gr.Row(): pro_feed_id = gr.Textbox(label="笔记 ID", interactive=False) pro_xsec_token = gr.Textbox(label="xsec_token", interactive=False) pro_title = gr.Textbox(label="标题", interactive=False) # 加载内容 & AI 分析 btn_pro_load = gr.Button("📖 加载笔记内容", variant="secondary") pro_load_status = gr.Markdown("") with gr.Row(): with gr.Column(scale=1): pro_content = gr.TextArea( label="📄 笔记正文摘要", lines=8, interactive=False, ) with gr.Column(scale=1): pro_comments = gr.TextArea( label="💬 已有评论", lines=8, interactive=False, ) # 隐藏: 完整文本 pro_full_text = gr.Textbox(visible=False) gr.Markdown("---") with gr.Row(): with gr.Column(scale=1): btn_pro_ai = gr.Button( "🤖 AI 智能生成评论", variant="primary", size="lg", ) pro_ai_status = gr.Markdown("") with gr.Column(scale=2): pro_comment_text = gr.TextArea( label="✏️ 评论内容 (可手动修改)", lines=3, interactive=True, placeholder="点击左侧按钮自动生成,也可手动编写", ) with gr.Row(): btn_pro_send = gr.Button("📩 发送评论", variant="primary") pro_send_status = gr.Markdown("") # ======== 子 Tab B: 回复我的评论 ======== with gr.Tab("💌 回复粉丝评论"): gr.Markdown( "> **流程**:选择我的笔记 → 加载评论 → " "粘贴要回复的评论 → AI 生成回复 → 一键发送" ) # 笔记选择器 (自动用已保存的 userId 获取) with gr.Row(): btn_my_fetch = gr.Button("🔍 获取我的笔记", variant="primary") with gr.Row(): my_selector = gr.Dropdown( label="📋 选择我的笔记", choices=[], interactive=True, ) my_fetch_status = gr.Markdown("") with gr.Row(): my_feed_id = gr.Textbox(label="笔记 ID", interactive=False) my_xsec_token = gr.Textbox(label="xsec_token", interactive=False) my_title = gr.Textbox(label="笔记标题", interactive=False) btn_my_load_comments = gr.Button("📥 加载评论", variant="primary") my_comment_status = gr.Markdown("") my_comments_display = gr.TextArea( label="📋 粉丝评论列表", lines=12, interactive=False, ) gr.Markdown("---") gr.Markdown("#### 📝 回复评论") with gr.Row(): with gr.Column(scale=1): my_target_comment = gr.TextArea( label="要回复的评论内容", lines=3, placeholder="从上方评论列表中复制粘贴要回复的评论", ) btn_my_ai_reply = gr.Button( "🤖 AI 生成回复", variant="secondary", ) my_reply_gen_status = gr.Markdown("") with gr.Column(scale=1): my_reply_content = gr.TextArea( label="回复内容 (可修改)", lines=3, interactive=True, ) btn_my_send_reply = gr.Button( "📩 发送回复", variant="primary", ) my_reply_status = gr.Markdown("") # -------- Tab 5: 📊 数据看板 -------- with gr.Tab("📊 数据看板"): gr.Markdown( "### 我的账号数据看板\n" "> 用户 ID 和 xsec_token 从「账号登录」自动获取,直接点击加载即可" ) with gr.Row(): with gr.Column(scale=1): data_user_id = gr.Textbox( label="我的用户 ID (自动填充)", value=config.get("my_user_id", ""), interactive=True, ) data_xsec_token = gr.Textbox( label="xsec_token (自动填充)", value=config.get("xsec_token", ""), interactive=True, ) btn_refresh_token = gr.Button( "🔄 刷新 Token", variant="secondary", ) btn_load_my_data = gr.Button( "📊 加载我的数据", variant="primary", size="lg", ) data_status = gr.Markdown("") with gr.Column(scale=2): profile_card = gr.Markdown( value="*等待加载...*", label="账号概览", ) gr.Markdown("---") gr.Markdown("### 📈 数据可视化") with gr.Row(): with gr.Column(scale=1): chart_interact = gr.Plot(label="📊 核心指标") with gr.Column(scale=2): chart_notes = gr.Plot(label="❤ 笔记点赞排行") gr.Markdown("---") notes_detail = gr.Markdown( value="*加载数据后显示笔记明细表格*", label="笔记数据明细", ) # -------- Tab 6: 智能学习 -------- with gr.Tab("🧠 智能学习"): gr.Markdown( "### 🧠 智能内容学习引擎\n" "> 自动分析已发布笔记的表现,学习哪些内容受欢迎,用权重指导未来创作\n\n" "**工作流程**: 采集数据 → 计算权重 → AI 深度分析 → 自动优化创作\n\n" "💡 启用后,自动发布将优先生成高权重主题的内容" ) with gr.Row(): # 左栏: 数据采集 & 权重计算 with gr.Column(scale=1): gr.Markdown("#### 📊 数据采集") learn_user_id = gr.Textbox( label="用户 ID", value=config.get("my_user_id", ""), interactive=True, ) learn_xsec_token = gr.Textbox( label="xsec_token", value=config.get("xsec_token", ""), interactive=True, ) btn_learn_collect = gr.Button( "📊 采集笔记数据", variant="primary", size="lg", ) learn_collect_status = gr.Markdown("") gr.Markdown("---") gr.Markdown("#### ⚖️ 权重计算") btn_learn_calc = gr.Button( "⚖️ 计算内容权重", variant="primary", size="lg", ) learn_calc_status = gr.Markdown("") gr.Markdown("---") gr.Markdown("#### 🤖 AI 深度分析") gr.Markdown("> 用 LLM 分析笔记数据,找出内容规律,生成策略建议") btn_learn_ai = gr.Button( "🧠 AI 深度分析", variant="primary", size="lg", ) gr.Markdown("---") gr.Markdown("#### ⏰ 定时自动学习") gr.Markdown("> 每隔 N 小时自动采集数据 + 计算权重 + AI 分析") learn_interval = gr.Number( label="学习间隔 (小时)", value=config.get("learn_interval", 6), minimum=1, maximum=48, ) with gr.Row(): btn_learn_start = gr.Button( "▶ 启动定时学习", variant="primary", size="sm", ) btn_learn_stop = gr.Button( "⏹ 停止", variant="stop", size="sm", ) learn_sched_status = gr.Markdown("⚪ 定时学习未启动") gr.Markdown("---") gr.Markdown("#### 🎯 加权主题预览") gr.Markdown("> 当前权重最高的主题 (自动发布会优先选择)") btn_show_topics = gr.Button("🔄 刷新加权主题", size="sm") learn_weighted_topics = gr.Textbox( label="加权主题池 (权重从高到低)", value=analytics.get_weighted_topics_display() or "暂无权重数据", interactive=False, lines=2, ) learn_use_weights = gr.Checkbox( label="🧠 自动发布时使用智能权重 (推荐)", value=cfg.get("use_smart_weights", True), interactive=True, ) # 右栏: 分析报告 with gr.Column(scale=2): gr.Markdown("#### 📋 智能学习报告") learn_report = gr.Markdown( value=analytics.generate_report(), label="分析报告", ) gr.Markdown("---") learn_ai_report = gr.Markdown( value="*点击「AI 深度分析」生成*", label="AI 深度分析报告", ) # -------- Tab 7: 自动运营 -------- with gr.Tab("🤖 自动运营"): gr.Markdown( "### 🤖 无人值守自动化运营\n" "> 一键评论引流 + 一键点赞 + 一键收藏 + 一键回复 + 一键发布 + 随机定时全自动\n\n" "⚠️ **注意**: 请确保已连接 LLM、SD WebUI 和 MCP 服务" ) persona_pool_hint = gr.Markdown( value=f"🎭 当前人设池: **{config.get('persona', '随机')[:20]}** → 关键词/主题池已匹配", ) with gr.Row(): # 左栏: 一键操作 with gr.Column(scale=1): gr.Markdown("#### 💬 一键智能评论") gr.Markdown( "> 自动搜索高赞笔记 → AI 分析内容 → 生成评论 → 发送\n" "每次随机选关键词搜索,从结果中随机选笔记" ) auto_comment_keywords = gr.Textbox( label="评论关键词池 (逗号分隔,随人设自动切换)", value=", ".join(get_persona_keywords(config.get("persona", ""))), placeholder="关键词1, 关键词2, ... (切换人设自动更新)", ) btn_auto_comment = gr.Button( "💬 一键评论 (单次)", variant="primary", size="lg", ) auto_comment_result = gr.Markdown("") gr.Markdown("---") gr.Markdown("#### 👍 一键自动点赞") gr.Markdown( "> 搜索笔记 → 随机选择多篇 → 依次点赞\n" "提升账号活跃度,无需 LLM" ) auto_like_count = gr.Number( label="单次点赞数量", value=config.get("auto_like_count", 5), minimum=1, maximum=20, ) btn_auto_like = gr.Button( "👍 一键点赞 (单次)", variant="primary", size="lg", ) auto_like_result = gr.Markdown("") gr.Markdown("---") gr.Markdown("#### ⭐ 一键自动收藏") gr.Markdown( "> 搜索笔记 → 随机选择多篇 → 依次收藏\n" "提升账号活跃度,与点赞互补" ) auto_fav_count = gr.Number( label="单次收藏数量", value=config.get("auto_fav_count", 3), minimum=1, maximum=15, ) btn_auto_favorite = gr.Button( "⭐ 一键收藏 (单次)", variant="primary", size="lg", ) auto_favorite_result = gr.Markdown("") gr.Markdown("---") gr.Markdown("#### 💌 一键自动回复") gr.Markdown( "> 扫描我的所有笔记 → 找到粉丝评论 → AI 生成回复 → 逐条发送\n" "自动跳过自己的评论,模拟真人间隔回复" ) auto_reply_max = gr.Number( label="单次最多回复条数", value=config.get("auto_reply_max", 5), minimum=1, maximum=20, ) btn_auto_reply = gr.Button( "💌 一键回复 (单次)", variant="primary", size="lg", ) auto_reply_result = gr.Markdown("") gr.Markdown("---") gr.Markdown("#### 🚀 一键智能发布") gr.Markdown( "> 随机选主题+风格 → AI 生成文案 → SD 生成图片 → 自动发布" ) auto_publish_topics = gr.Textbox( label="主题池 (逗号分隔,随人设自动切换)", value=", ".join(get_persona_topics(config.get("persona", ""))), placeholder="主题会从池中随机选取,切换人设自动更新", ) btn_auto_publish = gr.Button( "🚀 一键发布 (单次)", variant="primary", size="lg", ) auto_publish_result = gr.Markdown("") # 右栏: 定时自动化 (2列卡片网格) with gr.Column(scale=2): gr.Markdown("#### ⏰ 随机定时自动化") gr.Markdown( "> 设置时间间隔后启动,系统将在随机时间自动执行 · 模拟真人操作节奏,降低被检测风险" ) sched_status = gr.Markdown("⚪ **调度器未运行**") # ── 第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, ) # ── 第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, ) # ── 第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( "▶️ 启动定时", variant="primary", size="lg", ) btn_stop_sched = gr.Button( "⏹️ 停止定时", variant="stop", size="lg", ) sched_result = gr.Markdown("") gr.Markdown("---") with gr.Row(): with gr.Column(scale=2): gr.Markdown("#### 📋 运行日志") with gr.Row(): btn_refresh_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="自动化运行日志", value="📋 暂无日志\n\n💡 执行操作后日志将在此显示", lines=15, interactive=False, ) with gr.Column(scale=1): gr.Markdown("#### 📊 今日运营统计") auto_stats_display = gr.Markdown( value=_get_stats_summary(), ) # -------- Tab 8: 🔐 账号登录 -------- 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, ) # ================================================== # 事件绑定 # ================================================== # ---- 全局设置: LLM 提供商管理 ---- btn_connect_llm.click( fn=connect_llm, inputs=[llm_provider], outputs=[llm_model, status_bar], ) llm_provider.change( fn=on_provider_selected, inputs=[llm_provider], outputs=[llm_provider_info], ) btn_add_provider.click( fn=add_llm_provider, inputs=[new_provider_name, new_provider_key, new_provider_url], outputs=[llm_provider, provider_mgmt_status], ) btn_del_provider.click( fn=remove_llm_provider, inputs=[llm_provider], outputs=[llm_provider, provider_mgmt_status], ) btn_connect_sd.click( fn=connect_sd, inputs=[sd_url], outputs=[sd_model, status_bar, sd_model_info], ) sd_model.change( fn=on_sd_model_change, inputs=[sd_model], outputs=[sd_model_info], ) btn_check_mcp.click( fn=check_mcp_status, inputs=[mcp_url], outputs=[status_bar], ) # ---- 头像/换脸管理 ---- btn_save_face.click( fn=upload_face_image, inputs=[face_image_input], outputs=[face_image_preview, face_status], ) # ---- Tab 2: 热点探测 ---- btn_search.click( fn=search_hotspots, inputs=[hot_keyword, hot_sort, mcp_url], outputs=[search_status, search_output], ) # 搜索结果同步到 state search_output.change( fn=lambda x: x, inputs=[search_output], outputs=[state_search_result], ) btn_analyze.click( fn=analyze_and_suggest, inputs=[llm_model, hot_keyword, search_output], outputs=[analysis_status, analysis_output, topic_from_hot, hot_topic_dropdown], ) # 推荐选题下拉:选中后写入选题输入框 hot_topic_dropdown.change( fn=lambda x: x or "", inputs=[hot_topic_dropdown], outputs=[topic_from_hot], ) btn_gen_from_hot.click( fn=generate_from_hotspot, inputs=[llm_model, topic_from_hot, hot_style, search_output, sd_model, persona], outputs=[hot_title, hot_content, hot_prompt, hot_tags, hot_gen_status], ) # 同步热点文案到内容创作 Tab btn_sync_to_create.click( fn=lambda t, c, p, tg: (t, c, p, tg, "✅ 已同步到「内容创作」,可切换 Tab 继续绘图和发布"), inputs=[hot_title, hot_content, hot_prompt, hot_tags], outputs=[res_title, res_content, res_prompt, res_tags, status_bar], ) # 热点自动采集启停 btn_hotspot_collect_start.click( fn=start_hotspot_collector, inputs=[hotspot_collect_keywords, hotspot_collect_interval, mcp_url, llm_model], outputs=[hotspot_collect_status], ) btn_hotspot_collect_stop.click( fn=stop_hotspot_collector, inputs=[], outputs=[hotspot_collect_status], ) # ---- Tab 3: 评论管家 ---- # == 子 Tab A: 主动评论引流 == btn_pro_fetch.click( fn=fetch_proactive_notes, inputs=[pro_keyword, mcp_url], outputs=[pro_selector, pro_fetch_status], ) pro_selector.change( fn=on_proactive_note_selected, inputs=[pro_selector], outputs=[pro_feed_id, pro_xsec_token, pro_title], ) btn_pro_load.click( fn=load_note_for_comment, inputs=[pro_feed_id, pro_xsec_token, mcp_url], outputs=[pro_load_status, pro_content, pro_comments, pro_full_text], ) btn_pro_ai.click( fn=ai_generate_comment, inputs=[llm_model, persona, pro_title, pro_content, pro_comments], outputs=[pro_comment_text, pro_ai_status], ) btn_pro_send.click( fn=send_comment, inputs=[pro_feed_id, pro_xsec_token, pro_comment_text, mcp_url], outputs=[pro_send_status], ) # == 子 Tab B: 回复粉丝评论 == btn_my_fetch.click( fn=fetch_my_notes, inputs=[mcp_url], outputs=[my_selector, my_fetch_status], ) my_selector.change( fn=on_my_note_selected, inputs=[my_selector], outputs=[my_feed_id, my_xsec_token, my_title], ) btn_my_load_comments.click( fn=fetch_my_note_comments, inputs=[my_feed_id, my_xsec_token, mcp_url], outputs=[my_comment_status, my_comments_display], ) btn_my_ai_reply.click( fn=ai_reply_comment, inputs=[llm_model, persona, my_title, my_target_comment], outputs=[my_reply_content, my_reply_gen_status], ) btn_my_send_reply.click( fn=send_reply, inputs=[my_feed_id, my_xsec_token, my_reply_content, mcp_url], outputs=[my_reply_status], ) # ---- Tab 4: 账号登录 ---- btn_get_qrcode.click( fn=get_login_qrcode, inputs=[mcp_url], outputs=[qr_image, login_status], ) btn_check_login.click( fn=check_login, inputs=[mcp_url], outputs=[login_status, login_user_id, login_xsec_token], ) btn_logout.click( fn=logout_xhs, inputs=[mcp_url], outputs=[login_status], ) btn_save_uid.click( fn=save_my_user_id, inputs=[login_user_id], outputs=[save_uid_status], ) # ---- Tab 5: 数据看板 ---- def refresh_xsec_token(mcp_url): token = _auto_fetch_xsec_token(mcp_url) if token: cfg.set("xsec_token", token) return gr.update(value=token), "✅ Token 已刷新" return gr.update(value=cfg.get("xsec_token", "")), "❌ 刷新失败,请确认已登录" btn_refresh_token.click( fn=refresh_xsec_token, inputs=[mcp_url], outputs=[data_xsec_token, data_status], ) btn_load_my_data.click( fn=fetch_my_profile, inputs=[data_user_id, data_xsec_token, mcp_url], outputs=[data_status, profile_card, chart_interact, chart_notes, notes_detail], ) # ---- Tab 6: 智能学习 ---- btn_learn_collect.click( fn=analytics_collect_data, inputs=[mcp_url, learn_user_id, learn_xsec_token], outputs=[learn_collect_status], ) btn_learn_calc.click( fn=analytics_calculate_weights, inputs=[], outputs=[learn_calc_status, learn_report], ) btn_learn_ai.click( fn=analytics_llm_deep_analysis, inputs=[llm_model], outputs=[learn_ai_report], ) btn_learn_start.click( fn=start_learn_scheduler, inputs=[mcp_url, learn_user_id, learn_xsec_token, llm_model, learn_interval], outputs=[learn_sched_status], ) btn_learn_stop.click( fn=stop_learn_scheduler, inputs=[], outputs=[learn_sched_status], ) btn_show_topics.click( fn=analytics_get_weighted_topics, inputs=[], outputs=[learn_weighted_topics], ) learn_use_weights.change( fn=lambda v: cfg.set("use_smart_weights", v) or ("✅ 智能权重已启用" if v else "⚪ 智能权重已关闭"), inputs=[learn_use_weights], outputs=[learn_sched_status], ) # ---- Tab 7: 自动运营 ---- # 人设切换 → 联动更新评论关键词池、主题池和队列主题池(同时保存到配置) persona.change( fn=on_persona_changed, inputs=[persona], outputs=[auto_comment_keywords, auto_publish_topics, persona_pool_hint, queue_gen_topics], ) btn_auto_comment.click( fn=_auto_comment_with_log, inputs=[auto_comment_keywords, mcp_url, llm_model, persona], outputs=[auto_comment_result, auto_log_display], ) btn_auto_like.click( fn=_auto_like_with_log, inputs=[auto_comment_keywords, auto_like_count, mcp_url], outputs=[auto_like_result, auto_log_display], ) btn_auto_favorite.click( fn=_auto_favorite_with_log, inputs=[auto_comment_keywords, auto_fav_count, mcp_url], outputs=[auto_favorite_result, auto_log_display], ) btn_auto_reply.click( fn=_auto_reply_with_log, inputs=[auto_reply_max, mcp_url, llm_model, persona], outputs=[auto_reply_result, auto_log_display], ) btn_auto_publish.click( fn=_auto_publish_with_log, inputs=[auto_publish_topics, mcp_url, sd_url, sd_model, llm_model, persona, quality_mode, face_swap_toggle], outputs=[auto_publish_result, auto_log_display], ) btn_start_sched.click( fn=start_scheduler, inputs=[sched_comment_on, sched_publish_on, sched_reply_on, sched_like_on, sched_fav_on, sched_c_min, sched_c_max, sched_p_min, sched_p_max, sched_r_min, sched_r_max, sched_reply_max, sched_l_min, sched_l_max, sched_like_count, sched_fav_min, sched_fav_max, sched_fav_count, sched_start_hour, sched_end_hour, auto_comment_keywords, auto_publish_topics, mcp_url, sd_url, sd_model, llm_model, persona, quality_mode, face_swap_toggle], outputs=[sched_result], ) btn_stop_sched.click( fn=stop_scheduler, inputs=[], outputs=[sched_result], ) btn_refresh_log.click( fn=lambda: (get_auto_log(), get_scheduler_status()), inputs=[], outputs=[auto_log_display, sched_status], ) btn_clear_log.click( fn=lambda: (_auto_log.clear() or "📋 日志已清空"), inputs=[], outputs=[auto_log_display], ) btn_refresh_stats.click( fn=lambda: (get_scheduler_status(), _get_stats_summary()), inputs=[], outputs=[sched_status, auto_stats_display], ) # ---- 全局设置参数自动保存 ---- # persona的保存已整合到on_persona_changed函数中 mcp_url.change(fn=lambda v: cfg.set("mcp_url", v), inputs=[mcp_url], outputs=[]) sd_url.change(fn=lambda v: cfg.set("sd_url", v), inputs=[sd_url], outputs=[]) llm_model.change(fn=lambda v: cfg.set("model", v), inputs=[llm_model], outputs=[]) # ---- 自动运营参数自动保存 ---- sched_comment_on.change(fn=lambda v: cfg.set("sched_comment_on", v), inputs=[sched_comment_on], outputs=[]) sched_like_on.change(fn=lambda v: cfg.set("sched_like_on", v), inputs=[sched_like_on], outputs=[]) sched_fav_on.change(fn=lambda v: cfg.set("sched_fav_on", v), inputs=[sched_fav_on], outputs=[]) sched_reply_on.change(fn=lambda v: cfg.set("sched_reply_on", v), inputs=[sched_reply_on], outputs=[]) sched_publish_on.change(fn=lambda v: cfg.set("sched_publish_on", v), inputs=[sched_publish_on], outputs=[]) sched_c_min.change(fn=lambda v: cfg.set("sched_c_min", v), inputs=[sched_c_min], outputs=[]) sched_c_max.change(fn=lambda v: cfg.set("sched_c_max", v), inputs=[sched_c_max], outputs=[]) sched_l_min.change(fn=lambda v: cfg.set("sched_l_min", v), inputs=[sched_l_min], outputs=[]) sched_l_max.change(fn=lambda v: cfg.set("sched_l_max", v), inputs=[sched_l_max], outputs=[]) sched_like_count.change(fn=lambda v: cfg.set("sched_like_count", v), inputs=[sched_like_count], outputs=[]) sched_fav_min.change(fn=lambda v: cfg.set("sched_fav_min", v), inputs=[sched_fav_min], outputs=[]) sched_fav_max.change(fn=lambda v: cfg.set("sched_fav_max", v), inputs=[sched_fav_max], outputs=[]) sched_fav_count.change(fn=lambda v: cfg.set("sched_fav_count", v), inputs=[sched_fav_count], outputs=[]) sched_r_min.change(fn=lambda v: cfg.set("sched_r_min", v), inputs=[sched_r_min], outputs=[]) sched_r_max.change(fn=lambda v: cfg.set("sched_r_max", v), inputs=[sched_r_max], outputs=[]) sched_reply_max.change(fn=lambda v: cfg.set("sched_reply_max", v), inputs=[sched_reply_max], outputs=[]) sched_p_min.change(fn=lambda v: cfg.set("sched_p_min", v), inputs=[sched_p_min], outputs=[]) sched_p_max.change(fn=lambda v: cfg.set("sched_p_max", v), inputs=[sched_p_max], outputs=[]) sched_start_hour.change(fn=lambda v: cfg.set("sched_start_hour", v), inputs=[sched_start_hour], outputs=[]) sched_end_hour.change(fn=lambda v: cfg.set("sched_end_hour", v), inputs=[sched_end_hour], outputs=[]) auto_like_count.change(fn=lambda v: cfg.set("auto_like_count", v), inputs=[auto_like_count], outputs=[]) auto_fav_count.change(fn=lambda v: cfg.set("auto_fav_count", v), inputs=[auto_fav_count], outputs=[]) auto_reply_max.change(fn=lambda v: cfg.set("auto_reply_max", v), inputs=[auto_reply_max], outputs=[]) # ---- 智能学习参数自动保存 ---- learn_interval.change(fn=lambda v: cfg.set("learn_interval", v), inputs=[learn_interval], outputs=[]) # ---- 内容排期参数自动保存 ---- queue_gen_count.change(fn=lambda v: cfg.set("queue_gen_count", v), inputs=[queue_gen_count], outputs=[]) # ---- 开机自启 ---- autostart_toggle.change( fn=toggle_autostart, inputs=[autostart_toggle], outputs=[autostart_status], ) # ---- Tab 8: 内容排期 ---- # 队列主题池的更新已整合到 Tab 7 的 persona.change() 事件中 # 批量生成到队列 btn_queue_generate.click( fn=queue_generate_and_refresh, inputs=[queue_gen_topics, sd_url, sd_model, llm_model, persona, quality_mode, face_swap_toggle, queue_gen_count, queue_gen_schedule, queue_auto_schedule], outputs=[queue_gen_result, queue_table, queue_calendar, queue_processor_status], ) # 刷新队列 btn_queue_refresh.click( fn=lambda sf: (queue_refresh_table(sf), queue_refresh_calendar(), queue_get_status()), inputs=[queue_filter], outputs=[queue_table, queue_calendar, queue_processor_status], ) queue_filter.change( fn=lambda sf: queue_refresh_table(sf), inputs=[queue_filter], outputs=[queue_table], ) # 单项操作 btn_queue_preview.click( fn=queue_preview_item, inputs=[queue_item_id], outputs=[queue_preview_display], ) btn_queue_approve.click( fn=lambda iid, st: (queue_approve_item(iid, st), queue_format_table(), queue_format_calendar()), inputs=[queue_item_id, queue_schedule_time], outputs=[queue_op_result, queue_table, queue_calendar], ) btn_queue_reject.click( fn=lambda iid: (queue_reject_item(iid), queue_format_table()), inputs=[queue_item_id], outputs=[queue_op_result, queue_table], ) btn_queue_delete.click( fn=lambda iid: (queue_delete_item(iid), queue_format_table(), queue_format_calendar()), inputs=[queue_item_id], outputs=[queue_op_result, queue_table, queue_calendar], ) btn_queue_retry.click( fn=lambda iid: (queue_retry_item(iid), queue_format_table()), inputs=[queue_item_id], outputs=[queue_op_result, queue_table], ) btn_queue_publish_now.click( fn=lambda iid: (queue_publish_now(iid), queue_format_table(), queue_format_calendar(), queue_get_status()), inputs=[queue_item_id], outputs=[queue_op_result, queue_table, queue_calendar, queue_processor_status], ) btn_queue_batch_approve.click( fn=lambda sf: (queue_batch_approve(sf), queue_format_table(), queue_format_calendar()), inputs=[queue_filter], outputs=[queue_op_result, queue_table, queue_calendar], ) # 队列处理器 btn_queue_start.click( fn=lambda: (queue_start_processor(), queue_get_status()), inputs=[], outputs=[queue_processor_result, queue_processor_status], ) btn_queue_stop.click( fn=lambda: (queue_stop_processor(), queue_get_status()), inputs=[], outputs=[queue_processor_result, queue_processor_status], ) # ---- 启动时自动加载全局设置 ---- def load_global_settings(): """页面加载时恢复全局设置""" config = cfg.all providers = cfg.get_llm_provider_names() active_llm = cfg.get("active_llm", "") persona_val = config.get("persona", RANDOM_PERSONA_LABEL) # 初始化LLM提供商信息显示 provider_info = on_provider_selected(active_llm) if active_llm else "*选择提供商后显示详情*" # 获取人设对应的关键词和主题池 keywords = get_persona_keywords(persona_val) topics = get_persona_topics(persona_val) keywords_str = ", ".join(keywords) topics_str = ", ".join(topics) # 尝试连接 SD 并获取模型列表 sd_models = [] sd_status = "🔄 等待连接..." sd_model_val = None try: svc = SDService(config["sd_url"]) ok, msg = svc.check_connection() if ok: sd_models = svc.get_models() sd_model_val = sd_models[0] if sd_models else None sd_status = f"✅ {msg}" except Exception: pass # 图片生成参数 quality_mode_val = config.get("quality_mode", "标准 (约1分钟)") steps_val = config.get("sd_steps", 20) cfg_scale_val = config.get("sd_cfg_scale", 5.5) neg_prompt_val = config.get("sd_negative_prompt", DEFAULT_NEGATIVE) # 自动运营参数 return ( gr.update(choices=providers, value=active_llm), # llm_provider provider_info, # llm_provider_info config["mcp_url"], # mcp_url config["sd_url"], # sd_url persona_val, # persona gr.update(choices=sd_models, value=sd_model_val), # sd_model sd_status, # status_bar keywords_str, # auto_comment_keywords topics_str, # auto_publish_topics topics_str, # queue_gen_topics quality_mode_val, # quality_mode steps_val, # steps cfg_scale_val, # cfg_scale neg_prompt_val, # neg_prompt config.get("auto_like_count", 5), # auto_like_count config.get("auto_fav_count", 3), # auto_fav_count config.get("auto_reply_max", 5), # auto_reply_max config.get("sched_comment_on", True), # sched_comment_on config.get("sched_like_on", True), # sched_like_on config.get("sched_fav_on", True), # sched_fav_on config.get("sched_reply_on", True), # sched_reply_on config.get("sched_publish_on", True), # sched_publish_on config.get("sched_c_min", 15), # sched_c_min config.get("sched_c_max", 45), # sched_c_max config.get("sched_l_min", 10), # sched_l_min config.get("sched_l_max", 30), # sched_l_max config.get("sched_like_count", 5), # sched_like_count config.get("sched_fav_min", 12), # sched_fav_min config.get("sched_fav_max", 35), # sched_fav_max config.get("sched_fav_count", 3), # sched_fav_count config.get("sched_r_min", 20), # sched_r_min config.get("sched_r_max", 60), # sched_r_max config.get("sched_reply_max", 3), # sched_reply_max config.get("sched_p_min", 60), # sched_p_min config.get("sched_p_max", 180), # sched_p_max config.get("sched_start_hour", 8), # sched_start_hour config.get("sched_end_hour", 23), # sched_end_hour config.get("learn_interval", 6), # learn_interval config.get("queue_gen_count", 3), # queue_gen_count config.get("use_smart_weights", True), # learn_use_weights ) app.load( fn=load_global_settings, inputs=[], outputs=[ llm_provider, llm_provider_info, mcp_url, sd_url, persona, sd_model, status_bar, auto_comment_keywords, auto_publish_topics, queue_gen_topics, quality_mode, steps, cfg_scale, neg_prompt, auto_like_count, auto_fav_count, auto_reply_max, sched_comment_on, sched_like_on, sched_fav_on, sched_reply_on, sched_publish_on, sched_c_min, sched_c_max, sched_l_min, sched_l_max, sched_like_count, sched_fav_min, sched_fav_max, sched_fav_count, sched_r_min, sched_r_max, sched_reply_max, sched_p_min, sched_p_max, sched_start_hour, sched_end_hour, learn_interval, queue_gen_count, learn_use_weights, ], ) # ================================================== return app