✨ feat(automation): 新增无人值守自动化运营模块
- 新增完整的自动化运营模块,包含一键评论、一键发布和定时调度功能 - 【一键评论】自动搜索高赞笔记,AI分析内容并生成个性化评论进行引流 - 【一键发布】随机选择主题和风格,AI生成文案,SD生成图片并自动发布到小红书 - 【定时调度】支持随机时间间隔的自动评论和发布,模拟真人操作节奏降低风险 - 新增自动化日志系统,实时记录操作状态和结果 - 在UI中新增“自动运营”标签页,提供完整的配置和操作界面 📝 docs(prompts): 优化SD提示词生成模板 - 更新文案生成提示词,适配JuggernautXL模型并优化质量描述 - 新增详细的质量词、光影、风格、构图和细节要求 - 移除括号权重语法,改为英文逗号分隔的描述方式 - 优化反向提示词,针对SDXL模型进行适配和增强 🔧 chore(config): 更新安全令牌配置 - 更新config.json中的xsec_token为新的安全令牌值 ⚡️ perf(sd): 优化Stable Diffusion服务参数 - 增加SD服务超时时间至900秒,适应高质量图片生成 - 优化文生图和图生图的默认参数,适配JuggernautXL模型 - 新增采样器和调度器参数配置,提升图片生成质量 - 优化默认反向提示词,针对SDXL模型进行专门优化
This commit is contained in:
parent
88dfc09e2a
commit
d27ffe94f4
@ -21,5 +21,5 @@
|
||||
"base_url": "https://wolfai.top/v1"
|
||||
}
|
||||
],
|
||||
"xsec_token": "ABS1TagQqhCpZmeNlq0VoCfNEyI6Q83GJzjTGJvzEAq5I="
|
||||
"xsec_token": "ABdAEbqP9ScgelmyolJxsnpCr_e645SCpnub2dLZJc4Ck="
|
||||
}
|
||||
@ -26,7 +26,13 @@ PROMPT_COPYWRITING = """
|
||||
3. 结尾必须有 5 个以上相关话题标签(#)。
|
||||
|
||||
【绘图 Prompt】:
|
||||
生成对应的 Stable Diffusion 英文提示词,强调:masterpiece, best quality, 8k, soft lighting, ins style。
|
||||
生成对应的 Stable Diffusion 英文提示词,适配 JuggernautXL 模型,强调:
|
||||
- 质量词:masterpiece, best quality, ultra detailed, 8k uhd, high resolution
|
||||
- 光影:natural lighting, soft shadows, studio lighting, golden hour 等(根据场景选择)
|
||||
- 风格:photorealistic, cinematic, editorial photography, ins style
|
||||
- 构图:dynamic angle, depth of field, bokeh 等
|
||||
- 细节:detailed skin texture, sharp focus, vivid colors
|
||||
注意:不要使用括号权重语法,直接用英文逗号分隔描述。
|
||||
|
||||
返回 JSON 格式:
|
||||
{"title": "...", "content": "...", "sd_prompt": "...", "tags": ["标签1", "标签2", ...]}
|
||||
@ -105,7 +111,11 @@ PROMPT_COPY_WITH_REFERENCE = """
|
||||
3. 结尾有 5 个以上话题标签(#)。
|
||||
|
||||
【绘图 Prompt】:
|
||||
生成 Stable Diffusion 英文提示词。
|
||||
生成 Stable Diffusion 英文提示词,适配 JuggernautXL 模型:
|
||||
- 必含质量词:masterpiece, best quality, ultra detailed, 8k uhd
|
||||
- 风格:photorealistic, cinematic, editorial photography
|
||||
- 光影和细节:natural lighting, sharp focus, vivid colors, detailed skin texture
|
||||
- 用英文逗号分隔,不用括号权重语法。
|
||||
|
||||
返回 JSON 格式:
|
||||
{{"title": "...", "content": "...", "sd_prompt": "...", "tags": ["标签1", "标签2", ...]}}
|
||||
|
||||
433
main.py
433
main.py
@ -10,6 +10,9 @@ import time
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import threading
|
||||
import random
|
||||
from datetime import datetime
|
||||
from PIL import Image
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
@ -881,6 +884,306 @@ def fetch_my_profile(user_id, xsec_token, mcp_url):
|
||||
return f"❌ {e}", "", None, None, None
|
||||
|
||||
|
||||
# ==================================================
|
||||
# 自动化运营模块
|
||||
# ==================================================
|
||||
|
||||
# 自动化状态
|
||||
_auto_running = threading.Event()
|
||||
_auto_thread: threading.Thread | None = None
|
||||
_auto_log: list[str] = []
|
||||
|
||||
DEFAULT_TOPICS = [
|
||||
"春季穿搭", "通勤穿搭", "约会穿搭", "显瘦穿搭", "平价好物",
|
||||
"护肤心得", "妆容教程", "好物分享", "生活好物", "减脂餐分享",
|
||||
"居家好物", "收纳技巧", "咖啡探店", "书单推荐", "旅行攻略",
|
||||
]
|
||||
|
||||
DEFAULT_STYLES = ["好物种草", "干货教程", "情绪共鸣", "生活Vlog", "测评避雷"]
|
||||
|
||||
DEFAULT_COMMENT_KEYWORDS = [
|
||||
"穿搭", "美食", "护肤", "好物推荐", "旅行", "生活日常", "减脂",
|
||||
]
|
||||
|
||||
|
||||
def _auto_log_append(msg: str):
|
||||
"""记录自动化日志"""
|
||||
ts = datetime.now().strftime("%H:%M:%S")
|
||||
entry = f"[{ts}] {msg}"
|
||||
_auto_log.append(entry)
|
||||
if len(_auto_log) > 500:
|
||||
_auto_log[:] = _auto_log[-300:]
|
||||
logger.info("[自动化] %s", msg)
|
||||
|
||||
|
||||
def _auto_comment_with_log(keywords_str, mcp_url, model, persona_text):
|
||||
"""一键评论 + 同步刷新日志"""
|
||||
msg = auto_comment_once(keywords_str, mcp_url, model, persona_text)
|
||||
return msg, get_auto_log()
|
||||
|
||||
|
||||
def auto_comment_once(keywords_str, mcp_url, model, persona_text):
|
||||
"""一键评论:自动搜索高赞笔记 → AI生成评论 → 发送"""
|
||||
try:
|
||||
keywords = [k.strip() for k in keywords_str.split(",") if k.strip()] if keywords_str else DEFAULT_COMMENT_KEYWORDS
|
||||
keyword = random.choice(keywords)
|
||||
_auto_log_append(f"🔍 搜索关键词: {keyword}")
|
||||
|
||||
client = get_mcp_client(mcp_url)
|
||||
|
||||
# 搜索高赞笔记
|
||||
entries = client.search_feeds_parsed(keyword, sort_by="最多点赞")
|
||||
if not entries:
|
||||
_auto_log_append("⚠️ 搜索无结果,尝试推荐列表")
|
||||
entries = client.list_feeds_parsed()
|
||||
if not entries:
|
||||
return "❌ 未找到任何笔记"
|
||||
|
||||
# 过滤掉自己的笔记
|
||||
my_uid = cfg.get("my_user_id", "")
|
||||
if my_uid:
|
||||
filtered = [e for e in entries if e.get("user_id") != my_uid]
|
||||
if filtered:
|
||||
entries = filtered
|
||||
|
||||
# 从前10个中随机选择
|
||||
target = random.choice(entries[:min(10, len(entries))])
|
||||
feed_id = target["feed_id"]
|
||||
xsec_token = target["xsec_token"]
|
||||
title = target.get("title", "未知")
|
||||
_auto_log_append(f"🎯 选中: {title[:30]} (@{target.get('author', '未知')})")
|
||||
|
||||
if not feed_id or not xsec_token:
|
||||
return "❌ 笔记缺少必要参数 (feed_id/xsec_token)"
|
||||
|
||||
# 模拟浏览延迟
|
||||
time.sleep(random.uniform(2, 5))
|
||||
|
||||
# 加载笔记详情
|
||||
result = client.get_feed_detail(feed_id, xsec_token, load_all_comments=True)
|
||||
if "error" in result:
|
||||
return f"❌ 加载笔记失败: {result['error']}"
|
||||
|
||||
full_text = result.get("text", "")
|
||||
if "评论" in full_text:
|
||||
parts = full_text.split("评论", 1)
|
||||
content_part = parts[0].strip()[:600]
|
||||
comments_part = ("评论" + parts[1])[:800] if len(parts) > 1 else ""
|
||||
else:
|
||||
content_part = full_text[:500]
|
||||
comments_part = ""
|
||||
|
||||
# AI 生成评论
|
||||
api_key, base_url, _ = _get_llm_config()
|
||||
if not api_key:
|
||||
return "❌ LLM 未配置,请先在全局设置中配置提供商"
|
||||
|
||||
svc = LLMService(api_key, base_url, model)
|
||||
comment = svc.generate_proactive_comment(
|
||||
persona_text, title, content_part, comments_part
|
||||
)
|
||||
_auto_log_append(f"💬 生成评论: {comment[:60]}...")
|
||||
|
||||
# 随机等待后发送
|
||||
time.sleep(random.uniform(3, 8))
|
||||
result = client.post_comment(feed_id, xsec_token, comment)
|
||||
resp_text = result.get("text", "")
|
||||
_auto_log_append(f"📡 MCP 响应: {resp_text[:200]}")
|
||||
|
||||
if "error" in result:
|
||||
_auto_log_append(f"❌ 评论发送失败: {result['error']}")
|
||||
return f"❌ 评论发送失败: {result['error']}"
|
||||
|
||||
# 检查是否真正成功
|
||||
if "成功" not in resp_text and "success" not in resp_text.lower() and not resp_text:
|
||||
_auto_log_append(f"⚠️ 评论可能未成功,MCP 原始响应: {result}")
|
||||
return f"⚠️ 评论状态不确定,请手动检查\nMCP 响应: {resp_text[:300]}\n📝 评论: {comment}"
|
||||
|
||||
_auto_log_append(f"✅ 评论已发送到「{title[:20]}」")
|
||||
return f"✅ 已评论「{title[:25]}」\n📝 评论: {comment}\n\n💡 小红书可能有内容审核延迟,请稍等 1-2 分钟后查看"
|
||||
|
||||
except Exception as e:
|
||||
_auto_log_append(f"❌ 一键评论异常: {e}")
|
||||
return f"❌ 评论失败: {e}"
|
||||
|
||||
|
||||
def _auto_publish_with_log(topics_str, mcp_url, sd_url_val, sd_model_name, model):
|
||||
"""一键发布 + 同步刷新日志"""
|
||||
msg = auto_publish_once(topics_str, mcp_url, sd_url_val, sd_model_name, model)
|
||||
return msg, get_auto_log()
|
||||
|
||||
|
||||
def auto_publish_once(topics_str, mcp_url, sd_url_val, sd_model_name, model):
|
||||
"""一键发布:自动生成文案 → 生成图片 → 发布到小红书"""
|
||||
try:
|
||||
topics = [t.strip() for t in topics_str.split(",") if t.strip()] if topics_str else DEFAULT_TOPICS
|
||||
topic = random.choice(topics)
|
||||
style = random.choice(DEFAULT_STYLES)
|
||||
_auto_log_append(f"📝 主题: {topic} | 风格: {style}")
|
||||
|
||||
# 生成文案
|
||||
api_key, base_url, _ = _get_llm_config()
|
||||
if not api_key:
|
||||
return "❌ LLM 未配置,请先在全局设置中配置提供商"
|
||||
|
||||
svc = LLMService(api_key, base_url, model)
|
||||
data = svc.generate_copy(topic, style)
|
||||
title = (data.get("title", "") or "")[:20]
|
||||
content = data.get("content", "")
|
||||
sd_prompt = data.get("sd_prompt", "")
|
||||
tags = data.get("tags", [])
|
||||
|
||||
if not title:
|
||||
return "❌ 文案生成失败:无标题"
|
||||
_auto_log_append(f"📄 文案: {title}")
|
||||
|
||||
# 生成图片
|
||||
if not sd_url_val or not sd_model_name:
|
||||
return "❌ SD WebUI 未连接或未选择模型,请先在全局设置中连接"
|
||||
|
||||
sd_svc = SDService(sd_url_val)
|
||||
images = sd_svc.txt2img(prompt=sd_prompt, model=sd_model_name)
|
||||
if not images:
|
||||
return "❌ 图片生成失败:没有返回图片"
|
||||
_auto_log_append(f"🎨 已生成 {len(images)} 张图片")
|
||||
|
||||
# 保存图片到临时目录
|
||||
temp_dir = os.path.join(OUTPUT_DIR, "_temp_publish")
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
image_paths = []
|
||||
ts = int(time.time())
|
||||
for idx, img in enumerate(images):
|
||||
if isinstance(img, Image.Image):
|
||||
path = os.path.abspath(os.path.join(temp_dir, f"auto_{ts}_{idx}.png"))
|
||||
img.save(path)
|
||||
image_paths.append(path)
|
||||
|
||||
if not image_paths:
|
||||
return "❌ 图片保存失败"
|
||||
|
||||
# 发布到小红书
|
||||
client = get_mcp_client(mcp_url)
|
||||
result = client.publish_content(
|
||||
title=title, content=content, images=image_paths, tags=tags
|
||||
)
|
||||
if "error" in result:
|
||||
_auto_log_append(f"❌ 发布失败: {result['error']}")
|
||||
return f"❌ 发布失败: {result['error']}"
|
||||
|
||||
_auto_log_append(f"🚀 发布成功: {title}")
|
||||
return f"✅ 发布成功!\n📌 标题: {title}\n{result.get('text', '')}"
|
||||
|
||||
except Exception as e:
|
||||
_auto_log_append(f"❌ 一键发布异常: {e}")
|
||||
return f"❌ 发布失败: {e}"
|
||||
|
||||
|
||||
def _scheduler_loop(comment_enabled, publish_enabled,
|
||||
comment_min, comment_max, publish_min, publish_max,
|
||||
keywords, topics, mcp_url, sd_url_val, sd_model_name,
|
||||
model, persona_text):
|
||||
"""后台定时调度循环"""
|
||||
_auto_log_append("🤖 自动化调度器已启动")
|
||||
|
||||
# 首次执行的随机延迟
|
||||
next_comment = time.time() + random.randint(10, 60)
|
||||
next_publish = time.time() + random.randint(30, 120)
|
||||
|
||||
while _auto_running.is_set():
|
||||
now = time.time()
|
||||
|
||||
# 自动评论
|
||||
if comment_enabled and now >= next_comment:
|
||||
try:
|
||||
_auto_log_append("--- 🔄 执行自动评论 ---")
|
||||
msg = auto_comment_once(keywords, mcp_url, model, persona_text)
|
||||
_auto_log_append(msg)
|
||||
except Exception as e:
|
||||
_auto_log_append(f"❌ 自动评论异常: {e}")
|
||||
interval = random.randint(int(comment_min) * 60, int(comment_max) * 60)
|
||||
next_comment = time.time() + interval
|
||||
_auto_log_append(f"⏰ 下次评论: {interval // 60} 分钟后")
|
||||
|
||||
# 自动发布
|
||||
if publish_enabled and now >= next_publish:
|
||||
try:
|
||||
_auto_log_append("--- 🔄 执行自动发布 ---")
|
||||
msg = auto_publish_once(topics, mcp_url, sd_url_val, sd_model_name, model)
|
||||
_auto_log_append(msg)
|
||||
except Exception as e:
|
||||
_auto_log_append(f"❌ 自动发布异常: {e}")
|
||||
interval = random.randint(int(publish_min) * 60, int(publish_max) * 60)
|
||||
next_publish = time.time() + interval
|
||||
_auto_log_append(f"⏰ 下次发布: {interval // 60} 分钟后")
|
||||
|
||||
# 每5秒检查一次停止信号
|
||||
for _ in range(5):
|
||||
if not _auto_running.is_set():
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
_auto_log_append("🛑 自动化调度器已停止")
|
||||
|
||||
|
||||
def start_scheduler(comment_on, publish_on, c_min, c_max, p_min, p_max,
|
||||
keywords, topics, mcp_url, sd_url_val, sd_model_name,
|
||||
model, persona_text):
|
||||
"""启动定时自动化"""
|
||||
global _auto_thread
|
||||
if _auto_running.is_set():
|
||||
return "⚠️ 调度器已在运行中,请先停止"
|
||||
|
||||
if not comment_on and not publish_on:
|
||||
return "❌ 请至少启用一项自动化功能(评论或发布)"
|
||||
|
||||
api_key, _, _ = _get_llm_config()
|
||||
if not api_key:
|
||||
return "❌ LLM 未配置,请先在全局设置中配置提供商"
|
||||
|
||||
_auto_running.set()
|
||||
_auto_thread = threading.Thread(
|
||||
target=_scheduler_loop,
|
||||
args=(comment_on, publish_on,
|
||||
c_min, c_max, p_min, p_max,
|
||||
keywords, topics, mcp_url, sd_url_val, sd_model_name,
|
||||
model, persona_text),
|
||||
daemon=True,
|
||||
)
|
||||
_auto_thread.start()
|
||||
|
||||
parts = []
|
||||
if comment_on:
|
||||
parts.append(f"评论 (每 {int(c_min)}-{int(c_max)} 分钟)")
|
||||
if publish_on:
|
||||
parts.append(f"发布 (每 {int(p_min)}-{int(p_max)} 分钟)")
|
||||
|
||||
_auto_log_append(f"调度器已启动: {' + '.join(parts)}")
|
||||
return f"✅ 自动化已启动 🟢\n任务: {' | '.join(parts)}\n\n💡 点击「刷新日志」查看实时进度"
|
||||
|
||||
|
||||
def stop_scheduler():
|
||||
"""停止定时自动化"""
|
||||
if not _auto_running.is_set():
|
||||
return "⚠️ 调度器未在运行"
|
||||
_auto_running.clear()
|
||||
_auto_log_append("⏹️ 收到停止信号,等待当前任务完成...")
|
||||
return "🛑 调度器停止中...当前任务完成后将完全停止"
|
||||
|
||||
|
||||
def get_auto_log():
|
||||
"""获取自动化运行日志"""
|
||||
if not _auto_log:
|
||||
return "📋 暂无日志\n\n💡 点击「一键评论」「一键发布」或启动定时后日志将在此显示"
|
||||
return "\n".join(_auto_log[-80:])
|
||||
|
||||
|
||||
def get_scheduler_status():
|
||||
"""获取调度器运行状态"""
|
||||
if _auto_running.is_set():
|
||||
return "🟢 **调度器运行中**"
|
||||
return "⚪ **调度器未运行**"
|
||||
|
||||
|
||||
# ==================================================
|
||||
# UI 构建
|
||||
# ==================================================
|
||||
@ -1294,6 +1597,101 @@ with gr.Blocks(
|
||||
label="笔记数据明细",
|
||||
)
|
||||
|
||||
# -------- Tab 6: 自动运营 --------
|
||||
with gr.Tab("🤖 自动运营"):
|
||||
gr.Markdown(
|
||||
"### 🤖 无人值守自动化运营\n"
|
||||
"> 一键评论引流 + 一键内容发布 + 随机定时全自动\n\n"
|
||||
"⚠️ **注意**: 请确保已连接 LLM、SD WebUI 和 MCP 服务"
|
||||
)
|
||||
|
||||
with gr.Row():
|
||||
# 左栏: 一键操作
|
||||
with gr.Column(scale=1):
|
||||
gr.Markdown("#### 💬 一键智能评论")
|
||||
gr.Markdown(
|
||||
"> 自动搜索高赞笔记 → AI 分析内容 → 生成评论 → 发送\n"
|
||||
"每次随机选关键词搜索,从结果中随机选笔记"
|
||||
)
|
||||
auto_comment_keywords = gr.Textbox(
|
||||
label="评论关键词池 (逗号分隔)",
|
||||
value="穿搭, 美食, 护肤, 好物推荐, 旅行, 生活日常",
|
||||
placeholder="关键词1, 关键词2, ...",
|
||||
)
|
||||
btn_auto_comment = gr.Button(
|
||||
"💬 一键评论 (单次)", variant="primary", size="lg",
|
||||
)
|
||||
auto_comment_result = gr.Markdown("")
|
||||
|
||||
gr.Markdown("---")
|
||||
gr.Markdown("#### 🚀 一键智能发布")
|
||||
gr.Markdown(
|
||||
"> 随机选主题+风格 → AI 生成文案 → SD 生成图片 → 自动发布"
|
||||
)
|
||||
auto_publish_topics = gr.Textbox(
|
||||
label="主题池 (逗号分隔)",
|
||||
value="春季穿搭, 通勤穿搭, 显瘦穿搭, 平价好物, 护肤心得, 好物分享",
|
||||
placeholder="主题1, 主题2, ...",
|
||||
)
|
||||
btn_auto_publish = gr.Button(
|
||||
"🚀 一键发布 (单次)", variant="primary", size="lg",
|
||||
)
|
||||
auto_publish_result = gr.Markdown("")
|
||||
|
||||
# 右栏: 定时自动化
|
||||
with gr.Column(scale=1):
|
||||
gr.Markdown("#### ⏰ 随机定时自动化")
|
||||
gr.Markdown(
|
||||
"> 设置时间间隔后启动,系统将在随机时间自动执行\n"
|
||||
"> 模拟真人操作节奏,降低被检测风险"
|
||||
)
|
||||
sched_status = gr.Markdown("⚪ **调度器未运行**")
|
||||
|
||||
with gr.Group():
|
||||
sched_comment_on = gr.Checkbox(
|
||||
label="✅ 启用自动评论", value=True,
|
||||
)
|
||||
with gr.Row():
|
||||
sched_c_min = gr.Number(
|
||||
label="评论最小间隔(分钟)", value=15, minimum=5,
|
||||
)
|
||||
sched_c_max = gr.Number(
|
||||
label="评论最大间隔(分钟)", value=45, minimum=10,
|
||||
)
|
||||
|
||||
with gr.Group():
|
||||
sched_publish_on = gr.Checkbox(
|
||||
label="✅ 启用自动发布", value=True,
|
||||
)
|
||||
with gr.Row():
|
||||
sched_p_min = gr.Number(
|
||||
label="发布最小间隔(分钟)", value=60, minimum=30,
|
||||
)
|
||||
sched_p_max = gr.Number(
|
||||
label="发布最大间隔(分钟)", value=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("---")
|
||||
gr.Markdown("#### 📋 运行日志")
|
||||
with gr.Row():
|
||||
btn_refresh_log = gr.Button("🔄 刷新日志", size="sm")
|
||||
btn_clear_log = gr.Button("🗑️ 清空日志", size="sm")
|
||||
auto_log_display = gr.TextArea(
|
||||
label="自动化运行日志",
|
||||
value="📋 暂无日志\n\n💡 执行操作后日志将在此显示",
|
||||
lines=15,
|
||||
interactive=False,
|
||||
)
|
||||
|
||||
# ==================================================
|
||||
# 事件绑定
|
||||
# ==================================================
|
||||
@ -1482,6 +1880,41 @@ with gr.Blocks(
|
||||
outputs=[data_status, profile_card, chart_interact, chart_notes, notes_detail],
|
||||
)
|
||||
|
||||
# ---- Tab 6: 自动运营 ----
|
||||
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_publish.click(
|
||||
fn=_auto_publish_with_log,
|
||||
inputs=[auto_publish_topics, mcp_url, sd_url, sd_model, llm_model],
|
||||
outputs=[auto_publish_result, auto_log_display],
|
||||
)
|
||||
btn_start_sched.click(
|
||||
fn=start_scheduler,
|
||||
inputs=[sched_comment_on, sched_publish_on,
|
||||
sched_c_min, sched_c_max, sched_p_min, sched_p_max,
|
||||
auto_comment_keywords, auto_publish_topics,
|
||||
mcp_url, sd_url, sd_model, llm_model, persona],
|
||||
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],
|
||||
)
|
||||
|
||||
# ---- 启动时自动刷新 SD ----
|
||||
app.load(fn=connect_sd, inputs=[sd_url], outputs=[sd_model, status_bar])
|
||||
|
||||
|
||||
@ -10,13 +10,16 @@ from PIL import Image
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SD_TIMEOUT = 180 # 图片生成可能需要较长时间
|
||||
SD_TIMEOUT = 900 # 图片生成可能需要较长时间
|
||||
|
||||
# 默认反向提示词
|
||||
# 默认反向提示词(针对 JuggernautXL / SDXL 优化)
|
||||
DEFAULT_NEGATIVE = (
|
||||
"nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, "
|
||||
"extra digit, fewer digits, cropped, worst quality, low quality, "
|
||||
"normal quality, jpeg artifacts, signature, watermark, blurry"
|
||||
"nsfw, nudity, lowres, bad anatomy, bad hands, text, error, missing fingers, "
|
||||
"extra digit, fewer digits, cropped, worst quality, low quality, normal quality, "
|
||||
"jpeg artifacts, signature, watermark, blurry, deformed, mutated, disfigured, "
|
||||
"ugly, duplicate, morbid, mutilated, poorly drawn face, poorly drawn hands, "
|
||||
"extra limbs, fused fingers, too many fingers, long neck, username, "
|
||||
"out of frame, distorted, oversaturated, underexposed, overexposed"
|
||||
)
|
||||
|
||||
|
||||
@ -61,14 +64,16 @@ class SDService:
|
||||
prompt: str,
|
||||
negative_prompt: str = DEFAULT_NEGATIVE,
|
||||
model: str = None,
|
||||
steps: int = 25,
|
||||
cfg_scale: float = 7.0,
|
||||
width: int = 768,
|
||||
height: int = 1024,
|
||||
steps: int = 30,
|
||||
cfg_scale: float = 5.0,
|
||||
width: int = 832,
|
||||
height: int = 1216,
|
||||
batch_size: int = 2,
|
||||
seed: int = -1,
|
||||
sampler_name: str = "DPM++ 2M",
|
||||
scheduler: str = "Karras",
|
||||
) -> list[Image.Image]:
|
||||
"""文生图"""
|
||||
"""文生图(参数针对 JuggernautXL 优化)"""
|
||||
if model:
|
||||
self.switch_model(model)
|
||||
|
||||
@ -81,6 +86,8 @@ class SDService:
|
||||
"height": height,
|
||||
"batch_size": batch_size,
|
||||
"seed": seed,
|
||||
"sampler_name": sampler_name,
|
||||
"scheduler": scheduler,
|
||||
}
|
||||
|
||||
resp = requests.post(
|
||||
@ -101,11 +108,13 @@ class SDService:
|
||||
init_image: Image.Image,
|
||||
prompt: str,
|
||||
negative_prompt: str = DEFAULT_NEGATIVE,
|
||||
denoising_strength: float = 0.6,
|
||||
steps: int = 25,
|
||||
cfg_scale: float = 7.0,
|
||||
denoising_strength: float = 0.5,
|
||||
steps: int = 30,
|
||||
cfg_scale: float = 5.0,
|
||||
sampler_name: str = "DPM++ 2M",
|
||||
scheduler: str = "Karras",
|
||||
) -> list[Image.Image]:
|
||||
"""图生图(参考图修改)"""
|
||||
"""图生图(参数针对 JuggernautXL 优化)"""
|
||||
# 将 PIL Image 转为 base64
|
||||
buf = io.BytesIO()
|
||||
init_image.save(buf, format="PNG")
|
||||
@ -120,6 +129,8 @@ class SDService:
|
||||
"cfg_scale": cfg_scale,
|
||||
"width": init_image.width,
|
||||
"height": init_image.height,
|
||||
"sampler_name": sampler_name,
|
||||
"scheduler": scheduler,
|
||||
}
|
||||
|
||||
resp = requests.post(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user