Spaces:
Sleeping
Sleeping
| 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) |