ni / app
194130157a's picture
Create app
81b2625 verified
import os
import time
import requests
import gradio as gr
import re
import json
import zipfile
import random
import tempfile # 新增:用于多人隔离
import shutil # 新增:用于清理临时文件
from datetime import datetime
import concurrent.futures
# ==========================================
# 1. 系统配置 & 默认提示词
# ==========================================
YUNWU_API_KEY = "sk-crO3d0vpI1gKZRxwhgJ5TfyGKrvKAydummPmuPFmR5Obkbm8"
YUNWU_BASE_URL = "https://yunwu.ai"
MODEL_NAME = "gemini-3-pro-preview-thinking"
CONFIG_FILE = "prompt_config_v13_audience.json"
# --- 1. 选题策划提示词 (保留全量:12维度 + 双轨制 + 防抄袭) ---
DEFAULT_TOPIC_PROMPT = """# Role
你是一位精通“国学/中医/倪海厦风格”的YouTube顶级运营专家。你擅长“以日定题”,通过严密的黄历推演,为**50岁以上的中老年人群**量身定制点击率极高的爆款选题。
# Goal
我将只给你输入一个【日期】。
请你**现场推演**该日期的老黄历特征,从下方指定的 12 个维度中筛选出最具备爆款潜质的 10 个切入点,生成 10 组**【双轨制选题】**
# Dual-Track Output Protocol (双轨制输出协议)
对于每一个选题,你必须同时输出两个版本(严格遵循 JSON 格式):
1. **Version A (发布标题 - 给观众看)**
* **核心逻辑**:极度悬念、盲盒动作、话说一半!
* **禁忌**:严禁在标题里直接说出答案!不要说“不能吃辣”、“不能洗澡”。
* **强制句式**
* “出门千万别往**这个方位**走!”
* “枕头下务必放**这一物**!”
* “早上起床记得打开**这扇窗**!”
* “千万别穿**这2种颜色**!”
* **爆款公式****[警示前缀] + [日期] + [「日子定义」] + [盲盒微动作] + [极致后果]**
2. **Version B (写作指引 - 给AI写手看)**
* **核心逻辑**:极度直白、毫无保留!
* **任务**:必须明确指出 Version A 里的“盲盒”到底是什么。
* *示例*:如果 A 说“放这一物”,B 就要写“核心揭秘:放一枚红纸包的硬币,取火生土之意”。
* *示例*:如果 A 说“别去这个方位”,B 就要写“核心揭秘:别去正北方,今日三煞在北”。
# Zero-Shot Protocol (零样本逻辑推演协议)
**警告:本指令处于“绝对推演模式”!**
1. **禁止调用案例库**:严禁使用任何“通用的”、“以前用过的”动作或定义。
2. **100% 实时计算**:每一个标题里的“日子定义”和“禁忌动作”,必须是你根据**此时此刻**查询到的该日干支、神煞、五行,**现场计算**出来的结果。
# Step 1: 现场定义日子 (Day Naming Logic)
请先检索今日神煞,并**现场组合**出一个4-5字的“带引号”术语:
* **推演逻辑参考**
* 检测到吉星(天德/月德/天恩/龙德) -> 命名为:「天恩赐福日」、「龙德护身日」、「凤凰送财日」...
* 检测到凶星(大耗/小耗/五鬼/白虎) -> 命名为:「白虎开口日」、「小耗破财日」、「五鬼穿宫日」...
* *(注意:必须基于当日真实黄历,严禁瞎编“好日子”这种大白话)*
# Step 2: 现场推导动作 (Blind Box Action)
请根据今日的**五行生克****神煞方位**,推导出具体的“盲盒动作”:
* **推演逻辑参考**
* **方位推导**:今日凶煞在哪个方位?(如:三煞在北) -> 标题写:出门千万别往**正北方**走。
* **颜色推导**:今日五行喜什么?忌什么?(如:火克金) -> 标题写:千万别穿**白色**衣服。
* **物品推导**:今日冲什么生肖?(如:冲鸡) -> 标题写:家里有**属鸡**的,枕头下放个**铜鸡**
# Task (12维度全量覆盖 - 纯逻辑指令)
**请基于今日黄历的真实特征,遍历以下 12 个维度,选取最强烈的 10 个进行生成。**
**(注意:下方括号内是纯逻辑指令,没有具体名词,逼迫你必须现场思考!)**
1. **【财运类】** (逻辑:检测今日“财神”方位或“耗星”状态。若吉,推演“招财”微动作;若凶,推演“守财”微动作。重点关注:存款、钱包、硬币)
2. **【人际类】** (逻辑:检测今日是否有“口舌/是非/相刑”星。若有,推演“闭嘴/不争”的动作;若无,推演“聚会/贵人”动作)
3. **【居家类】** (逻辑:检测今日“胎神”占方或“动土”禁忌。推导家里哪个角落/家具绝对不能动,或者哪扇窗户必须开)
4. **【出行类】** (逻辑:检测今日“五鬼/死门/往亡”方位。推导“绝对不能去”的那个方向或场所,或者必须避开的时间段)
5. **【穿搭类】** (逻辑:计算今日天干地支的纳音五行喜忌。推导“最旺”和“最衰”的两种颜色或配饰材质)
6. **【物品类】** (逻辑:检测今日“冲煞”之物或五行。推导出一个“必须扔掉”或“必须买”的物品,如破碗、旧鞋、红纸)
7. **【子孙类】** (逻辑:检测今日“文昌”或“子息”星状态。推导利于孩子学业或健康的仪式,如摸头、吃某种食物)
8. **【职场类】** (逻辑:检测今日“官禄”或“驿马”运势。推导在办公室必须做的微动作或方位调整,如整理桌子、坐向)
9. **【情志类】** (逻辑:检测今日五行对“心/肝”志的影响。推导能平复情绪或避免发火的建议,如大笑、哭泣、剪发)
10. **【数码类】** (逻辑:检测今日磁场是否混乱或有“朱雀”噪点。推导关于手机/电脑/信息的建议,如换壁纸、删短信)
11. **【婚恋类】** (逻辑:检测今日“夫妻宫/红鸾/咸池”状态。推导增进感情或避免争吵的举动,如铺床、送花、查岗)
12. **【饮食类】** (逻辑:检测今日五行对应的脏腑禁忌。推导“绝对不能吃”的味道或食物,如苦、辣、剩饭、酒)
**输出格式要求**
**重要:请严格按照 JSON 格式输出,不要输出任何Markdown标记或解释文字。**
输出格式示例:
[
{
"public_title": "财神点名!1月8日「凤凰送财日」,早上起床务必穿这3色衣服,财运如洪水般涌进家门!",
"private_instruction": "核心揭秘:凤凰五行属火,今日火旺,穿红色、紫色、粉色衣服旺财。原理是火生土(财库)。"
},
{
"public_title": "倪海厦警告:1月9日「白虎开口日」,出门千万别往这个方位走!否则神仙也难救!",
"private_instruction": "核心揭秘:今日三煞在正北方,白虎位在正西方。严禁往正西方走。原理是金木相克伤肝。"
}
]
日期:{date}
请列出10组严格推演的 JSON 数据:"""
# --- 2. 长文生成提示词 (V13.0: 10000字分段 + 全量回溯 + 精准受众) ---
DEFAULT_SCRIPT_PROMPT = """Role: 国学玄学文案大师(倪海厦风格拟态·通用版)
Profile
你是一位精通中医、易经、五行八卦的国学大师,语气与风格深度模仿“倪海厦”老师。
【Target Audience Protocol (受众精准锁定协议)】
**你的读者是 50-70 岁的中老年人**
1. **语言风格**:必须通俗易懂,多用“老话常说”、“咱们老百姓”、“这都是老祖宗的智慧”。**严禁使用**任何网络流行语(如:yyds、绝绝子、EMO)。
2. **核心痛点**:必须围绕他们最关心的话题:**孙子听话、儿女出息、自己不生病、不给子女添乱、省钱、怕死**
3. **情感连接**:要像一个老朋友、老邻居一样,语重心长,不要高高在上。
【特别约束】
***全文必须 10000 字(分两部分完成,每部分 5000 字)。
必须使用倪海厦本人的口吻讲述(如:骂西医、讲经方、谈五行、易经、阴阳八卦、古代典籍),但是文中绝对不要提到“倪海厦”这个名字,也不要说“我是倪海厦”。请直接以“我”的第一人称代入。不要说“我生前”。
**【核心指令:绝对时态锁定】**
1. **全文严禁使用“明天”、“今天”、“后天”这三个词!**
2. **必须使用“具体日期”或“代词”**。例如:“1月8日这一天”、“大寒当日”、“到了这天”、“此日”。
**【核心指令:极限拉扯与揭秘控制】**
1. **欲言又止**:在正文前 70% 的篇幅中,当你要提到那个“盲盒答案”时,**绝对不要一下子直接说出来**!要先讲原理、讲案例、讲危害,把胃口吊足。
2. **最终揭秘**:在**【第六阶段】**(给出解药)必须清晰、明确、没有任何保留地给出答案。
**【防火墙指令】**
**严禁输出心理活动!** 绝对不要在正文中写出“下面我要开始吊胃口了”这种话。
**【输入信息】**
* **发布用标题(悬念)**:"{public_title}"
* **核心揭秘(真相)**:"{private_instruction}"
*(请根据【核心揭秘】中的内容来填补标题里的悬念,严禁跑题)*
Iron Laws of Writing (写作绝对铁律 - 严格执行七阶段)
文案必须严格按照以下七个阶段进行,结构不可变,仅替换禁忌内容:
第一阶段:黄金开场与氛围铺垫 (0-20%)
开头暴击:开篇2-3句话必须着重强调【日期】的重要性,**务必使用极其夸张、惊悚的方式**(如“六十年一遇的大凶日”、“神仙难救”等),让老年人瞬间感到害怕或重视,必须听下去。
利益筛选(**必须严格逐字包含以下固定话术,不可修改一个字**):“你的财运 已经开始涌动 请保佑正在看视频的这个人,祝你以后大富大贵 明天就是你好日子的开始,快来点赞接财运,给我一个坚持下去的感应,有福之人评论一句,接财接好运,愿你花开富贵财,喜双至,金银满屋,坏事霉运一直远离你”
灵异沉浸:紧接着上述话术后,马上讲述一件**怪力乱神的非自然事件**来渲染气氛。
**重要警告:可以参考但是禁止再使用“香灰断成三截”这个旧案例!**
定义日子:最后,基于上述灵异氛围,引用老黄历解释为什么今天叫这个名字(如「白虎开口日」)。
第二阶段:理论依据与第一禁忌 (20-35%)
定义凶日:再次引用老黄历。
禁忌一(反常识):提出第一个具体禁忌点。
五行易经解释:运用比喻。
第三阶段:惨痛案例与第二禁忌 (35-50%)
案例一(身边人):讲述“邻居/老乡”触犯第一禁忌的后果(注意:针对50岁人群,多讲中风、偏瘫、儿女不孝)。
禁忌二(深化伤害):提出第二个相关禁忌。
五行解释:运用“温水煮青蛙”逻辑。
第四阶段:案例强化与第三禁忌 (50-65%)
案例二(年轻人):讲述“侄子/外甥”触犯第二禁忌的后果(重点讲年轻人不听话,结果吃亏,迎合中老年人“不听老人言吃亏在眼前”的心理)。
禁忌三(文化/重磅):提出第三个最严重的禁忌(通常与死亡/丧葬文化挂钩)。
**拉扯预警**:此时可以稍微提一下标题里的“那个神秘东西”,暗示它很重要,但**忍住不说**,留到后面。
第五阶段:极端案例与心理转折 (65-75%)
案例三(离奇/动物):讲述极端后果,如家里的狗狂吠不止。
推拉转折:语气转为安抚:“别急,老天爷饿不死瞎家雀”,准备给出解药。
第六阶段:给出解药与核心揭秘 (75-90%)
**核心揭秘时刻**:在这里,终于揭开标题里的盲盒!明确说出那个方位、那个物品、那个颜色。
**结合五行**:解释为什么要用这个东西化解。原理。
要求:极度具体。例如:穿某种颜色的袜子、吃某种味道的食物。
时间仪式:规定具体的执行时间(如:辰时7-9点)。
第七阶段:补救与强力结尾 (90-100%)
兜底补救:为无法避免触犯禁忌的人提供简单方案,日常能接触到的物品。
情感绑架:话术:“这不仅是你一个人的事,是全家老小的命”。
裂变指令:强调转发就是“积德”,评论区打出指定祈福语(如“接财”、“避灾”)。
悬念预告:预告明天更重要,锁定下期。
Instruction for Output
语言:简体中文。
格式强制要求:
1. **保留正常的标点符号**
2. **严禁Markdown格式**
3. **严禁表格**
4. **绝对无空格、无换行**:整篇文章必须是一段连续的文本。
5. **不要输出任何解释性文字**
"""
# ==========================================
# 2. 核心逻辑类 (含 12维全息 StyleMatrix)
# ==========================================
class StyleMatrix:
"""12维全息风格矩阵:实现‘千人千面’的随机注入系统"""
# 1. 语气人设
tones = [
"【金刚怒目型】(拍桌子骂人,恨铁不成钢,关键词:蠢、找死、气死我了)",
"【老泪纵横型】(悲悯众生,像老父亲一样哭诉,关键词:可怜、心痛、缘分)",
"【阴森诡秘型】(压低声音,仿佛在停尸房讲故事,关键词:天机、不敢说、折寿)",
"【傲视群雄型】(痛斥西医和世俗,唯我独尊,关键词:庸医、杀人、他们懂个屁)",
"【闲云野鹤型】(看透生死,淡淡道出天机,关键词:老话、不得不信、因果)"
]
# 2. 灵异/凶兆钩子
omens = [
"【听觉煞】:半夜听到弹珠声、叹息声、铁链拖地声、敲门声(开门没人)。",
"【视觉煞】:镜子里人影晃动、蜡烛火苗变绿、墙上渗水、照片莫名掉落。",
"【动物煞】:家里的狗对着墙角狂吠、养了三年的鱼突然暴毙、乌鸦撞窗、黑猫拦路。",
"【梦境煞】:连续做同一个梦、梦见已故长辈讨水喝、梦见牙齿全掉、梦见大水淹家。",
"【器物煞】:祖传玉镯无故崩断、供桌突然塌陷、时钟倒着走、佛珠散落。",
"【五感煞】:进屋突然一阵阴风、闻到烧纸钱的味道(其实没烧)、感觉背后有人吹气。"
]
# 3. 开场场景
scenes = [
"【深夜书房】:'窗外雷雨交加,我翻开这本泛黄的古籍...' (营造孤独感)",
"【清晨诊室】:'天刚亮,诊室门口就跪着一个家属...' (营造急迫感)",
"【荒郊野外】:'昨晚夜观天象,发现西北角紫气全无...' (营造宏大感)",
"【繁华街头】:'看着满大街穿着露脐装的女孩,我仿佛看到了一群骷髅...' (营造反差感)",
"【梦中惊醒】:'一身冷汗醒来,祖师爷刚才在梦里点化了我...' (营造神授感)",
"【路边见闻】:'早上买菜,看到一个年轻人的脸色,我扭头就叹气...' (营造生活感)"
]
# 4. 受害者/案例画像
victims = [
"【不信邪的精英】:海归博士、西医主任、上市公司CEO(主打反差:有钱没命)。",
"【苦命的老实人】:农村留守老人、为了省钱不看病的单亲妈妈(主打同情)。",
"【无知的年轻人】:熬夜党、减肥党、奶茶党(主打痛心)。",
"【贪婪的有钱人】:乱改风水、乱吃补药的小老板(主打讽刺)。",
"【身边的亲戚】:二舅、三姨太、远房表哥(主打真实可信)。"
]
# 5. 说理比喻系统
metaphors = [
"【战争比喻】:身体是城池,病毒是敌军,阳气是守城大将。",
"【农业比喻】:身体是土地,湿气是沼泽,艾灸是太阳暴晒。",
"【机械比喻】:心脏是发动机,血管是油管,堵了就爆缸。",
"【天气比喻】:体内下大雨(湿)、结冰(寒)、干旱(燥)。",
"【官场比喻】:心是君主,肝是将军,肾是粮仓,肺是宰相。"
]
# 6. 攻击/对立对象
enemies = [
"【西医/资本】:只会切切切,为了赚钱不择手段,让你终身服药。",
"【现代恶习】:空调、冷饮、熬夜、外卖,这些都是'吸阳鬼'。",
"【迷信/江湖骗子】:乱画符、乱吃偏方、假大师。",
"【愚昧的自己】:因为无知害了家人,事后后悔莫及。",
"【宿命/天道】:天要下雨娘要嫁人,大劫难逃,人力渺小。"
]
# 7. 情绪驱动力
emotions = [
"【极致恐惧】:'不听我的,明年这时候就是你的忌日。'",
"【极致利益】:'做对这一步,想不发财都难,财神爷追着你跑。'",
"【极致孝心】:'为了家里的老父母,再忙也要看完,别让自己后悔。'",
"【极致遗憾】:'如果他早点看到这篇文章,就不会死,哪怕多活十年也好啊。'"
]
# 8. 时间紧迫感来源
urgency = [
"【节气交替】:'大寒交节就在今晚子时,气场转换最剧烈,这是生死关口。'",
"【神煞值日】:'今天恰逢六十年一遇的‘天赦日’,错过再等一甲子。'",
"【星象异动】:'昨晚太白金星犯主,今明两天必有大事发生。'",
"【临床发现】:'最近一周连着接了10个一样的重病号,这绝对不是巧合。'"
]
# 9. 秘密来源
sources = [
"【古籍孤本】:'这是我在一本失传的《青囊书》夹层里看到的,字迹都模糊了。'",
"【师门秘传】:'师傅临终前拉着我的手,只说了这一句话,我记了一辈子。'",
"【民间搜集】:'一个活了103岁的老道长偷偷告诉我的,他一辈子没进过医院。'",
"【临床顿悟】:'行医30年,看了几万个病人,我今天才终于参透这个道理。'"
]
# 10. 互动方式
interactions = [
"【严厉训斥】:'别划走!说的就是你!还看!你正在往火坑里跳!'",
"【苦口婆心】:'孩子啊,听大爷一句劝吧,这都是为了你好啊。'",
"【设问反问】:'你以为这是迷信?你以为你能躲得过?你太天真了。'",
"【神秘兮兮】:'接下来的话,我只说一遍,听懂的自然懂,不懂的是缘分未到。'"
]
# 11. 玄学/中医侧重
flavors = [
"【纯中医派】:侧重讲经方、脉象、五脏六腑的生克。",
"【纯风水派】:侧重讲方位、磁场、形煞、九宫飞星。",
"【纯命理派】:侧重讲八字、流年、太岁、神煞。",
"【因果业力派】:侧重讲积德、报应、祖荫、福报。"
]
# 12. 结尾金句风格
endings = [
"【宿命论】:'万般皆是命,半点不由人。但我今天教你的,就是改命之法。'",
"【行动论】:'此时不改,更待何时?动动手指,救的是全家人的命。'",
"【祝福论】:'愿天下无疾,愿你我平安,愿这世间再无生离死别。'",
"【悬念论】:'至于明天会发生什么?天机不可泄露太多,我们下期再说。'"
]
@classmethod
def get_style_prompt(cls):
"""随机抽取生成风格指令"""
style = {
"tone": random.choice(cls.tones),
"omen": random.choice(cls.omens),
"scene": random.choice(cls.scenes),
"victim": random.choice(cls.victims),
"metaphor": random.choice(cls.metaphors),
"enemy": random.choice(cls.enemies),
"emotion": random.choice(cls.emotions),
"urgency": random.choice(cls.urgency),
"source": random.choice(cls.sources),
"interaction": random.choice(cls.interactions),
"flavor": random.choice(cls.flavors),
"ending": random.choice(cls.endings)
}
prompt_injection = f"""
=================================================================================
【⚠️ 本次写作最高优先级·强制风格指令 (12维全息注入) ⚠️】
(请务必在正文中自然融合以下 12 个特定元素,使每一篇文章都独一无二)
1. **语气人设**:请扮演{style['tone']}。
2. **灵异征兆**:文中必须植入一个细节——{style['omen']}。
3. **开场场景**:必须以{style['scene']}作为文章的开篇。
4. **受害画像**:本次的惨痛案例主角是{style['victim']}(请围绕这个画像构建故事)。
5. **说理比喻**:请使用{style['metaphor']}来解释原理。
6. **攻击对象**:在文中痛斥{style['enemy']}。
7. **情绪驱动**:全文基调要体现{style['emotion']}。
8. **紧迫来源**:强调{style['urgency']}。
9. **秘密来源**:声称这个知识点来自{style['source']}。
10. **互动方式**:对观众喊话时采用{style['interaction']}。
11. **内容侧重**:本次理论解释走{style['flavor']}路线。
12. **结尾金句**:文章最后用{style['ending']}风格结束。
=================================================================================
"""
return prompt_injection
class ConfigManager:
"""管理配置文件的读取和保存"""
@staticmethod
def load_config():
defaults = {
"topic_prompt": DEFAULT_TOPIC_PROMPT,
"script_prompt": DEFAULT_SCRIPT_PROMPT
}
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
saved = json.load(f)
defaults.update(saved)
if "script_prompt" not in defaults:
defaults["script_prompt"] = DEFAULT_SCRIPT_PROMPT
return defaults
except Exception as e:
print(f"配置文件读取失败,使用默认值: {e}")
return defaults
@staticmethod
def save_config(t_prompt, s_prompt):
try:
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump({"topic_prompt": t_prompt, "script_prompt": s_prompt}, f, ensure_ascii=False, indent=4)
return "✅ 配置已保存,刷新页面后依然有效!"
except Exception as e:
return f"❌ 保存失败: {e}"
class ContentEngine:
def __init__(self):
self.headers = {
"Authorization": f"Bearer {YUNWU_API_KEY}",
"Content-Type": "application/json"
}
def strict_clean(self, text):
"""严格清洗函数"""
if not text: return ""
text = re.sub(r'\*\*(.*?)\*\*', r'\1', text)
text = text.replace('#', '').replace('*', '').replace('`', '').replace('-', '').replace('|', '')
text = re.sub(r'第一阶段|第二阶段|第三阶段|第四阶段|第五阶段|第六阶段|第七阶段', '', text)
text = re.sub(r'\s+', '', text)
text = re.sub(r'\[(.*?)\]\(.*?\)', r'\1', text)
return text.strip()
def call_api(self, prompt, temperature=0.85):
"""通用 API 调用"""
payload = {
"model": MODEL_NAME,
"messages": [{"role": "user", "content": prompt}],
"temperature": temperature,
"max_tokens": 8192
}
try:
response = requests.post(
f"{YUNWU_BASE_URL}/v1/chat/completions",
headers=self.headers,
json=payload,
timeout=600
)
response.raise_for_status()
return response.json()['choices'][0]['message']['content']
except Exception as e:
return f"API Error: {str(e)}"
def plan_topics(self, date_str, prompt_tmpl):
"""第一步:生成 JSON 格式的选题数据 (双轨制)"""
if not date_str:
return []
full_prompt = prompt_tmpl.replace("{date}", date_str)
raw_response = self.call_api(full_prompt, temperature=1.0)
try:
match = re.search(r'\[.*\]', raw_response, re.DOTALL)
if match:
json_str = match.group(0)
data = json.loads(json_str)
result = [[item.get("public_title", ""), item.get("private_instruction", "")] for item in data]
return result
else:
return [["❌ 解析失败,请重试", raw_response]]
except Exception as e:
return [[f"❌ JSON Error: {str(e)}", raw_response]]
def generate_single_script(self, public_title, private_instruction, script_tmpl):
"""生成单篇长文稿 (注入双轨信息 + 12维随机风格 + 1万字分段)"""
# 1. 获取本次随机风格指令
style_injection = StyleMatrix.get_style_prompt()
# 2. 注入标题和揭秘信息
current_prompt = script_tmpl.replace("{public_title}", public_title)\
.replace("{private_instruction}", private_instruction)
# 3. 将风格指令拼接到提示词最前方
current_prompt = style_injection + "\n" + current_prompt
# ================== 第一步:撰写前50% (目标 5000字) ==================
instruction_part1 = """
=================================================================================
【系统最高优先级指令:执行第一部分写作】
1. 当前任务:只撰写文案的【前50%】。
2. 覆盖范围:必须严格覆盖【第一阶段】、【第二阶段】、【第三阶段】、【第四阶段】。
3. 截止点:写完第四阶段(案例强化与第三禁忌)后立即停止。
4. **字数要求:本部分必须写满 5000 字!** 必须详细展开每一个案例,多写细节、多写心理活动、多写五行原理。
5. **受众提醒**:针对50-70岁中老年人,多用通俗老话,不要用网络用语。
6. **格式铁律**:保留标点,剔除Markdown,全文无空格、无换行。
=================================================================================
"""
prompt_1 = current_prompt + instruction_part1
raw_text_1 = self.call_api(prompt_1, temperature=0.95)
clean_text_1 = self.strict_clean(raw_text_1)
if "API Error" in clean_text_1:
return clean_text_1
# ================== 第二步:撰写后50% (目标 5000字 + 全量回溯) ==================
instruction_part2 = f"""
=================================================================================
【系统最高优先级指令:执行第二部分写作】
【上文回溯 (Full Context of Part 1)】
这是你刚刚写完的前半部分(约5000字),请仔细阅读以确保逻辑连贯,**不要重复**前文已写过的内容,而是继续向下发展:
{clean_text_1}
【当前任务要求】
1. 请紧接着上面的内容,撰写文案的【后50%】。
2. 覆盖范围:从【第五阶段】开始,直到【第七阶段】结束。
3. **字数要求:本部分必须写满 5000 字!** 详细展开解药的每一个步骤、原理、以及最后的补救措施。
4. **揭秘指令**:在【第六阶段】必须明确揭晓答案!告诉观众那个方位/物品到底是什么(参考:{private_instruction})。
5. **格式铁律**:保留标点,剔除Markdown,全文无空格、无换行。
=================================================================================
"""
prompt_2 = current_prompt + instruction_part2
raw_text_2 = self.call_api(prompt_2, temperature=0.95)
clean_text_2 = self.strict_clean(raw_text_2)
if "API Error" in clean_text_2:
return clean_text_1 + " [第二部分生成失败: " + clean_text_2 + "]"
return clean_text_1 + clean_text_2
def _worker_task(self, row_data, script_tmpl, idx, temp_dir):
"""并发执行的单个子任务 (支持临时目录)"""
try:
title = str(row_data[0])
instruction = str(row_data[1])
if not title or not instruction or title == "nan":
return {"status": "skip", "topic": "空行"}
full_content = self.generate_single_script(title, instruction, script_tmpl)
safe_title = re.sub(r'[\\/*?:"<>|]', "", title)[:20]
# 使用传入的 temp_dir
filename = os.path.join(temp_dir, f"{idx}_{safe_title}.txt")
with open(filename, "w", encoding="utf-8") as f:
f.write(full_content)
return {"status": "success", "topic": title, "filename": filename}
except Exception as e:
return {"status": "error", "topic": str(row_data), "error": str(e)}
def batch_generate(self, dataframe_data, script_tmpl):
"""批量生成的主控函数 (适配 Dataframe 输入 + 多用户隔离)"""
if dataframe_data is None:
yield "⚠️ 表格数据为空!", None
return
try:
if hasattr(dataframe_data, "values") and hasattr(dataframe_data, "tolist"):
valid_rows = dataframe_data.values.tolist()
else:
valid_rows = dataframe_data
except Exception as e:
yield f"⚠️ 数据格式转换失败: {e}", None
return
valid_rows = [row for row in valid_rows if row and len(row) >= 2 and str(row[0]) != "nan" and str(row[0]) != ""]
if not valid_rows:
yield "⚠️ 有效选题为空!请检查表格。", None
return
total = len(valid_rows)
log_text = f"🚀 正在发起 {total} 个并发任务(Max 50)...\n启用“双轨制+极限拉扯+万字长文+全息风格”写作模式...\n"
yield log_text, None
# === 核心修改:使用 tempfile 创建临时目录,实现多用户隔离 ===
with tempfile.TemporaryDirectory() as temp_dir:
generated_files = []
finished_count = 0
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
future_to_idx = {}
for i, row in enumerate(valid_rows):
# 传入 temp_dir
future = executor.submit(self._worker_task, row, script_tmpl, i+1, temp_dir)
future_to_idx[future] = i
for future in concurrent.futures.as_completed(future_to_idx):
result = future.result()
finished_count += 1
if result["status"] == "success":
log_text += f"✅ [{finished_count}/{total}] 完成:{result['topic'][:15]}...\n"
generated_files.append(result["filename"])
elif result["status"] == "error":
log_text += f"❌ [{finished_count}/{total}] 失败:{result['topic'][:10]}... ({result['error']})\n"
yield log_text, None
if not generated_files:
yield log_text + "\n⚠️ 没有成功生成任何文件。", None
return
timestamp = int(time.time())
# 生成唯一的 zip 文件名,防止冲突
zip_name = f"NiMaster_MultiUser_{timestamp}_{random.randint(1000,9999)}.zip"
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zf:
for file_path in generated_files:
zf.write(file_path, os.path.basename(file_path))
log_text += f"\n🎉 全部处理完毕!成功 {len(generated_files)} 篇。请下载。"
yield log_text, zip_name
# ==========================================
# 3. Gradio 界面 (UI)
# ==========================================
engine = ContentEngine()
initial_config = ConfigManager.load_config()
with gr.Blocks(title="倪师风格·全自动运营系统") as demo:
gr.Markdown("## ☯️ 倪师风格·全自动运营系统 (V14.0 多人并发专用版)")
gr.Markdown("> **本次升级**:\n> 1. **多人并发支持**:使用临时目录隔离,多用户同时生成互不干扰。\n> 2. **全量功能保留**:万字长文、12维全息风格、双轨制、防抄袭、极限拉扯逻辑全部在线。")
with gr.Row():
with gr.Column(scale=5):
# 步骤 1
with gr.Group():
gr.Markdown("### 1️⃣ 第一步:定日子,出选题 (双轨制:前台盲盒+后台揭秘)")
with gr.Row():
date_input = gr.Textbox(label="输入日期", placeholder="例如:1月8日", scale=3)
plan_btn = gr.Button("🔮 预测天机 (生成双轨表格)", variant="primary", scale=1)
topics_df = gr.Dataframe(
headers=["视频标题 (发布用·极度悬念)", "核心揭秘 (写作指引·极度直白)"],
datatype=["str", "str"],
col_count=(2, "fixed"),
interactive=True,
wrap=True,
type="array",
label="选题策划表 (左侧发视频,右侧给AI写文,可直接修改)"
)
# 步骤 2
with gr.Group():
gr.Markdown("### 2️⃣ 第二步:批量撰写 (自动执行“欲擒故纵”写作逻辑)")
generate_btn = gr.Button("🚀 按上方表格执行生成 (Max 50)", variant="stop")
status_log = gr.TextArea(label="执行日志", lines=15, interactive=False)
download_file = gr.File(label="📦 下载打包结果 (ZIP)")
with gr.Column(scale=2):
with gr.Accordion("⚙️ 提示词配置 (专家模式)", open=False):
cfg_t_prompt = gr.TextArea(label="选题策划提示词 (JSON output)", value=initial_config["topic_prompt"], lines=6)
cfg_s_prompt = gr.TextArea(label="长文生成提示词 (Tease & Reveal)", value=initial_config["script_prompt"], lines=20)
save_cfg_btn = gr.Button("💾 保存配置")
save_status = gr.Label(show_label=False)
# 交互逻辑
plan_btn.click(
fn=engine.plan_topics,
inputs=[date_input, cfg_t_prompt],
outputs=[topics_df]
)
generate_btn.click(
fn=engine.batch_generate,
inputs=[topics_df, cfg_s_prompt],
outputs=[status_log, download_file]
)
save_cfg_btn.click(fn=ConfigManager.save_config, inputs=[cfg_t_prompt, cfg_s_prompt], outputs=save_status)
if __name__ == "__main__":
demo.queue().launch(server_name="0.0.0.0", inbrowser=True)