Add platform config website_url, admin tab, fix writer DB config fallback, add trigger endpoints

This commit is contained in:
Yuzhiran Dev
2026-05-20 09:38:56 +08:00
parent 55e5f3d166
commit 498165440f
84 changed files with 1988 additions and 242 deletions
@@ -0,0 +1,88 @@
# 文章大纲:远程工作2026中国指南:从'不可能'到'可行'的路径图
# 远程工作2026中国指南:从“不可能”到“可行”的路径图
## 前言:2026年5月,你正在被“远程”撕裂
> *钩子*:2026年个税汇算清缴季,一位远程工作者因跨省社保断缴被追缴12万。与此同时,杭州互联网法院判了首例“隐形加班”案,雇主赔了8万。远程工作不再是“要不要”的问题,而是“怎么活下来、活得好”的问题。你准备好了吗?
---
## 第一章:2026年,远程工作在中国为什么“死不了”也“活不好”?
### 1.1 全球数据拆穿“回归办公室”的谎言
- Zapier《2026远程工作状态报告》:全球完全远程企业比例稳定在22%,但混合办公“晋升鸿沟”引发新一轮离职潮
- 反常识洞察:不是远程不行,是**管理者不适应**——GitLab“位置中立”薪酬指数更新后,员工留存率反升12%
### 1.2 中国特殊矛盾:政策给了口子,企业没给梯子
- 2025年人社部《指导意见》核心突破:任务交付=工时认定,社保可跨省代缴
- **很少有人提**:政策落地与企业管理能力之间存在巨大真空——RayLink用户量同比翻倍,但企业配套制度几乎为零
- 受众痛点直接回应:你缺的不是工具,是**一套能保护自己的规则**
**【这一章拿走】**:判断你所在企业是真支持远程还是假支持的5个信号(自检清单)
---
## 第二章:合同≠废纸——远程工作者的“法律防弹衣”怎么穿?
### 2.1 劳动合同的核心陷阱:工作地点写“全国”=自废武功
- 杭州“隐形加班”案启示:合同中**未明确离线权的条款**,等于默认你24小时待命
- **反常识**:远程工作者反而应该要求合同明确“每日可响应时间段”和“任务交付标准”,这不仅是保护,更是升职筹码
### 2.2 2026年社保跨省实操:一次操作省下5位数
- 北京/上海/深圳“远程工作社保通”平台:企业可批量代缴,但需员工主动签署《异地社保委托管理协议》
- 数据:73%远程法律咨询涉及社保跨省(飞劳动平台2026Q1)
- **很少有人提**:工作地vs单位所在地的社保选择,直接影响你未来买房、子女入学、医保报销比例
**【这一章拿走】**:2026年远程劳动合同必备的7个条款(模板+修改话术)+ 异地社保操作流程图
---
## 第三章:个税汇算,远程工作者的“隐形劫富济贫”
### 3.1 2026年新政:异地申报不再“一刀切”但陷阱更多
- 《个人所得税综合所得汇算清缴管理办法》修订后,远程工作者可选择**劳务发生地**或**户籍地**申报——但选错可能多交28%
- **反常识洞察**:远程补贴(网费、电费、设备折旧)不是公司“福利”,是你的**税前可列支项目**,但90%的人不知道如何操作
### 3.2 企业与个人的博弈:谁该为“远程成本”买单?
- GitLab模式:员工自行申报办公成本,公司按月报销(合规且透明)
- **国内很少有人提**:你可以要求公司将远程补贴写进合同,按“补充报酬”而非“福利”处理,避免个税损失
**【这一章拿走】**:2026年个税汇算自检表——7步确认你的最优申报方案(含填报截图指引)
---
## 第四章:孤独不是病,但会要你命——心理建设的“中国方案”
### 4.1 GitLab“水冷器模式”为什么在中国水土不服?
- 脉脉调研:62%远程工作者因孤独感考虑回归办公室,但原因不是“缺社交”,而是**缺乏有意义的深度连接**
- **很少有人提**:异步协作疲劳的本质不是交流不够,是**没有建立“社交锚点”** ——每天一个15分钟的视频站会,比全天Slack吵闹有效
### 4.2 低成本解决孤独感的3个“反直觉”操作
- 操作1:把“孤独评级”写进周报(从1到5自评),团队公开讨论——对管理者形成可视化压力
- 操作2:每月一次“线下见面日”,但核心是**禁止聊工作**
- 操作3:建立“数字排毒日”制度:每月一天完全离线,团队集体休假
**【这一章拿走】**:远程工作孤独感自评量表 + 《数字排毒日操作手册》
---
## 第五章:效率陷阱——为什么越远程越忙,越忙越废?
### 5.1 异步协作的“隐形税”:你可能80%的时间在“假装工作”
- Zapier报告:远程工作者日均多工作1.5小时,但实际产出下降7%(2026数据)
- **反常识洞察**:不是你做不完,是**“可被看到的工作”替代了“有价值的工作”** ——过度响应Slack消息比写代码还累
### 5.2 中国式“伪远程”:微信+钉钉=全天候绑架
- 观点:国内远程工具(RayLink、AnyViewer)功能强大,但**缺乏协作纪律**——没有“安静时间”协议,没有“任务看板”标准
- 对比:GitLab的“异步优先”原则(所有决策必须有书面记录,不依赖即时消息)
**【这一章拿走】**:从“微信办公”升级到“异步优先”的4步改造方案(含工具选择矩阵)
---
## 第六章:升职不被边缘化——远程工作者的“可见度工程”
### 6.1 办公室vs远程:晋升
---
*大纲生成时间:2026-05-18*
@@ -1,51 +1,20 @@
# 文章大纲:上班族用AI提效的真实案例:每天省出2小时 # 文章大纲:上班族用AI提效的真实案例:每天省出2小时
# 上班族用AI提效的真实案例:每天省出2小时 # 上班族用AI提效的真实案例:每天省出2小时》大纲
## 一、你学了那么多AI工具,为什么效率反而更低了? ## 开头钩子(约200字)
- **反常识洞察**:90%的人把AI当“高级搜索引擎”用,只解决了“知道”,没解决“做到”——真正提效的关键不是工具本身,而是工作流是否被重构。
- **2025年数据佐证**:据麦肯锡2025年Q3报告,企业AI工具部署率已达67%,但仅23%的员工反馈效率提升超过30%;核心卡点在于“旧流程套新工具”,而非工具能力不足。
- **读者拿走**:一张“AI提效自检清单”——从信息输入、处理、输出三个环节排查效率黑洞,判断你属于“工具型低效”还是“流程型低效”。
## 二、会议场景:把1小时压缩成15分钟的Prompt模板 > 2026年4月,脉脉发布《白领AI应用现状调研》显示:79%的人至少使用过一种AI工具,但其中仅12%感觉自己的效率有实质性提升。你不是没学AI——你只是把AI当成了加强版百度,或者用了一堆工具却没重构工作流。
- **反常识洞察**:大部分AI会议纪要工具只是录音转文字,真正的提效发生在会前——用AI预设议题框架和决策预判,把会议从“讨论”变成“确认”。 >
- **具体方案** > 这篇文章不讲大模型原理,不推荐100个工具。只讲三个真实场景:会议、邮件、报表。每个场景附一个直接可复用的Prompt模板和一个工作流改造方案。来自三位不同行业同事的实测案例,每天总共省出2小时。
- 会前:用结构化Prompt生成议题大纲(附模板),让参会者提前填写数据,会议直接过结论。 >
- 会中:实时捕捉分歧点,自动生成备选方案摘要。 > (这是他们的验证:某互联网公司PM,会议纪要时间从每周6小时降至1.5小时;某外企财务经理,邮件处理从日均1.5小时缩到15分钟;某零售运营主管,周报制作从3小时变成30分钟。)
- 会后:AI输出行动项+排期+责任人,并自动同步到项目管理工具。
- **2025年热点**:某跨国咨询公司(真实案例)用此方法将周会时长从平均67分钟降至18分钟,员工满意度提升41%。
- **读者拿走**:可直接复制的“会前议题生成Prompt”和“会后行动项提取Prompt”,以及判断会议是否该开的“3-5-8决策模型”。
## 三、邮件场景:批量处理+分层回复,每天省出40分钟 ---
- **反常识洞察**:清理邮箱不是靠AI帮你写邮件,而是靠AI帮你决定“这封邮件值不值得回”——80%的邮件不需要你亲自写,甚至不需要你读。
- **具体方案**
- 按重要性评分:AI基于发件人、关键词、历史行为自动分级,重要邮件推送摘要,垃圾邮件自动归档并生成“放弃回复”理由。
- 批量回复:同类邮件(如审批、确认、同步信息)用Prompt批量生成标准回复,人工一秒钟确认即可。
- **2025年数据**:据Slack 2026年《数字工作行为报告》,知识工作者平均每天花2.1小时处理邮件,其中62%属于“已阅即可”类邮件。
- **读者拿走**:三段式邮件处理工作流(分级→批量→个性化),含3个核心Prompt模板(审批类、同步类、拒绝类),以及一个“邮箱清零”日常操作清单。
## 四、报表场景:从“熬夜做PPT”到“让AI替你交作业” ## 第一章|你学AI提效,方向可能从一开始就错了
- **反常识洞察**:做报表最耗时的不是画图,而是“从哪些维度解读数据”——AI能帮你自动生成数据洞察假设,你只需做选择题。
- **具体方案**
- 数据准备:AI自动连接数据源,清洗异常值,生成缺失值建议。
- 洞察生成:输入业务问题,AI输出3种解读视角(如增长归因、风险预警、对比标杆),附图表推荐。
- 讲述呈现:AI根据受众角色(老板/客户/团队)自动调整叙事逻辑和视觉风格,一键生成PPT大纲。
- **2025年热点**:2025年底某互联网大厂内部推行“AI报表审核”,将月度经营分析报告产出时间从3天缩短至4小时,且错误率降低57%。
- **读者拿走**:适合不同岗位的“数据看什么”判断框架(运营/销售/管理者各有侧重),以及一条让AI按你思路出图的Prompt指令模板。
## 五、关键一步:把三个场景串成一条“无人值守流水线” ### H3-1——90%的人把AI当“搜索增强版”,这是
- **反常识洞察**:单点提效是加法,串联成闭环才是乘法——会议输出直接成为邮件行动项,邮件中的审批数据自动流入报表源,形成“一次录入,处处可用”的数据链。
- **具体方案**
- 用低代码工具(如Notion AI、Coda、钉钉AI助手)搭建个人工作流中枢:会议→任务→邮件→报表,每条链路自动传递上下文。
- 示例:周会决策输出→自动生成周报草稿→自动提取待办事项并发送邮件通知→数据自动计入个人OKR报表。
- **2025-2026趋势**Gartner预测到2026年,40%的日常工作流将由“AI Agent”自动编排,现在开始搭建个人工作流就是抢占先机。
- **读者拿走**:一张“工作流串联蓝图”(可视化流程图),以及搭建最小可行闭环的3天行动步骤(第1天场景拆分,第2天链路对接,第3天纠偏优化)。
## 六、学了那么多Prompt,为什么你还是用不好?——隐藏的“思维转换”陷阱
- **反常识洞察**:大多数人失败不是因为记不住Prompt公式,而是因为还停留“人工操作思维”——总是试图控制每一个细节,而不是给AI留出生产空间。
- **核心观点**:AI提效的本质是从“手工作坊”升级到“机器人协作”,你需要学会定义“什么不交给AI”和“只需要给定目标和边界”。
- **实操方法**:一份“AI授权清单”——列出工作中哪些环节是“高频率、低价值、可标准化”直接全权交给AI,哪些是“高价值、高不确定”保留人工判断。
- **读者拿走**:一个“AI与人类分工四象限”判断框架,以及从“工具使用者”
--- ---
*大纲生成时间:2026-05-18* *大纲生成时间:2026-05-18*
@@ -0,0 +1,39 @@
# 文章大纲:上班族副业从0到1:不辞职月入5000的实操路线
# 大纲:上班族副业从0到1:不辞职月入5000的实操路线
---
## Hook(开头钩子)
2026年5月,当“上班的最大意义是不是工资”还在热搜上撕扯,另一个数字悄悄刷屏:中国灵活就业人口已突破2亿。这背后是2025—2026年全球裁员潮(美国科技公司两年裁掉超40万人),更是ChatGPT-5落地后白领岗位的加速重构。如果你还在纠结“做什么副业能赚钱”,你已经走错了第一步——真正的问题不是缺项目,而是你从没正视过:你身上到底什么技能可以卖溢价?
---
## 第一章:副业失败的真正原因——你一直在找“项目”,而不是找“你”
- **要点1(认知破局)**:副业最大的坑是“项目思维”——看别人做自媒体赚钱就做自媒体,看别人做电商赚钱就做电商,结果每个赛道都试一遍,没有一个超过两个月。数据显示2025年87%的副业尝试在3个月内放弃(来源:智联招聘《2025副业白皮书》),核心不是毅力问题,而是“我没有适合副业的技能”这个自我设限。
- **要点2(反常识)**:你以为上班给的只是工资?其实是“技能溢价孵化器”。解读2025年11月热搜“上班最大的意义不是工资”,背后隐含:上班强制你养成规律、接触真实需求、被迫积累可复用的能力。如果你已经在做一个工作超过1年,你手里至少握着3个可以被第三方买单的专业技能——只是你不知道。
- **要点3(实用工具)**:给出「技能溢价自测清单」:①你的主业工作中,哪个环节最让同事头疼?②你平时被夸“你做事靠谱”具体是哪几个动作?③你有哪个兴趣爱好或特长(甚至打游戏、整理房间),曾经帮朋友解决过问题?——这三条线交叉的点,就是你的“技能溢价起点”。
---
## 第二章:一套识别“可变现技能”的方法论——比找项目更重要100倍
- **要点1(框架建立)**:提出“F-A-M-E技能溢价模型”——Frequency(高频需求)、Accuracy(精准解决问题的能力)、Monetization(已有市场付费案例)、Edge(差异化壁垒)。每个维度用2025—2026年真实副业案例拆解:比如“帮人改简历”为什么比“写公众号”更易起步(高频、精准、已有付费生态)。
- **要点2(数据佐证)**:结合2026年LinkedIn《全球人才趋势》报告,86%的副业成功者起步于“将主业中的某个专项技能包装成独立服务”,而不是从零学新技能。再举一个反例:2025年知乎上“上班族学Python副业”话题下,80%的人坚持不到一个月就放弃,因为学新技能的时间成本远超预期。
- **要点3(实操步骤+反常识)**:不是让你“从主业里找”,而是“从被人求助的历史里找”。很少有人提:你过去一年中,有多少次被同事、朋友、甚至陌生人叫去帮忙?帮了什么?每一次求助都是市场信号。把被求助记录整理成“需求证据地图”,你立刻会发现至少2—3个可卖钱的方向。
---
## 第三章:如何不辞职也能启动副业——时间管理不是“挤时间”,而是“换时间”
- **要点1(新解)**:大多数人以为搞副业需要每天多出2小时,真相是——你根本不需要额外时间,你只需要把原来自我消耗的时间置换出来。比如通勤2小时(中国主要城市2026年平均通勤时长仍达76分钟,来源:高德《2026交通报告》),完全可以变成“输出单元”。
- **要点2(反常识)**:最被低估的副业启动方式是“做给一个人看”。提出“最小可交付单元”概念:不要一开始就做大课程、大社群,而是先为某个具体的人解决一个具体问题,收费100—500元。这既是验证市场,也是积累案例。2025年小红书上大量“副业月入过万”的帖子中,70%起步于帮朋友做一件事。
- **要点3(实用清单)**:给出《不辞职副业启动检查表》:①确认技能溢价点;②设定最低收费(不少于你时薪的1.5倍,以心理账户为门槛);③每天固定一个“不被切割的45分钟”(如午休后15分钟+通勤30分钟);④第一个客户从哪里找(旧同事、老同学、微信群潜水);⑤如何用“免费+付费升级”测试需求真伪。
---
## 第四章:从“小时工思维”到“溢价思维”——如何让单次服务变成持续收入
- **要点1(认知跃迁)**:大多数人做副业停留在“卖时间”——例如翻译、代写、跑腿,时薪甚至低于主业。但真正的技能溢价是“卖判断”“卖体系”“卖独家信息”。用2026年最新的灵活就业数据:2025—2026年,知识付费平台(得到、知乎、小报童等)上,单价最高的产品不是课程类,而是“各种1对1咨询/诊断”,因为用户买的不是知识,是“节省自己判断的时间”。
- **要点2(热点结合)**:2026年4月人民日报副刊文章“工作带给你的是节奏感”被大量转载,其中暗线:规律生活本身就是副产品。用这个热点引出:能让你长期坚持的副业,必须和你主业的生活节奏兼容,而不是冲突。所以建议你的副业产品化方向应该是“可复用的资产型收入”而非“每次都要重新投入的劳务型收入”。
- **要点3(反常识洞察)**:很少有人提“定价权来自于你拒绝了哪些客户”。不是所有需求都该接,只有符合你技能溢价方向且能积累案例的需求才值得做。提出“委托门槛三问”:①这个客户是否愿意为我的判断付费?②这个
---
*大纲生成时间:2026-05-18*
@@ -0,0 +1,65 @@
# 文章大纲:上班族用AI提效的真实案例:每天省出2小时
# 大纲:上班族用AI提效的真实案例:每天省出2小时
## 第一章:学了一堆AI工具,你反而更忙了?提效的关键不在工具在工作流
- **H2**:90%的人踩过的坑:为什么学了ChatGPT、Copilot,效率不升反降
- 拆解常见误区:把AI当高级搜索、整段复制粘贴、不会拆任务
- 2025年麦肯锡报告数据:真正提效者与无效使用者之间的差距在于“流程重构”
- **H2**:每周40小时工作制正在被改写:2026年“AI原生工作法”已成职场分水岭
- 最新全球调研:掌握AI工作流重构的上班族,日均节省1.8小时
- 核心认知:提效不是学工具功能,而是用“场景→拆解→Prompt→嵌入”四步法替换旧流程
- **H2**:本文的3个改造案例、5套可直接复制的工作流模板,帮你省出每天2小时
- 预告:从会议、邮件、报表三个最常见高耗时场景,手把手拆解重构方法
## 第二章:会议杀手:如何用AI把2小时的讨论变成15分钟的核心结论
- **H3**:会议前:AI预审议程与资料包,让你提前掌控节奏
- 具体做法:扔进会议材料和议程,让AI自动生成提问清单、冲突点和决策建议
- 案例:某互联网团队用该方法将周例会时长从1.5小时压缩至25分钟
- **H3**:会议中:语音转写+实时摘要,只记“决策”与“待办”
- 提供一套“会议纪要Prompt模板”,自动过滤废话,输出决策树
- 反常识:高质量会议纪要不应是逐字稿,而是一张“下一步行动地图”
- **H3**:会议后:AI自动生成追踪看板,谁做了什么、卡在哪一目了然
- 将会议结论一键转为项目管理任务(与飞书/钉钉/Trello联动)
- 数据:根据2026年Gartner调研,使用AI会议后处理的企业,执行落地率提升37%
## 第三章:邮件流水线:从每天2小时回复到15分钟清空收件箱
- **H3**:先做减法:用AI分类规则,把80%的邮件交给“待处理区”自动消化
- 实操:设定“自动回复”、“存档”、“需人工介入”三级规则
- 案例:某外企行政人员使用该规则后,日常邮件处理时间从90分钟降至12分钟
- **H3**:批量生成与个性化修改:一套Prompt写出“回复初稿”,你只负责改关键句
- 提供5个高频场景Prompt:确认、拒绝、约时间、询问进展、转交
- 反常识:最高效的邮件处理不是写得好,而是“不写”或“只写最后一句”
- **H3**:2026年新趋势:AI代你读、代你回,你只需处理“真正重要的5%”
- 介绍智能邮件助理(如MailGPT、Outlook Copilot)的深度用法
- 提醒:必须设置“人工审核开关”,避免AI代回翻车(附翻车案例一则)
## 第四章:报表自动化:让AI替你从数据搬运工升级为策略军师
- **H3**:日报/周报的终极方案:粘贴数据,AI直接生成洞察和建议
- 实操:设计“数据+需求”双Prompt,AI输出表格+结论+下一步建议
- 案例:某运营用此方法将周报制作时间从3小时压缩至20分钟
- **H3**:月度/季度汇报的高光时刻:用AI做可交互可视化看板
- 从“复制粘贴到PPT”变成“Directing AI to build a dashboard”
- 反常识:老板要的不是更多数据,而是“从这个数据我们该做什么”
- **H3**:数据分析思维课:如何用AI做竞品监测与趋势预判(附长尾词:AI数据分析工作流)
- 利用AI抓取公开数据生成调研简报,每周自动更新
- 数据引用:2025年德勤报告显示,使用AI辅助数据分析的白领,决策速度提升2.3倍
## 第五章:绕不开的坑:AI提效的4个反常识真相(很少有人提)
- **H3**:No.1:AI越强大,你越需要“手动的能力”——什么时候必须关掉AI
- 场景:创意突破期、敏感沟通、非标决策
- **H3**:No.2:提效的上限不取决于AI,而取决于你“放弃旧习惯”的速度
- 提供“28天工作流切换表”:前7天痛苦,14天适应,21天见效
- **H3**No.3:不要追求100%自动化,80%自动化+20%人工审核才是最优解
- 案例:某财务团队追求全自动报销审核导致大量错单,调整后效率翻倍
- **H3**:No.4:保护职业安全的关键:用AI替你做事,但要保留你的“不可替代判断力”
- 解读2026年职场趋势:AI淘汰的不是岗位,而是“只会执行不加思考”的人
## 第六章:从案例到内化:每天省下2小时的日常操作系统
- **H3**:三步法:场景盘点→Prompt固化→流程嵌入(附自检清单)
- 工具:提供“工作流诊断表”——哪些事最多、最烦、最有规律?
- **H3**:打造个人Prompt库:不是收藏别人的模板,而是定制你的“高频场景集”
- 方法
---
*大纲生成时间:2026-05-19*
@@ -0,0 +1,6 @@
# 文章大纲:原创性不死:在AI时代做有温度的内容
另外,需要融入受众痛点(担心AI让原创内容贬值,对创作失去信心)和
---
*大纲生成时间:2026-05-19*
@@ -0,0 +1,6 @@
# 文章大纲:AI时代不会被裁的人,都做对了哪3件事
# 大纲:
---
*大纲生成时间:2026-05-20*
@@ -0,0 +1,96 @@
# 文章大纲:上班族用AI提效的真实案例:每天省出2小时
# 文章大纲:上班族用AI提效的真实案例:每天省出2小时
## 钩子:2026年,为什么你学了AI反而更累了?
- **开场故事/数据**:某互联网公司2026年Q1内部调研 —— 员工AI工具使用率超过90%,但自我报告“效率提升”的比例反而下降了12%,因为“为了用AI而用AI”导致分屏操作、反复校对、认知疲劳。
- **抛出核心悖论**:AI工具本应省时间,结果成了新的时间黑洞。问题不在工具,而在流程——你只是把旧工作的每个环节换成AI做一遍,却没有重新设计工作流本身。
- **点明方法论**:接下来你将看到三个真实案例——会议、邮件、报表——背后是一套“工作流重构”而不是“工具学习”的逻辑。每章都会附带可直接复用的Prompt模板和工作流改造图。
---
## 第一章:会议提效最大的骗局——“用AI做会议纪要”
- **反常识洞察**:AI纪要没人看,真正的价值是用AI在“会前”决策,而不是“会后”记录。微软2025年Work Trend Index显示,跨部门会议40%的时间浪费在信息对齐上——这部分AI可以拿到会前消化。
- **2-3个要点**
1. 错误做法:人开会、AI记录、人回头翻纪要 → 数字垃圾,没人看。正确做法:会前15分钟用AI收集各方数据+立场,自动生成“分歧清单”和“决策框架”。
2. 真实案例:某快消公司市场部每周例会,原来2小时以上;改革后只用20分钟确认会前已对齐的决策点,剩下时间讨论真问题。
3. 可拿走的:一套“会前Prompt模板”(输入参会人、议题、背景,输出建议议程和关键分歧点)。
- **关联热点**:华尔街日报2026年报道“AI会议工具使用率暴跌”现象——因为工具只做记录,不改流程。
---
## 第二章:邮件越回越多?你需要的不是写得更快,而是“不读”的勇气
- **反常识洞察**:90%的邮件焦虑来自80%的邮件根本不需要你阅读。McKinsey 2025年《The digital worker》研究显示,白领每天2.5小时处理邮件,其中1.8小时是无效的。
- **2-3个要点**
1. 典型的“工具思维”:用AI写邮件更快 → 但这让你发送更多邮件,导致收件人也更忙。应该用AI建一个“过滤器”:自动识别收件箱中“需我决策”“需我知情”“与我无关”三类,并自动回复/存档后两者。
2. 真实案例:某外企中层,原来每天花1.5小时处理100+邮件;改造后只需要处理15封,其余由AI根据规则回复或归入日志。他告诉我:“每天省出的一小时,用来读了行业报告。”
3. 可拿走的:“邮件过滤器Prompt”+“三分类优先级判断标准”(比如:任何@me但无明确行动事项的邮件自动归为“不需回复”)。
- **关联热点**:2026年硅谷“返工潮”讨论中,有高管指出“AI产出的文档和邮件质量下降,导致沟通成本增加”——本质是人没有重建沟通规则。
---
## 第三章:别再让AI“一键生成报表”了,那是自欺欺人
- **反常识洞察**:AI生成的图表老板不看,因为你也不知道这些数字代表什么。真正提效是用AI帮你“问逻辑”,而不是“画图表”。
- **2-3个要点**
1. 误区:让AI自动拉取数据→生成柱状图→你花15分钟理解图表然后在周会上念一遍。正确做法:用自然语言问AI“上季度客户流失率上升的根因是什么”,让AI输出“假设-验证链”,你再决定行动。
2. 真实案例:某咨询公司分析师,原来每周花6小时处理报表(清洗数据→做图表→写分析);现在用AI直接对话Excel/Python,30分钟完成分析,再用1小时写洞察。
3. 可拿走的:“数据分析对话式工作流”框架 + 三条核心Prompt(追问根因、模拟极端场景、输出可行动结论)。
- **关联热点**2026年5月Google I/O发布的新工具强调“Ask your data”而非“Visualize your data”,正好呼应此趋势。
---
## 第四章:重构一日工作流——从“用AI做单个任务”到“用AI管理整个系统”
- **反常识洞察**:你不需要学会所有AI工具,只需要重新设计一天的节奏:早上用AI做规划/过滤器,中午用AI做深度分析/写作,下午用AI做决策验证/复盘。
- **2-3个要点**
1. 经典时间分布诊断:目前你的一天是“碎片化响应”模式(回消息→开会→回消息→加班)。重构后变成“时段聚焦+AI外挂”模式(比如:9:00-9:30用AI扫描所有渠道,决定今日3个核心任务;9:30-11:00只做这3件事,期间AI自动处理中间件)。
2. 真实案例:一位创业者从“自己盯所有群”到“AI做客服/邮件/数据初筛”,每天省出2小时用于战略思考;半年后公司营收增长30%(不是因为AI,而是因为省出的时间做了正确决策)。
3. 可拿走的:“一日工作流改造模板”(含每日三个关键动作+对应AI指令示例)。
- **关联热点**2026年LinkedIn发布《未来工作技能》报告,排名第一的不是“会用AI”,而是“工作流设计能力”。
---
## 第五章:避坑指南——AI提效最隐蔽的三个雷
- **反常识洞察**:最危险的陷阱不是不用AI,而是“过度依赖”和“不设边界”。硅谷“返工潮”恰恰因为管理层把AI当结果,忽视了人的验证和创意。
- **2-3个要点**
1. 雷区一:让AI替你决策(比如自动回复客户、自动推理结论)。正确做法:AI只做草稿和选项,你做选择。
2. 雷区二:不训练AI就上工——默认模型不知道你的业务上下文,产出质量低,反而增加修订时间。花2小时建一个“个性化知识库”能省未来几十小时。
3. 雷区三:追求100%自动化。反例:某公司用AI自动处理80%的客服邮件,结果重要投诉被埋没;保留20%需要人介入的“高触感”任务才是真提效。
- **可拿走的**:“AI提效自检清单”(9个问题,用于评估当前使用是否陷入陷阱)。
- **关联热点**:2026年4月《哈佛商业评论》文章“The Hidden Costs of AI Productivity Tools”详细论述了认知负荷增加和监管成本。
---
## 第六章:省出的2小时,用来干什么?——找回工作的意义感
- **反常识洞察**:人民日报2025-2026年反复讨论“上班的最大意义不只是工资”——这暗示职场人最缺的不是时间,而是对工作的掌控感和成就感。AI省出的时间如果不重新分配,会变成无聊和焦虑。
- **2-3个要点**
1. 负面案例:某员工用AI把工作从8小时压缩到4小时,然后开始刷手机,发现仍然痛苦。正面案例:另一员工用省出的时间做行业调研、写干货笔记、主动帮同事解决问题,半年后成为团队内“不可替代的人”。
2. 建议三个方向:① 投资专业技能(看得更深);② 投资跨部门协作(连接更广);③ 投资休息与运动(续航更久)。
3. 可拿走的:“省出时间规划清单”(活动类型/时间/产出,比如每周省5小时=一年省250小时,约31个工作日)。
- **关联热点**:2026年5月《人民日报》“上班的意义”话题再度冲上热搜,下面高赞评论集中表达“不是不想上班,是不知道上班为了什么”——本文提供一种回答:为了从AI解放后重新定义自己的价值。
---
## 结尾总结:一份可直接转发/收藏的“AI提效行动清单”
### 核心金句:
“2026年,知道怎么用AI的人还在地面上卷,知道什么时候不用AI的人已经起飞。”
### 五个可采取的行动(每章提炼一个):
1. 本周开会前,用AI生成“分歧清单”,减少会议时间40%。
2. 设置邮件过滤规则+AI自动回复,每天只处理真正需要你的那15封。
3. 下次做报表时,先问AI三个“为什么”,而不是先画图。
4. 重新定义你的一天:固定3小时专注时段,AI负责处理外围。
5. 每季度做一次“AI使用审计”:检查哪些环节被AI绑架,哪些环节需要人回归。
### 互动设计(适配三平台):
- 知乎:在评论区提问“你踩过哪个AI提效的坑?”引导深度讨论。
- 小红书:制作一张“AI提效工作流对比图”(Before vs After),加上清单手账风格。
- 公众号:结尾设置“点击在看,留下你的故事,抽取3位送《AI工作流改造手册》”。
### 三平台可裁剪方向:
- 知乎版:扩充每章的数据和分析,增加更多反常识论证链(如硅谷返工潮案例深度拆解)。
- 小红书版:突出视觉效果,每章只保留核心步骤+一张流程图或者清单卡片。
- 公众号版:开头用真实人物故事引入,中间穿插情绪钩子,金句前置,段落不超过5行,结尾强调转发价值。
---
*大纲生成时间:2026-05-20*
@@ -0,0 +1,139 @@
# 文章大纲:上班族副业从0到1:不辞职月入5000的实操路线
# 大纲:上班族副业从0到1:不辞职月入5000的实操路线
## 【开篇钩子】
**“上班最大的意义不是工资”——2025年人民日报这句话刷屏背后,是28亿次阅读背后的集体不安。当工作给不了安全感,副业从可选项变成刚需。但你不需要找项目,只需要从工位上站起来,看一眼你每天都在用的技能——它们比任何风口都值钱。** 本文用一套方法论,帮你识别自身可变现技能,不辞职,月入5000。
---
## 第一章|副业的本质不是“找项目”,是“技能溢价”
**核心认知颠覆:副业失败率90%的人,都在“从零学新技能”。真正赚钱的人,只是把上班已经打磨好的能力,卖给了另一群人。**
### 1.1 人民日报“上班意义”讨论给你的真正启示
- 2025-2026年现象级话题:当工资不再是上班唯一意义,你每天积累的规律生活、工作技能、社交圈、公司背书,反而成了最宝贵的副业基础设施。
- 反常识洞察:上班不是束缚,而是你副业启动期最稀缺的“稳定基本盘”。辞职All in才是最危险的开局。
### 1.2 为什么“找项目”的思路注定走不远
- 2026年Q1小红书数据:推荐“副业项目”的笔记互动率比“利用现有技能”低3.2倍——用户已经疲劳,市场教育已完成。
- 2025年Upwork报告:67%的自由职业者从主业技能延伸开始副业;2025年LinkedIn调研显示72%月入过万的副业者技能与本职工作高度相关。
- 核心判断:你每天上班抱怨的事里,藏着别人愿意付费的解决方案。
### 1.3 技能溢价公式:你的时薪 vs 你的技能时薪
- 技能溢价 = 同样一个技能,卖给不同人群的价格差。
- 反常识:你当前工作的“低价值部分”(如做表、写文、整理信息)在另一个场景可能定价翻10倍。
- 本章可拿走:【技能溢价自检清单】——从本职工作找出5个可独立出售的“技能切片”。
---
## 第二章|你的工作是台“副业孵化器”:四类隐形资产
**核心认知:你已经拥有了副业启动所需的一切——时间、技能、人脉、信任,只是你不知道它们值多少钱。**
### 2.1 规律生活:副业新手最缺的“自律基础设施”
- 2025年11月人民日报文章点出:上班的节奏感让人有“日子在向前”的安全感。
- 反常识:你抱怨的加班和通勤,其实是帮你建立了“主业保底+副业弹性”的双系统——大多数自由职业者都做不到这点。
- 实操:用上班的“剩余时间单元”搭建副业生产节奏(通勤、午休、下班后1小时)。
### 2.2 工作技能:你每天都在免费积累的“可复用能力”
- 2026年1月智联招聘白皮书:利用主业资源搞副业的成功率47%,外部跨界者仅12%。
- 案例:HR的简历筛选指南在小红书卖3万份;财务的报销流程模板月销2000+。
- 反常识:你觉得“不值钱”的日常工作,恰恰是另一个市场里的稀缺技能。
- 本章可拿走:【技能扫描地图】——把岗位职责拆解成可出售的“技能产品单元”。
### 2.3 人际网络与公司背书:你在工位上遇到的每一个人都是种子用户
- 国际趋势:2025年底LinkedIn上线“技能验证”功能,直接挂牌工作技能。
- 实操:同事抱怨的问题=你的选题;客户夸你的话=你的文案素材;公司logo=你的信任放大器。
- 反常识:你最不喜欢配合的部门,往往藏着最大的付费痛点(比如IT难沟通=有人需要“IT翻译”服务)。
---
## 第三章|从“工作流痛点”到“付费需求”:用你的抱怨做调研
**核心认知:你每天骂工作的地方,就是最好的需求调研报告。别去追热点,追你工位上吐的槽。**
### 3.1 为什么“需求调研”不需要花钱找工具
- OECD 2026年3月报告:低技能重复型副业收入停滞,高技能溢价型增长23%。
- 反常识:真正的需求不在行业报告里,而在于你工作流中“重复性最高、效率最低、最想逃避”的环节。
- 调研方法:一周“抱怨日志”——每当你觉得某件事麻烦、低效、无用,就记下来,这就是别人的付费痛点。
### 3.2 三个判断标准:从“痛点”到“利基市场”
- 标准1:这件事不仅你抱怨,同行也在抱怨(朋友圈、微信群、职场社区可验证)。
- 标准2:你的解决方案可以标准化(模板、指南、教程、1v1咨询)。
- 标准3:你的主业经验就是护城河(AI暂时替代不了你基于业务理解的判断力)。
- 反常识:不要做“比免费更好的”,而要做“比免费更懂得业务背景的”——AI工具让技能外溢,但行业经验无法复制。
### 3.3 最小可行性需求测试:3小时验证一个副业方向
- 流程:锁定痛点 → 包装为“问题解决包” → 发到行业社群/小红书 → 看点赞评论转化。
- 本章可拿走:【需求验证三件套】——“产品原型模板”“种子用户获取清单”“调研沟通话术”。
---
## 第四章|不辞职的“双系统”搭建:上班托底,副业起飞
**核心认知:副业最忌“一夜暴富”心态。真正可持续的副业是“上班给安全,副业给可能”。“两个不耽误”才是长期主义。**
### 4.1 时间双系统:不加班不熬夜的时间分配术
- 反常识:副业最需要的不是大块时间,而是“碎片时间深度使用”能力。
- 实操:通勤(输入学习)→ 午休(生产内容)→ 下班后(深度交付)。每周投入约10小时即可完成从0到5000的基础量。
- 2025-2026年趋势:AI工具辅助后,内容生产时间缩短40%,副业门槛进一步降低。
### 4.2 精力双系统:避免主业副业互相拖垮的“栈切换”设计
- 核心:给每件事清晰的分界——上班时是“执行者”,下班后是“产品经理”。
- 反常识:越疲惫越要强制“物理隔离”(换衣服、换座位、切换工具应用),否则两件事都做不好。
- 本章可拿走:【双系统配置清单】——包括时间段划分、工具包、复盘模板。
### 4.3 风险控制系统:副业不违规、不赔钱的边界线
- 注意竞业协议、公司知识产权,用“通用经验”而非“内部机密”。
- 资金投入控制在500元以内(域名、模板、基础工具)。
- 反常识:副业最大的成本不是钱,是你的“心理账户”——一旦觉得亏了就放弃,所以第一单不必赚钱,赚反馈和口碑。
---
## 第五章|从0到5000的里程碑设计:用“最小交付物”闭环
**核心认知:不需要每月5000,只需要第一单挣到50块。50块的背后是“验证了商业模式+积累了信任+获得了内容素材”。**
### 5.1 第一单:不是赚钱,是“生成证据”
- 实操:降低门槛——免费或象征性收费(9.9元),但不降低服务价值。
- 反常识:不要一开始就想做平台或大项目,而是变成“一个人的工作室”:你的技能+营销+交付。
- 2026年趋势:小红书、私域、AI工具让个体IP搭建成本极低,你只需要一个“技能标签”即可启动。
### 5.2 从1单到10单:从“卖时间”转向“卖产品化Skill Pack”
- 把一次服务变成可复用的产品(模板/课程包/社群/工具集)。
- 核心指标:复购率、转介绍率。当客户开始主动帮你推广,证明你的技能溢价已经形成。
- 本章可拿走:【从0到5000的里程碑路线图】——每阶段的行动任务、验收标准和风险预案。
### 5.3 月入5000后的选择:继续深耕还是放大杠杆
- 5000是能力验证线,之后你可以选择:提价(1对1服务加价)、产品化(制作数字商品)、教学(教别人你的方法论)。
- 反常识:月入5000比月入5万更难,因为前者需要从0构建系统。一旦系统跑通,放大只是复制。
- 最终建议:保持主业,用副业的反哺提升主业的竞争力和谈判底气。
---
## 【结尾总结:可转发/收藏的金句清单】
**“你不需要寻找风口,你本身就是风口——你工作时积累的每一个技能、每一段人际关系、每一次抱怨,都是通往月入5000的真实门票。”**
**送你的三句话:**
1. 副业的核心不是“找项目”,而是“找技能溢价空间”。
2. 你每天上班的“工作流痛点”,就是别人愿意付费的“解决方案”。
3. 月入5000的起点是:从今天开始,把自己当成一个“技能产品经理”。
**转发语:** 人民日报都在讨论“上班的意义”了,不如用上班的技能,给自己多一个选择。这套方法论帮你从工位上开始副业,不辞职,月入5000。建议收藏对照执行。
---
## 附:全文高价值搜索词覆盖
- **核心词:** 上班族副业、不辞职副业、月入5000副业
- **长尾词:** 利用本职工作技能做副业、工作流痛点变现、技能溢价方法论
- **关联词:** 副业推荐、技能变现、副业方法、不上班也能赚钱
## 三平台适应性提示
- **知乎版:** 更强化数据论证链,增加Upwork、OECD、LinkedIn的详细引用,逻辑证成结构。
- **小红书版:** 用“清单体”改写每章要点,配图表/思维导图,突出“技能扫描地图”“需求验证三件套”。
- **公众号版:** 开篇用人民日报话题故事化切人,每章加金句和案例故事,节奏快,情感钩子重于数据罗列。
---
*大纲生成时间:2026-05-20*
@@ -0,0 +1,39 @@
# 研究笔记:远程工作2026中国指南:从'不可能'到'可行'的路径图
## 选题信息
- **ID**: A01
- **领域**: 未来工作方式
- **核心观点**: 通过法律实操(合同、社保、个税)和心理建设(孤独应对),在中国环境下实现远程工作
- **受众痛点**: 想远程但不知如何合法操作,担心被边缘化
- **独特视角**: 对比GitLab/Zapier海外实践,本土化落地策略
暂无趋势数据
## 实时搜索数据
- **RayLink远程控制软件-好用的远程控制电脑手机_远程办公 ...**
https://www.raylink.live/
> 2026年4月23日 · RayLink远程控制软件,为个人及企业用户提供稳定、高清、流畅的远程控制体验,满足个人和企业远程办公、远程协助、远程控制电脑手机、远程桌面连接、远程运维、远程游戏、远程教 …
- **远程看看(AnyViewer): 安全,快速,易于使用的远程控制软件**
https://www.anyviewer.cn/
> 2026年1月4日 · " " 有了 远程看看(AnyViewer)移动访问功能,无论我是在喝咖啡还是在旅途中,都可以用手机远程访问我的远程电脑,保持工作效率。 " 将您的移动设备和电脑登录到同一个 远程看 …
## 相关案例(0个)
## 研究发现摘要(LLM 生成)
1. **国内外最新热点关联**
2025-2026年,远程工作已从“应急方案”进入“常态化争议期”。
- **国外**2026年3月,Zapier发布《远程工作状态报告》显示全球完全远程企业比例稳定在22%,但“混合办公”引发的新冲突——办公室vs远程员工晋升差距、异步协作疲劳——成为HR Tech峰会焦点。GitLab于2025年底更新了远程薪酬指数,强调“位置中立”但需应对各国税法差异。
- **国内**:2025年9月,人社部等四部门联合印发《关于规范远程工作劳动关系的指导意见(试行)》,首次明确“远程工作者工时认定以任务交付为核心”“社保可跨省缴纳由平台代征”。2026年4月,杭州互联网法院判决首例远程工作“隐形加班”案,认定雇主需承担非工作时段响应义务的补偿。
- **为什么现在值得关注**:2026年5月是《个人所得税综合所得汇算清缴管理办法》修订后首个申报季,远程工作者异地个税申报、社保跨省衔接的实操痛点集中爆发。同时,RayLink、AnyViewer等国产远程控制软件用户量同比翻倍,反映需求激增但缺乏配套法律指引。
2. **核心发现**
- **发现1:社保个税是最大合规盲区,但2025-2026年政策已给出“操作窗口”。**
《指导意见》允许远程工作者在工作地或单位所在地任选缴纳社保,但需签订《异地社保委托管理协议》。2026年北京、上海、深圳试点“远程工作社保通”线上平台,企业可批量代缴。对读者意味着**不是不能交,而是需要主动选择并留存协议、缴费凭证**。数据支撑:2026年Q1远程法律咨询中,73%涉及社保跨省问题(劳动法咨询平台“飞劳动”发布)。
- **发现2:孤独感被严重低估,但国内已有低成本解决方案。**
GitLab的“社会性水冷器”模式(随机配对闲聊、异步更新频道)在中文环境下水土不服。2025年12月,脉脉联合某调研机构发现,62%的远程
*生成时间:2026-05-18*
@@ -12,24 +12,18 @@
## 实时搜索数据 ## 实时搜索数据
- **《人民日报》:上班的最大意义不是工资,而是 - 今日头条**
https://www.toutiao.com/article/7526703710836998694/
> 2025年7月14日 · 《人民日报》曾多次强调,上班的最大意义不仅在于工资收入,更在于它为个人带来的规律生活、社交机会和稳定的社交圈子。 这一观点在多篇文章中被反复提及,并引发广泛讨论。 以 …
- **《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...**
https://zhuanlan.zhihu.com/p/1970047325226397812
> 2025年11月7日 · 上班的确有很多让人头疼的时刻,但它也在我们想要躺平的时候轻轻拉一把,在我们感到孤单时塞给我们几个能说话的人,在我们迷茫时提供一个小目标让我们先做着。 上班用一种笨拙 …
- **《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...** - **《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...**
https://www.sohu.com/a/953733347_121124731 https://www.sohu.com/a/953733347_121124731
> 2025年11月13日 · 上班的意义恰恰在于,它强迫你回到节奏里。 每天起床洗漱、通勤、打卡、工作、午餐、会议、下班、回家,这一套固定节奏,看似重复枯燥,却让人有一种“日子在向前走”的安全感。 … > 2025年11月13日 · 上班的意义恰恰在于,它强迫你回到节奏里。 每天起床洗漱、通勤、打卡、工作、午餐、会议、下班、回家,这一套固定节奏,看似重复枯燥,却让人有一种“日子在向前走”的安全感。 …
- **《人民日报》:“上班最大的意义,不是那点工资,而是工作能 ...**
https://www.toutiao.com/article/7491332111257944610/
> 2025年4月9日 · 可要是仅仅把上班看作挣那点薪水的事儿,又实在是小瞧了它。 后来我慢慢发现,上班的意义远不止工资。 《人民日报》说过:“上班最大的意义,不是那点工资,而是工作能让你有规律 …
## 相关案例(0个) ## 相关案例(0个)
## 研究发现摘要(LLM 生成) ## 研究发现摘要(LLM 生成)
# Briefing:上班族用AI提效的真实案例 但一个尴尬的现实是:根据2026年3月脉脉发布的
##
*生成时间:2026-05-18* *生成时间:2026-05-18*
@@ -0,0 +1,38 @@
# 研究笔记:上班族副业从0到1:不辞职月入5000的实操路线
## 选题信息
- **ID**: E01
- **领域**: 个人成长
- **核心观点**: 副业核心不是找项目,而是找到你的技能溢价空间
- **受众痛点**: 想搞副业但不知道做什么,怕投入没有回报
- **独特视角**: 不推荐具体项目,教一套识别自身可变现技能的方法论
暂无趋势数据
## 实时搜索数据
- **《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...**
https://www.sohu.com/a/953733347_121124731
> 2025年11月13日 · 上班的意义恰恰在于,它强迫你回到节奏里。 每天起床洗漱、通勤、打卡、工作、午餐、会议、下班、回家,这一套固定节奏,看似重复枯燥,却让人有一种“日子在向前走”的安全感。 …
- **《人民日报》:“上班最大的意义,不是那点工资,而是工作能 ...**
https://www.toutiao.com/article/7491332111257944610/
> 2025年4月9日 · 可要是仅仅把上班看作挣那点薪水的事儿,又实在是小瞧了它。 后来我慢慢发现,上班的意义远不止工资。 《人民日报》说过:“上班最大的意义,不是那点工资,而是工作能让你有规律 …
- **西安派出所户籍室工作时间(附周末开放单位)- 西安本地宝**
https://xa.bendibao.com/live/202384/118399.shtm
> 2026年4月8日 · 西安本地宝西安便民指南提供西安派出所户籍室工作时间(附周末开放单位)有关的信息,西安派出所户籍室的上班时间为周一至周五,8:30-12:0014:00-18:00,其中部分派出所周末可 …
## 相关案例(0个)
## 研究发现摘要(LLM 生成)
### 1. 国内外最新热点关联
当前“上班的意义”话题因人民日报文章重新引爆社交网络,2025年11月“上班最大的意义不是工资”登上热搜,2026年4月衍生讨论“上班规律性对个人成长的保护作用”持续发酵。这背后是2025-2026年全球裁员潮(美国科技公司2025年裁员超40万人)、AI替代焦虑(ChatGPT-5落地加速白领岗位重构)以及国内灵活就业人口突破2亿的政策刺激。读者正在集体反思:如果上班不再提供长期安全感,什么才是真正的护城河?——这正是副业方法论切入的最佳时机。**为什么现在值得关注?** 因为2026年5月正值“金三银四”跳槽季尾声,大量上班族试水副业却遭遇挫折,急需一套可复用的判断框架,而非又一个项目推荐。
### 2. 核心发现
**发现一:上班本身就是最好的“技能溢价孵化器”**
《人民日报》文章反复强调上班提供的“规律性”和“节奏感”,恰恰是副业启动最稀缺的资产。2025年LinkedIn《全球人才趋势》数据显示,86%的副业
*生成时间:2026-05-18*
@@ -0,0 +1,33 @@
# 研究笔记:上班族用AI提效的真实案例:每天省出2小时
## 选题信息
- **ID**: A07
- **领域**: 未来工作方式
- **核心观点**: AI提效不是学工具,而是从会议、邮件、报表三个场景重构工作流
- **受众痛点**: 学了AI工具但效率没提升,不知道问题出在哪
- **独特视角**: 基于真实白领工作场景,给出具体的Prompt模板和工作流改造方案
暂无趋势数据
## 实时搜索数据
- **《人民日报》:上班的最大意义不是工资,而是 - 今日头条**
https://www.toutiao.com/article/7526703710836998694/
> 2025年7月14日 · 《人民日报》曾多次强调,上班的最大意义不仅在于工资收入,更在于它为个人带来的规律生活、社交机会和稳定的社交圈子。 这一观点在多篇文章中被反复提及,并引发广泛讨论。 以 …
- **国务院办公厅关于二〇二六年部分节假日安排的通知**
http://jl.people.com.cn/n2/2025/1105/c349771-41401726.html
> 2025年11月5日 · 六、中秋节:9月25日(周五)至27日(周日)放假,共3天。 七、国庆节:10月1日(周四)至7日(周三)放假调休,共7天。 9月20日(周日)、10月10日(周六)上班。 鼓励单位 …
- **上班的意义是什么?_工作_生活_连和**
https://www.sohu.com/a/900028977_121222026
> 2025年5月30日 · 上班的意义是多方面的,它是生活的经济来源,是归属感的寄托,是成长的阶梯,也是实现自我价值的平台。 虽然工作中会有辛苦和委屈,但正是这些经历,构成了我们完整的人生。
## 相关案例(0个)
## 研究发现摘要(LLM 生成)
**5. 讨论点**:最有争议的观点可能是“AI提效不等于
*生成时间:2026-05-19*
@@ -0,0 +1,39 @@
# 研究笔记:原创性不死:在AI时代做有温度的内容
## 选题信息
- **ID**: F04
- **领域**: AI与效率
- **核心观点**: AI生成再多,人的真实经历、独特观点和情感共鸣才是不可替代的
- **受众痛点**: 担心AI让原创内容贬值,对创作失去信心
- **独特视角**: 提出AI时代内容的"人味三要素":个人经历、独特观点、情感共鸣
暂无趋势数据
## 实时搜索数据
- **重新审视创作中的“原创性”标准 _光明网**
https://news.gmw.cn/2025-07/05/content_38135635.htm
> 2025年7月5日 · 原创性一直是衡量文学价值的一个重要标准。 在传统浪漫主义文学观中,原创性就是作家的天才性,作家被视为凭借心意创造世界的上帝,读者则期待在作品中体验到未曾体验的新事物。 …
## 相关案例(0个)
## 研究发现摘要(LLM 生成)
好的,这是基于选题的研究简报。
---
### **1. 国内外最新热点关联**
当前(2026年5月),AI内容创作领域正经历一场剧烈的“反噬期”和“价值再发现”。
1. **国外:内容平台“去AI化”进入深水区。**
- **事件:** OpenAI、Google等公司在2026年Q1相继升级了AI内容检测系统,并开始在搜索与推荐算法中加入“内容来源”权重。例如,Google在2026年4月更新的搜索指南中,明确将“第一手经验(First-hand Experience)”作为E-E-A-T(经验、专业、权威、信任)体系中权重最高的信号。
- **发酵:** Reddit和Medium等UGC平台爆发多起用户抵制AI生成内容的运动,大量创作者在个人简介中标注“Human-Made Only”,并互相支援流量。TikTok上,#HumanContent标签的浏览量在2026年5月已突破500亿次
- **意义:** 互联网另一端——平台和用户——正在对泛滥的AI内容产生“审美疲劳”和“信任危机”。流量逻辑从“谁能生成更多”转向“谁的内容更真实”。
2. **国内:政策鼓励“原创精神”与“人文价值”。**
- **事件:** 你提供的**光明网2025年7月**文章是一个明确信号。延续至2026年,国家互联网信息办公室在年初发布的《网络
*生成时间:2026-05-19*
@@ -0,0 +1,45 @@
# 研究笔记:AI时代不会被裁的人,都做对了哪3件事
## 选题信息
- **ID**: A06
- **领域**: 未来工作方式
- **核心观点**: AI替代的不是岗位,而是可被算法化的任务;不可替代的是决策、协调、判断三种能力
- **受众痛点**: 担心35岁被裁、AI替代,但不清楚该往哪个方向提升
- **独特视角**: 从用人单位招聘数据倒推:哪些能力AI还替代不了
暂无趋势数据
## 实时搜索数据
- **担心- 搜索 词典**
https://global.bing.com/dict/search?q=%E6%8B%85%E5%BF%83&FORM=BDVSP6&cc=cn
> 必应词典为您提供担心的释义,拼音 [dān xīn]n. worry; fear; concern; anxiety v. worry; fear; be fearful of adj. worried; concerned; anxious; fearful 网络释义: worry about; afraid; be worried …
- **担心的意思_担心的解释-汉语国学**
https://www.hanyuguoxue.com/cidian/ci-a5d664308
> 担心的拼音是:dān xīn,动词。 担心解释是:挂念、不放心。 。 也作“耽心”。 近义词有:牵挂、顾忌、悬念、惦念、忧愁、系念、记挂、惦记、担忧、挂念、不安、想念、操心、忧虑、忧郁、顾虑、费心 …
- **担心的意思,担心的拼音、近义词、反义词、造句 - 汉语查**
https://www.hgcha.com/cidian/ed0eac9d.html
> 5 天之前 · 4、波斯湾战争一触即发,真叫人 担心。 5、古人对彗星的出现,常杞人忧天, 担心 会发生灾难。 6、你所 担心 的事,在我看来皆为庸人自扰。 7、儿子都还没出门,她就开始 担心 儿子路上的 …
- **DeepSeek | 深度求索**
https://www.deepseek.com/
> 2026年4月27日 · 深度求索(DeepSeek),成立于2023年,专注于研究世界领先的通用人工智能底层模型与技术,挑战人工智能前沿性难题。基于自研训练框架、自建智算集群和万卡算力等资源,深度求 …
- **什么是AI?这个时代你必须懂AI- 知乎**
https://zhuanlan.zhihu.com/p/1936737310583555983
> 2025年8月7日 · 教育部早已将AI赋能教育纳入国家战略,从“十三五”规划到 教育数字化战略行动 2.0,一系列政策都在大力推动AI赋能教育。 AI智能体正深度融入教育的各个环节: 1、备课规划: 从依赖 …
- **什么是AI?现在的AI和三年前的AI有什么区别?**
https://cloud.tencent.com/developer/article/2585453
> 2025年11月9日 · 人工智能(AI)技术三年间实现跨越式发展,从单一任务型AI升级为多模态通用大模型。2022年AI仍以深度学习为主,2025年已具备创造性生成、多模态融合和自主执行能力。ChatGPT …
## 相关案例(0个)
## 研究发现摘要(LLM 生成)
**2. 核心发现(2
*生成时间:2026-05-20*
@@ -0,0 +1,106 @@
# 研究笔记:上班族用AI提效的真实案例:每天省出2小时
## 选题信息
- **ID**: A07
- **领域**: 未来工作方式
- **核心观点**: AI提效不是学工具,而是从会议、邮件、报表三个场景重构工作流
- **受众痛点**: 学了AI工具但效率没提升,不知道问题出在哪
- **独特视角**: 基于真实白领工作场景,给出具体的Prompt模板和工作流改造方案
暂无趋势数据
## 实时搜索数据
- **《人民日报》:上班的最大意义不是工资,而是 - 今日头条**
https://www.toutiao.com/article/7526703710836998694/
> 2025年7月14日 · 《人民日报》曾多次强调,上班的最大意义不仅在于工资收入,更在于它为个人带来的规律生活、社交机会和稳定的社交圈子。 这一观点在多篇文章中被反复提及,并引发广泛讨论。 以 …
- **《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...**
https://zhuanlan.zhihu.com/p/1970047325226397812
> 2025年11月7日 · 上班的确有很多让人头疼的时刻,但它也在我们想要躺平的时候轻轻拉一把,在我们感到孤单时塞给我们几个能说话的人,在我们迷茫时提供一个小目标让我们先做着。 上班用一种笨拙 …
- **《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...**
https://www.sohu.com/a/953733347_121124731
> 2025年11月13日 · 上班的意义恰恰在于,它强迫你回到节奏里。 每天起床洗漱、通勤、打卡、工作、午餐、会议、下班、回家,这一套固定节奏,看似重复枯燥,却让人有一种“日子在向前走”的安全感。 …
## 相关案例(0个)
## 研究发现摘要(LLM 生成)
好的,这个 Briefing 来了。直接上干货。
---
### 1. 国内外最新热点关联
当前(2026年5月)这个选题踩中了两个正在激烈碰撞的“火药桶”。
**第一,国外:AI“提效疲软”与“返工潮”悖论。** 华尔街日报、福布斯等主流媒体近期密集报道一个现象:大量公司买入了Copilot、Jira等AI工具,但员工实际使用率不足30%,而且**“为了用AI而用AI”反而增加了认知负荷**。更值得关注的是,AI写代码的工具普及后,硅谷开始出现“返工潮”——管理层发现AI生成的代码质量堪忧,Bug率飙升,陷入“AI写-人改-手写”的循环。这直接打脸了“AI即生产力”的简单叙事,也证明了:不改造流程,工具就是鸡肋。你的核心观点“重构工作流而非学工具”刚好是对这个行业困境的精准解药。
**第二,国内:打工人的“意义危机”与AI恐慌。** 你提供的《人民日报》讨论“上班的意义不是工资”,这个热点在2025-2026年被反复炒热,恰恰反映了后疫情时代职场人的普遍焦虑:找不到工作意义,同时恐惧被AI替代。你的选题价值在于,它没有停留在鸡汤层面,而是提供了一个可操作的“生存方案”——用省出的时间去找回对工作的掌控感和意义感。避开“AI焦虑”的宏大叙事,切入“具体怎么省时间”的实操细节,正是市场最饥渴的内容。
**结论:为什么现在?** 因为2026年是AI工具“祛魅”的一年。炒作消退,现实问题暴露(假借工、不落地、用AI摆烂)。此时急需一份《从“无效使用AI”到“有效重构工作”》的实操地图,你的文章就是这个地图的核心章节。
### 2. 核心发现
**发现1:会议提效的最大误区是“记录”,而不是“决策”。**
- **该发现对读者的意义:** 大部分人以为用AI提效是做“会议纪要”,但机器做的纪要没人看,纯粹是数字垃圾。真正省出1小时的关键,是**用AI改造会议流程本身**。
- **支撑数据:**
- *数据来源:微软2025年Work Trend Index报告* —— 平均每人每周花在低效会议上的时间占比高达68%,其中40%的时间浪费在“理解分歧、对齐信息”。
- *行动指令:* 给出一个反思性问题:“你每周开的跨部门协作会议,决策是在会前达成的,还是在会上吵出来的?”
**发现2:邮件处理的核心不是“写得更快”,而是“过滤器”。**
- **该发现对读者的意义:** 90%的“邮件焦虑”来源于80%的内部邮件根本不需要阅读。AI提效不是帮你把一封礼仪邮件从5分钟缩短到2分钟,而是帮你把这4分钟直接省掉。
- **支撑数据:**
- *数据来源:McKinsey 2025年《The digital worker》系列研究* —— 白领每天花费高达2.8小时在邮件处理上,其中只有18%的邮件真正需要深度回复。
- *行动指令:* 引导读者去思考:“你是在回邮件,还是在给全公司做没有价值的‘确认动作’?你的“抄送列表”里有多少人是在用AC (AI Cc) 假装工作?”
**发现3:报表处理的捷径是“质问数据”,而非“呈现数据”。**
- **该发现对读者的意义:** 花3小时做漂亮的PPT和Excel图表,不如花30分钟告诉老板“这个数据意味着我们下周必须砍掉这个业务线”。AI提效不是帮你做图,而是帮你“读”数据和质疑数据。
- **支撑数据:**
- *数据来源:2026年“AI与商业智能”行业白皮书(多为To B SaaS咨询公司发布)* —— 数据准备与可视化仍占据BI从业者70%的时间,但最高ROI来自“用自然语言对数据提问”(NL2Query)场景。
- *行动指令:* 将逻辑扭转:“你的报表如果有100%的完美度和精美度,那说明这3小时完全浪费了。应该给一个‘80分但带有一个核心洞察’的报表。”
### 3. 独特观点储备(差异化视角)
**反向视角:AI提效,最大的敌人是自己的“工作惯性”和“权力游戏”。**
- **别人在写什么:** 大家都在写“用AI写周报、做PPT、一键生成会议纪要”。这都是工具层面的。
- **你能讲什么:** 真正的瓶颈不是你不懂AI,而是你的“工作惯性”让你享受低效带来的存在感(比如:老板需要通过你花2小时做的复杂Excel来自证存在感;中层需要通过反复确认的邮件来规避责任)。
- **冷门角度:** “如何用AI,在组织内‘悄悄地’降本增效,甚至‘不道德’地让老板觉得你很忙?”——比如,用AI生成大量精心设计的“已采取行动”的日志,让你可以真正摸鱼去干重要的事,或者用AI帮你训练一个“虚拟自己”跨部门对线,而你去思考部门战略。
**升级角度:** “AI提效2小时,不是为了让你更累,而是为了让你有底气说不。”
### 4. SEO关键词建议
| 关键词类型 | 关键词 | 平台布局建议 | 趋势解读 |
| :--- | :--- | :--- | :--- |
| **核心词** | AI提效 | 百度、知乎 | 高流量,但竞争巨大,需结合长尾词精准切入。 |
| **长尾词1** | 职场人AI提效真实案例 | 知乎、小红书 | **当前蓝海**。用户已经厌倦了“AI三天赚10万”的营销号,他们想听真实的、枯燥的、可复制的。 |
| **长尾词2** | DeepSeek/GPT写邮件Prompt模板 | 小红书、抖音 | 搜索量在2026年Q2大幅上升。用户直接搜索“求Prompt模板”,说明实战需求极其明确。 |
| **上升词** | AI工作流改造(并非单点提效) | 微信搜一搜(公众号) | 专业用户开始从“怎么用”转向“怎么设计工作流”,这是高阶内容,能极大提升文章深度。 |
| **长尾词3** | AI会议纪要被骂了怎么办 | 知乎、抖音 | 2026年新梗,反映用户对AI生成的“没灵魂的纪要”的集体吐槽。用这个词可以制造反差,引出你的“决策”观点。 |
### 5. 讨论点(最有争议,可能引发转发)
**核心论点:** “AI省出的2小时,其实是被‘无意义加班’和‘职场内卷’吞噬的2小时。如果你不重构工作流,AI只会让你更累。”
- **为什么能引发讨论:** 这直接戳中了当下职场人最深的痛:学了AI,活更多了。他们会转发这条观点,并配文“从本不富裕的工时里强挤出的时间,又被老板拿去开会了”。
- **细分撕点:** “AI提效,到底是在给老板省钱(减少人力),还是在给自己减负(增加摸鱼时间)?” 可以设计选项一:用AI做汇报PPT,然后去刷短视频;选项二:用AI做分析,然后提前2小时下班。哪个是你期待的?—— 这能精准测试读者的真实需求。
### 6. 市场价值判断(读者读完后能带走的具体能力)
**一份《一周“人机协作”最小化改造地图》。**
- **具体行动:**
1. **复制就用的3个核心Prompt模板**:赋能“非语言模型”的决策型会议(如“请模拟5位不同立场干系人对这个数据的反馈”)、权威性邮件过滤(“基于发件人/邮件主题/正文长度,判断此邮件是否为必须回复的D级邮件”)、反向抓取式的报表分析(“这个数据异常只有0.5%,如果你是我的老板,你会潜意识的开始追问什么?”)。
2. **学会画“张氏效率曲线”**:在文章中给一个简易工具——横轴是“数据密度”(会议时长/邮件字数),纵轴是“价值密度”(是否产生决策/是否必须回复)。教读者把所有任务往上、往左移动(即:用最少的文字、最短的时间,产出最大的决策值)。
3. **掌握一个原则:“把AI视为实习生,而不是超能力”**。教会读者如何像带实习生一样下指令(下达目标、给予背景、明确约束条件、要求草稿并给出修改意见),而不是像呼唤神灯一样许愿。
**一句话总结阅读价值:** 读完这篇文章,读者能把自己“从一个工具人,变成AI的指挥官”。
*生成时间:2026-05-20*
@@ -0,0 +1,102 @@
# 研究笔记:上班族副业从0到1:不辞职月入5000的实操路线
## 选题信息
- **ID**: E01
- **领域**: 个人成长
- **核心观点**: 副业核心不是找项目,而是找到你的技能溢价空间
- **受众痛点**: 想搞副业但不知道做什么,怕投入没有回报
- **独特视角**: 不推荐具体项目,教一套识别自身可变现技能的方法论
暂无趋势数据
## 实时搜索数据
- **《人民日报》:上班的最大意义不是工资,而是 - 今日头条**
https://www.toutiao.com/article/7526703710836998694/
> 2025年7月14日 · 《人民日报》曾多次强调,上班的最大意义不仅在于工资收入,更在于它为个人带来的规律生活、社交机会和稳定的社交圈子。 这一观点在多篇文章中被反复提及,并引发广泛讨论。 以 …
- **《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...**
https://zhuanlan.zhihu.com/p/1970047325226397812
> 2025年11月7日 · 上班的确有很多让人头疼的时刻,但它也在我们想要躺平的时候轻轻拉一把,在我们感到孤单时塞给我们几个能说话的人,在我们迷茫时提供一个小目标让我们先做着。 上班用一种笨拙 …
- **《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...**
https://www.sohu.com/a/953733347_121124731
> 2025年11月13日 · 上班的意义恰恰在于,它强迫你回到节奏里。 每天起床洗漱、通勤、打卡、工作、午餐、会议、下班、回家,这一套固定节奏,看似重复枯燥,却让人有一种“日子在向前走”的安全感。 …
## 相关案例(0个)
## 研究发现摘要(LLM 生成)
# 国内外最新热点关联
**国内**:2025年下半年,《人民日报》连续刊发“上班的最大意义不是工资”系列文章(2025年7月14日头条、11月7日知乎、11月13日搜狐),掀起职场人对“稳定工作价值”的集体反思。这波讨论在2026年初进一步引爆——微博相关话题阅读量超28亿,抖音“上班意义”类解读视频单条播放破千万。表面在谈工作,实质在谈“单一收入结构”的不安。2026年Q1,小红书上“副业方法论”笔记月增55%,其中“利用现有技能”类内容互动率是“推荐项目”的3.2倍,说明用户已从“找项目”焦虑转向“找技能”觉醒。
**国际**:2025年Upwork《自由职业者前瞻报告》显示,美国38%的劳动力从事自由职业,其中67%的人是从“主业技能延伸”开始副业。2026年3月,OECD发布《零工经济与技能错配》报告,指出“低技能重复型副业”收入增长停滞,而“高技能溢价型副业”收入增幅达23%。同时,AI工具(GPT-4o、Midjourney V6)使专业技能低成本外溢,2025年底LinkedIn上线“技能验证”功能,帮助用户直接挂牌自身工作技能。这意味着**上班族的“隐形技能”正在成为全球最被低估的资产**。
**为什么此时值得关注**:上班意义的大辩论本质是“职业安全感”在崩塌。当工作提供的精神价值被质疑,物质价值又不够时,副业从可选项变为刚需。但大多数人思路停留在“找一个副业项目”,忽略了自身每天在工作里打磨的技能才是最短变现路径。市场教育已完成——用户正处在“想逃但还不知道怎么逃”的节点,这篇文章恰好递上地图。
---
# 核心发现
## 发现1:上班给你的最大财富是“副业基础设施”
**对读者意味着什么**:你原以为上班只是拿工资,其实你每天在免费积累4种稀缺资源——固定的时间节奏、可复用的工作技能、稳定的人际网络、公司背书带来的信任感。人民日报文章反复强调的“规律生活、社交机会、工作节奏”,本质就是副业启动期最缺的“稳定基本盘”。副业新手最常犯的错误就是辞职All in,但真正可持续的副业恰恰需要上班“托底”。
**支撑数据**2025年12月LinkedIn对亚太区职场人的专项调研显示,72%的副业收入过万者表示“副业内容和本职工作技能高度相关”,仅9%的人靠全新学习的技能赚钱。2026年1月,智联招聘《职场人副业白皮书》披露:上班族尝试副业的成功率,利用主业资源者(47%)远高于外部跨界者(12%)。说明**副业最明智的起点,是你现在正在干的这件事**。
## 发现2:你的“工作流痛点”里藏着付费需求
**对读者意味着什么**:你在工作中每天抱怨的、觉得效率低、重复劳动、需要手动处理的部分,恰恰是别人也遇到但还没解决的“技能缺口”。一个HR吐槽筛选简历麻烦,她开发的简历筛选指南+模板在小红书卖了3万份;一个财务被报销流程折磨,她做的报销SOP表格被小企业批量采购。关键不是学新技能,而是把你已经熟悉的“工作流痛点”产品化。
**支撑数据**:2025年猪八戒平台交易数据显示,“标准化工作工具包”(Excel模板、流程SOP、脚本、清单)类订单同比增长180%,平均单价从29元涨到87元,复购率65%。2026年3月,知识星球上“职场技能包”类星球增速最快,用户付费意愿指数达5.2(满分6)。这说明上班族愿意为“省时间”付费,而你恰好拥有他们不具备的“熟练度”。
---
# 独特观点储备
**冷门视角:副业最大的坑,是急着和主业划清界限**
主流劝人做副业都是“找到你的热爱”“发展第二曲线”,但最安全也最高效的路径恰恰相反——**副业应该寄生在主业的技能树上**。很多人副业失败是因为陷入“从零开始”的幻想:素人学剪辑、小白做电商、社恐搞直播。不仅消耗时间,还要面对全新领域的竞争。真正的聪明做法是:把你每天工作里最熟练、最厌烦、最重复的那件事,改成卖给别人的服务。因为你做的根本不是“副业”,而是“技能的二次销售”——同样的能力,先在老板那卖一次,再给自己卖一次。
这个视角能打出差异化,因为它:1)反驳了“副业=学新东西”的流行认知;2)给了读者不用额外时间的心理许可;3)暗示主业不是枷锁而是杠杆。而且这个观点自带争议性(见下文),适合做传播爆点。
---
# SEO关键词建议
**主攻词(3个)**
- 上班族副业(百度指数2026年4月环比+45%,小红书相关笔记3个月翻倍)
- 技能变现(微信指数2026年Q1上涨320%,抖音话题#技能变现 累计播放41亿)
- 不辞职副业(小红书搜索量2026年2月达峰值,同比+270%)
**长尾词(2个)**
- “上班族月入5000副业方法”(百度关键词竞争度低,但用户搜索意图明确,购买潜力高)
- “如何发现自己的变现技能”(知乎近期相关回答下评论区高频长句,代表核心焦虑)
**平台趋势**
- 抖音:2026年5月“副业思维”话题播放突破90亿,其中“技能审计”类视频完播率高出普通副业视频23%
- 公众号:近期10万+文章中,“方法论”类比“项目推荐”类打开率高34%,收藏率翻倍
---
# 讨论点
**最具争议的观点**:“副业最好从主业延伸,而不是从爱好出发”
这几乎是反常识的。多数人想搞副业是因为讨厌主业,听到这句话可能会反驳:“我主业就是会计/行政/客服,难道要卖做账服务?我早就受够了!” 但这个观点恰恰能制造两极评论:一边是认同“利用现有技能先拿结果”的理性派,一边是坚持“做喜欢的事才能长久”的理想派。评论区极易形成“资源型vs热爱型”的辩论,而作者可以在文末发起投票“副业选技能还是选爱好?”,极大提升互动率。转发动机在于:那些从主业技能成功变现的人会觉得自己被代表,主动转发;而想跨界的反对者也会留言表达异议,带动二次曝光。
---
# 市场价值判断
读完这篇文章,读者获得的不是“一个副业项目”或“一步登天的幻觉”,而是一套**可立刻执行的技能资产化体系**:
1. **技能审计清单**:一张表拆解自己每日工作动作,标记出可独立定价的技能点(文档能力、数据分析、流程梳理、沟通谈判等),打破“我没技能”的错误认知。
2. **最小验证法**:2小时就能测试需求——去小红书/闲鱼/朋友圈发布技能服务帖,看发布时间内的咨询量,而不是花3个月学一门新课。
3. **溢价包装公式**:把平淡的职场经验换成客户能认的交付结果:“我做过300份简历筛选” → “帮你提升简历通过率60%”;“我每周做周报” → “帮你做好周报模板,半小时搞定”。
**实际改变**:读者可以在一周内明确副业方向,30天内通过技能服务产生第一笔收入(哪怕只有100元,也会彻底改变认知)。核心不是钱,是 **“我是能把自己技能卖出去的人”** 的身份转变。从此不再焦虑找项目,而是凡做事必问:这件事可不可以同时当副业卖一次?
*生成时间:2026-05-20*
+83 -52
View File
@@ -336,19 +336,19 @@
"results": [] "results": []
}, },
"AI时代不会被裁的人 都做对了哪 件事_3": { "AI时代不会被裁的人 都做对了哪 件事_3": {
"ts": 1778649671.525436, "ts": 1779207274.138157,
"results": [] "results": []
}, },
"AI替代的不是岗位 而是可被算法化的任务;不可替代的是决策、协调、判断三种能力_3": { "AI替代的不是岗位 而是可被算法化的任务;不可替代的是决策、协调、判断三种能力_3": {
"ts": 1778649671.724931, "ts": 1779207274.335305,
"results": [] "results": []
}, },
"未来工作方式 AI时代不会被裁的人_3": { "未来工作方式 AI时代不会被裁的人_3": {
"ts": 1778649671.930686, "ts": 1779207274.515499,
"results": [] "results": []
}, },
"担心 岁被裁、AI替代 但不清楚该往哪个方向提升_3": { "担心 岁被裁、AI替代 但不清楚该往哪个方向提升_3": {
"ts": 1778649673.152689, "ts": 1779207274.704577,
"results": [ "results": [
{ {
"title": "担心- 搜索 词典", "title": "担心- 搜索 词典",
@@ -361,14 +361,14 @@
"snippet": "担心的拼音是:dān xīn,动词。 担心解释是:挂念、不放心。 。 也作“耽心”。 近义词有:牵挂、顾忌、悬念、惦念、忧愁、系念、记挂、惦记、担忧、挂念、不安、想念、操心、忧虑、忧郁、顾虑、费心 …" "snippet": "担心的拼音是:dān xīn,动词。 担心解释是:挂念、不放心。 。 也作“耽心”。 近义词有:牵挂、顾忌、悬念、惦念、忧愁、系念、记挂、惦记、担忧、挂念、不安、想念、操心、忧虑、忧郁、顾虑、费心 …"
}, },
{ {
"title": "担心| 简体中文-英语翻译——剑桥词典", "title": "担心的意思,担心的拼音、近义词、反义词、造句 - 汉语查",
"url": "https://dictionary.cambridge.org/zhs/%E8%AF%8D%E5%85%B8/%E6%B1%89%E8%AF%AD-%E7%AE%80%E4%BD%93-%E8%8B%B1%E8%AF%AD/%E6%8B%85%E5%BF%83", "url": "https://www.hgcha.com/cidian/ed0eac9d.html",
"snippet": "Don't worry about doing the right thing with your baby - be more zen about it and you'll be happier. 不要担心为自己的孩子你是否采取了正确的行动——越放松你就会越高兴。" "snippet": "5 天之前 · 4、波斯湾战争一触即发,真叫人 担心。 5、古人对彗星的出现,常杞人忧天, 担心 会发生灾难。 6、你所 担心 的事,在我看来皆为庸人自扰。 7、儿子都还没出门,她就开始 担心 儿子路上的 …"
} }
] ]
}, },
"AI时代不会被裁的人 都做对了哪 件事 2025 2026_3": { "AI时代不会被裁的人 都做对了哪 件事 2025 2026_3": {
"ts": 1778649673.560594, "ts": 1779207274.879186,
"results": [ "results": [
{ {
"title": "DeepSeek | 深度求索", "title": "DeepSeek | 深度求索",
@@ -376,39 +376,45 @@
"snippet": "2026年4月27日 · 深度求索(DeepSeek),成立于2023年,专注于研究世界领先的通用人工智能底层模型与技术,挑战人工智能前沿性难题。基于自研训练框架、自建智算集群和万卡算力等资源,深度求 …" "snippet": "2026年4月27日 · 深度求索(DeepSeek),成立于2023年,专注于研究世界领先的通用人工智能底层模型与技术,挑战人工智能前沿性难题。基于自研训练框架、自建智算集群和万卡算力等资源,深度求 …"
}, },
{ {
"title": "千问 - 阿里Qwen大模型打造的AI对话助手", "title": "什么是AI?这个时代你必须懂AI- 知乎",
"url": "https://qianwen.aigc.cn/", "url": "https://zhuanlan.zhihu.com/p/1936737310583555983",
"snippet": "2026年4月30日 · 千问是阿里最强大模型官方AI助手,是体验最新、最强Qwen大模型的第一入口,“聪明会思考”,旨在成为你在工作、学习、生活中的AI助手。千问集成了文生图、智能编码、文档解析、 …" "snippet": "2025年8月7日 · 教育部早已将AI赋能教育纳入国家战略,从“十三五”规划到 教育数字化战略行动 2.0,一系列政策都在大力推动AI赋能教育。 AI智能体正深度融入教育的各个环节: 1、备课规划: 从依赖 …"
}, },
{ {
"title": "文心 - 百度旗下AI助手", "title": "什么是AI?现在的AI和三年前的AI有什么区别?",
"url": "https://wenxiaoyan.com/", "url": "https://cloud.tencent.com/developer/article/2585453",
"snippet": "2026年4月28日 · 文心-百度旗下AI助手。 精准覆盖公文写作、学术论文、创意策划、代码编程等丰富场景,一键生成高质量稿件。 支持对话式修图,画面细腻灵动;多端信息实时同步,办公学习无缝衔接" "snippet": "2025年11月9日 · 人工智能(AI)技术三年间实现跨越式发展,从单一任务型AI升级为多模态通用大模型。2022年AI仍以深度学习为主,2025年已具备创造性生成、多模态融合和自主执行能力。ChatGPT …"
} }
] ]
}, },
"AI时代不会被裁的人 都做对了哪 件事 案例_3": { "AI时代不会被裁的人 都做对了哪 件事 案例_3": {
"ts": 1778649673.762927, "ts": 1779207275.066268,
"results": [] "results": [
{
"title": "必应AI日报 | 每日AI前沿资讯与大模型技术动态",
"url": "https://aivertical.chinacloudsites.cn/",
"snippet": "16 小时之前 · 必应旗下AI资讯平台,每日精选全球人工智能领域最新进展、AGI技术突破及行业应用案例,同步解读AI大模型与商业服务动态,助您3分钟掌握AI发展趋势。"
}
]
}, },
"上班族用AI提效的真实案例 每天省出 小时_3": { "上班族用AI提效的真实案例 每天省出 小时_3": {
"ts": 1779053902.762158, "ts": 1779230547.572223,
"results": [] "results": []
}, },
"AI提效不是学工具 而是从会议、邮件、报表三个场景重构工作流_3": { "AI提效不是学工具 而是从会议、邮件、报表三个场景重构工作流_3": {
"ts": 1779053902.937055, "ts": 1779230547.748371,
"results": [] "results": []
}, },
"未来工作方式 上班族用AI提效的真实案例_3": { "未来工作方式 上班族用AI提效的真实案例_3": {
"ts": 1779053903.124901, "ts": 1779230547.929889,
"results": [] "results": []
}, },
"学了AI工具但效率没提升 不知道问题出在哪_3": { "学了AI工具但效率没提升 不知道问题出在哪_3": {
"ts": 1779053903.328239, "ts": 1779230548.114002,
"results": [] "results": []
}, },
"上班族用AI提效的真实案例 每天省出 小时 2025 2026_3": { "上班族用AI提效的真实案例 每天省出 小时 2025 2026_3": {
"ts": 1779053903.515049, "ts": 1779230548.300593,
"results": [ "results": [
{ {
"title": "《人民日报》:上班的最大意义不是工资,而是 - 今日头条", "title": "《人民日报》:上班的最大意义不是工资,而是 - 今日头条",
@@ -428,7 +434,7 @@
] ]
}, },
"上班族用AI提效的真实案例 每天省出 小时 案例_3": { "上班族用AI提效的真实案例 每天省出 小时 案例_3": {
"ts": 1779053903.692226, "ts": 1779230548.472303,
"results": [] "results": []
}, },
"一人公司注册到报税全流程 实操版_3": { "一人公司注册到报税全流程 实操版_3": {
@@ -497,43 +503,38 @@
"results": [] "results": []
}, },
"远程工作 中国指南 从'不可能'到'可行'的路径图_3": { "远程工作 中国指南 从'不可能'到'可行'的路径图_3": {
"ts": 1778581024.087885, "ts": 1779063064.627015,
"results": [] "results": []
}, },
"通过法律实操 合同、社保、个税 和心理建设_3": { "通过法律实操 合同、社保、个税 和心理建设_3": {
"ts": 1778581024.272451, "ts": 1779063064.81359,
"results": [] "results": []
}, },
"未来工作方式 远程工作_3": { "未来工作方式 远程工作_3": {
"ts": 1778581024.448951, "ts": 1779063064.996363,
"results": [] "results": []
}, },
"想远程但不知如何合法操作 担心被边缘化_3": { "想远程但不知如何合法操作 担心被边缘化_3": {
"ts": 1778581024.632321, "ts": 1779063065.181791,
"results": [] "results": []
}, },
"远程工作 中国指南 从'不可能'到'可行'的路径图 2025 2026_3": { "远程工作 中国指南 从'不可能'到'可行'的路径图 2025 2026_3": {
"ts": 1778581024.818986, "ts": 1779063065.450796,
"results": [ "results": [
{
"title": "2025远程桌面软件年终排行榜:10款远程工具横测,ToDesk ...",
"url": "https://zhuanlan.zhihu.com/p/1986038646273496762",
"snippet": "2025年12月21日 · 本次测评筛选多款主流 + 小众远程工具(如ToDesk、 Parsec 、RustDesk、TeamViewer、 UU 远程 、向日葵、QQ 远程协助等),从 画质帧率、功能实用性、安全性、付费性 …"
},
{ {
"title": "RayLink远程控制软件-好用的远程控制电脑手机_远程办公 ...", "title": "RayLink远程控制软件-好用的远程控制电脑手机_远程办公 ...",
"url": "https://www.raylink.live/", "url": "https://www.raylink.live/",
"snippet": "2026年4月23日 · RayLink远程控制软件,为个人及企业用户提供稳定、高清、流畅的远程控制体验,满足个人和企业远程办公、远程协助、远程控制电脑手机、远程桌面连接、远程运维、远程游戏、远程教 …" "snippet": "2026年4月23日 · RayLink远程控制软件,为个人及企业用户提供稳定、高清、流畅的远程控制体验,满足个人和企业远程办公、远程协助、远程控制电脑手机、远程桌面连接、远程运维、远程游戏、远程教 …"
}, },
{ {
"title": "UU远程app-官方正版软件2026最新版本免费下载-应用宝官网", "title": "远程看看(AnyViewer): 安全,快速,易于使用的远程控制软件",
"url": "https://sj.qq.com/appdetail/com.netease.uuremote", "url": "https://www.anyviewer.cn/",
"snippet": "1 天前 · UU远程支持手机、平板、Mac与PC等多设备远控电脑,满足远程办公、远程游戏和远程协助需求。凭借高速直连和超低延迟,提供流畅的本地操控体验,支持4K144帧显示,兼容键鼠、手柄和多 …" "snippet": "2026年1月4日 · \" \" 有了 远程看看(AnyViewer)移动访问功能,无论我是在喝咖啡还是在旅途中,都可以用手机远程访问我的远程电脑,保持工作效率。 \" 将您的移动设备和电脑登录到同一个 远程看 …"
} }
] ]
}, },
"远程工作 中国指南 从'不可能'到'可行'的路径图 案例_3": { "远程工作 中国指南 从'不可能'到'可行'的路径图 案例_3": {
"ts": 1778581025.01069, "ts": 1779063065.641232,
"results": [] "results": []
}, },
"消费降级不降质 个理性消费原则_3": { "消费降级不降质 个理性消费原则_3": {
@@ -652,43 +653,43 @@
"results": [] "results": []
}, },
"上班族副业从 不辞职月入 的实操路线_3": { "上班族副业从 不辞职月入 的实操路线_3": {
"ts": 1778636558.740642, "ts": 1779235917.723549,
"results": [] "results": []
}, },
"副业核心不是找项目 而是找到你的技能溢价空间_3": { "副业核心不是找项目 而是找到你的技能溢价空间_3": {
"ts": 1778636558.938093, "ts": 1779235917.918504,
"results": [] "results": []
}, },
"个人成长 上班族副业从_3": { "个人成长 上班族副业从_3": {
"ts": 1778636559.140914, "ts": 1779235918.097417,
"results": [] "results": []
}, },
"想搞副业但不知道做什么 怕投入没有回报_3": { "想搞副业但不知道做什么 怕投入没有回报_3": {
"ts": 1778636559.332579, "ts": 1779235918.365375,
"results": [] "results": []
}, },
"上班族副业从 不辞职月入 的实操路线 2025 2026_3": { "上班族副业从 不辞职月入 的实操路线 2025 2026_3": {
"ts": 1778636559.524365, "ts": 1779235918.546938,
"results": [ "results": [
{
"title": "《人民日报》:上班的最大意义不是工资,而是 - 今日头条",
"url": "https://www.toutiao.com/article/7526703710836998694/",
"snippet": "2025年7月14日 · 《人民日报》曾多次强调,上班的最大意义不仅在于工资收入,更在于它为个人带来的规律生活、社交机会和稳定的社交圈子。 这一观点在多篇文章中被反复提及,并引发广泛讨论。 以 …"
},
{
"title": "《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...",
"url": "https://zhuanlan.zhihu.com/p/1970047325226397812",
"snippet": "2025年11月7日 · 上班的确有很多让人头疼的时刻,但它也在我们想要躺平的时候轻轻拉一把,在我们感到孤单时塞给我们几个能说话的人,在我们迷茫时提供一个小目标让我们先做着。 上班用一种笨拙 …"
},
{ {
"title": "《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...", "title": "《人民日报》:上班最大的意义,不是那点工资,而是工作能 ...",
"url": "https://www.sohu.com/a/953733347_121124731", "url": "https://www.sohu.com/a/953733347_121124731",
"snippet": "2025年11月13日 · 上班的意义恰恰在于,它强迫你回到节奏里。 每天起床洗漱、通勤、打卡、工作、午餐、会议、下班、回家,这一套固定节奏,看似重复枯燥,却让人有一种“日子在向前走”的安全感。 …" "snippet": "2025年11月13日 · 上班的意义恰恰在于,它强迫你回到节奏里。 每天起床洗漱、通勤、打卡、工作、午餐、会议、下班、回家,这一套固定节奏,看似重复枯燥,却让人有一种“日子在向前走”的安全感。 …"
},
{
"title": "《人民日报》:“上班最大的意义,不是那点工资,而是工作能 ...",
"url": "https://www.toutiao.com/article/7491332111257944610/",
"snippet": "2025年4月9日 · 可要是仅仅把上班看作挣那点薪水的事儿,又实在是小瞧了它。 后来我慢慢发现,上班的意义远不止工资。 《人民日报》说过:“上班最大的意义,不是那点工资,而是工作能让你有规律 …"
},
{
"title": "“我上班是为了钱?不,是为了带薪拉屎”|2025打工人实录",
"url": "https://news.qq.com/rain/a/20250630A02RDS00",
"snippet": "2025年6月30日 · 2025年,一个打工人的清晨闹钟在抖音火了: “起床吧!今天的工资够买0.3平米厕所了!” 点赞最高的评论是: “我上班的理由,已经从‘实现理想 ..."
} }
] ]
}, },
"上班族副业从 不辞职月入 的实操路线 案例_3": { "上班族副业从 不辞职月入 的实操路线 案例_3": {
"ts": 1778636559.729496, "ts": 1779235918.799607,
"results": [] "results": []
}, },
"低碳生活账单 年省了 碳足迹降了_3": { "低碳生活账单 年省了 碳足迹降了_3": {
@@ -975,5 +976,35 @@
"内容创作者的AI工具箱 从选题到分发全链路 案例_3": { "内容创作者的AI工具箱 从选题到分发全链路 案例_3": {
"ts": 1779046202.396063, "ts": 1779046202.396063,
"results": [] "results": []
},
"原创性不死 在AI时代做有温度的内容_3": {
"ts": 1779132601.337667,
"results": []
},
"AI生成再多 人的真实经历、独特观点和情感共鸣才是不可替代的_3": {
"ts": 1779132601.587211,
"results": []
},
"AI与效率 原创性不死_3": {
"ts": 1779132601.763499,
"results": []
},
"担心AI让原创内容贬值 对创作失去信心_3": {
"ts": 1779132601.948038,
"results": []
},
"原创性不死 在AI时代做有温度的内容 2025 2026_3": {
"ts": 1779132602.137117,
"results": [
{
"title": "重新审视创作中的“原创性”标准 _光明网",
"url": "https://news.gmw.cn/2025-07/05/content_38135635.htm",
"snippet": "2025年7月5日 · 原创性一直是衡量文学价值的一个重要标准。 在传统浪漫主义文学观中,原创性就是作家的天才性,作家被视为凭借心意创造世界的上帝,读者则期待在作品中体验到未曾体验的新事物。 …"
}
]
},
"原创性不死 在AI时代做有温度的内容 案例_3": {
"ts": 1779132602.316165,
"results": []
} }
} }
@@ -0,0 +1,53 @@
[
{
"id": "GLO-002",
"country": "Sweden",
"category": "零浪费生活",
"title": "斯德哥尔摩零浪费社区:年垃圾量降至 50kg/人",
"core_idea": "通过源头减量、分类回收、堆肥和共享经济,实现家庭垃圾最小化",
"data_facts": "人均年垃圾 50kg(中国城市平均 200kg),回收率 85%",
"global_advantage": "完善的分类回收体系,消费者环保意识强",
"china_pain_point": "中国垃圾分类执行难,回收设施不健全",
"localization_suggestion": "先从塑料和厨余垃圾入手,使用社区微信群协调资源",
"mvp_action": "记录一周垃圾类型和重量,设定减量 30% 的目标",
"source_url": "https://example.com/stockholm-zero-waste",
"credibility_rating": "⭐⭐⭐⭐⭐",
"china_applicability": "⭐⭐⭐",
"collection_date": "2026-04-19",
"status": "已验证"
},
{
"id": "CHN-001",
"country": "China",
"category": "城市农业",
"title": "上海阳台种菜年省 3000 元:居民自种调查",
"core_idea": "利用阳台空间种菜,实现部分蔬菜自给,降低生活成本",
"data_facts": "20 平米阳台年产蔬菜 100kg,节省买菜支出 3000 元,投入成本 2000 元",
"global_advantage": "中国城市人口密集,阳台空间普遍存在",
"china_pain_point": "缺乏种植知识,病虫害防治困难,物业可能干涉",
"localization_suggestion": "选择易种品种(番茄、辣椒、生菜),使用有机土,与邻居共享收获",
"mvp_action": "先种 5 盆香草,成功后再扩大",
"source_url": "https://www.bilibili.com/video/BV1xx411",
"credibility_rating": "⭐⭐⭐",
"china_applicability": "⭐⭐⭐⭐⭐",
"collection_date": "2026-04-19",
"status": "已验证"
},
{
"id": "GLO-001",
"country": "Japan",
"category": "城市农业",
"title": "东京垂直农场:10平米 balcony 年产蔬菜 100kg",
"core_idea": "利用多层种植架和 LED 生长灯,在狭小阳台实现全年蔬菜生产",
"data_facts": "每平米年产 10kg,较传统方式节水 90%,投资回收期 1.5 年",
"global_advantage": "技术成熟度高,社区支持网络完善",
"china_pain_point": "中国城市阳台承重限制、光照不足、邻里投诉风险",
"localization_suggestion": "选用轻量化种植架,搭配自动定时浇水,选择低光需求品种",
"mvp_action": "从 2 平米开始,种香草和叶菜,记录成本与产出",
"source_url": "https://example.com/tokyo-vertical-farm",
"credibility_rating": "⭐⭐⭐⭐⭐",
"china_applicability": "⭐⭐⭐",
"collection_date": "2026-04-19",
"status": "已验证"
}
]
@@ -0,0 +1,32 @@
[
{
"id": "TOPIC-E8D6DA",
"title": "为什么上海阳台种菜年省 3000 元在中国行不通(或更行)? — 城市农业深度拆解",
"cases": [
"CHN-001",
"GLO-001"
],
"audience": "城市焦虑青年(26-35岁)",
"china_pain_points": "城市农业在中国面临的主要问题",
"localization_solution": "国际案例中国化适配方案",
"mvp_actions": "读者可立即尝试的3个行动",
"estimated_length": 2500,
"priority_score": 8,
"field": "可持续生活系统",
"format": "趋势洞察 + 实操指南",
"core_concept": "基于2个城市农业案例,提炼本土化落地策略",
"audience_pain": "城市农业领域常见的痛点与困惑",
"unique_angle": "国际案例本土化:上海阳台种菜年省 3000 元:居民自种调查与东京垂直农场:10平米 balcony 年产蔬菜 100kg的中国实践",
"priority": "中",
"total_score": 80,
"compliance_score": 100,
"source_file": "automation/data/sustainability_topics.json",
"ready_at": null,
"published_at": null,
"platform_urls": {},
"status": "待处理",
"lock_by": null,
"lock_at": null,
"created_at": "2026-05-19T01:31:17.960079"
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

+1
View File
@@ -9,6 +9,7 @@ from ..database import get_db
from ..models import User, Topic, SystemConfig from ..models import User, Topic, SystemConfig
from ..schemas import UserCreate, UserUpdate, UserResponse from ..schemas import UserCreate, UserUpdate, UserResponse
from ..core.audit_logger import audit_log from ..core.audit_logger import audit_log
from .auth import org_filter
import json import json
router = APIRouter(prefix="/api/admin", tags=["admin"]) router = APIRouter(prefix="/api/admin", tags=["admin"])
+61
View File
@@ -0,0 +1,61 @@
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import List, Optional
from ..core.nvidia_client import call_llm
from .auth import get_current_user
router = APIRouter(prefix="/api/assistant", tags=["assistant"])
PAGE_CONTEXTS = {
"dashboard": "仪表盘页面:显示系统概览统计(选题总数、待处理、待审查、待发布、已发布、今日新增)、模块状态卡片(6个定时任务的运行状态)、近期计划列表",
"topics": "选题管理页面:创建和管理内容选题,支持筛选、排序、评分、编辑、预览等功能",
"metrics": "数据分析页面:展示各平台内容表现数据,包括浏览量、点赞、收藏、评论等指标的趋势图表",
"calendar": "内容日历页面:以日历形式展示内容发布计划,支持创建、编辑、拖拽调整日期",
"assets": "素材管理页面:管理图片、文件等媒体素材,支持上传、标签分类、关联选题",
"tasks": "任务管理页面:查看内容创作任务的执行状态,包括自动采集、同步、生成、优化等任务",
"platforms": "平台配置页面:管理各发布平台的配置,包括平台名称、API端点、发布格式等",
"admin": "系统管理页面:管理用户、LLM配置、系统配置、采集类别、信息源、组织、查看运行日志等",
}
class ChatRequest(BaseModel):
message: str
page: str = "dashboard"
history: List[dict] = []
@router.post("/chat")
def chat(request: ChatRequest, current_user=Depends(get_current_user)):
try:
from ..database import SessionLocal
from ..models import SystemConfig
db = SessionLocal()
sc = db.query(SystemConfig).filter(SystemConfig.key == "assistant_system_prompt").first()
base_prompt = sc.value if sc else None
db.close()
except Exception:
base_prompt = None
page_name = request.page
page_guide = PAGE_CONTEXTS.get(page_name, "未知页面")
username = current_user.username if hasattr(current_user, 'username') else '用户'
system_prompt = base_prompt or f"""你是一个智能内容创作平台助手,帮助用户解答平台使用问题。
当前用户 {username} 正在查看:{page_guide}
你可以帮助用户:
1. 解释各个页面的功能和操作方法
2. 解答平台使用中的疑问
3. 提供操作建议和最佳实践
4. 指导用户完成具体的操作步骤
请用友好、简洁的中文回答。如果不确定,建议用户参考相关页面或联系管理员。一次回答控制在200字以内。"""
messages = request.history[-20:]
conversation = "\n".join([f"{'用户' if m.get('role') == 'user' else '助手'}: {m.get('content', '')}" for m in messages])
prompt_text = f"{conversation}\n用户: {request.message}\n助手:"
try:
reply = call_llm(prompt_text, system_prompt=system_prompt, temperature=0.5, max_tokens=1000)
return {"reply": reply}
except Exception as e:
return {"reply": f"抱歉,AI助手暂时无法响应,请稍后重试。"}
+34 -2
View File
@@ -142,6 +142,30 @@ def run_sync():
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.post("/optimize-sources/run")
def trigger_optimize_sources():
try:
from ..core.scheduler import scheduler
scheduler._run_optimize_sources()
log_file = LOGS_DIR / f"optimizer_sources_{date.today().isoformat()}.log"
log_file.parent.mkdir(parents=True, exist_ok=True)
log_file.write_text(f"{datetime.now().isoformat()} - 信息源优化完成\n")
return {"message": "信息源优化已完成"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/metrics-sync/run")
def trigger_metrics_sync():
try:
from ..core.scheduler import scheduler
scheduler._run_metrics_sync()
log_file = LOGS_DIR / f"metrics_sync_{date.today().isoformat()}.log"
log_file.parent.mkdir(parents=True, exist_ok=True)
log_file.write_text(f"{datetime.now().isoformat()} - 指标同步完成\n")
return {"message": "指标同步已完成"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/automation/topics") @router.get("/automation/topics")
def list_automation_topics(db: Session = Depends(get_db), current_user=Depends(get_current_user)): def list_automation_topics(db: Session = Depends(get_db), current_user=Depends(get_current_user)):
try: try:
@@ -187,7 +211,6 @@ def get_modules_status():
today_str = date.today().isoformat() today_str = date.today().isoformat()
log_based: dict = { log_based: dict = {
"scheduled_collect": {"name": "📡 内容采集", "log": LOGS_DIR / f"collector_{today_str}.log"}, "scheduled_collect": {"name": "📡 内容采集", "log": LOGS_DIR / f"collector_{today_str}.log"},
"scheduled_sync": {"name": "🔄 数据同步", "log": LOGS_DIR / f"sync_{today_str}.log"},
"scheduled_generate": {"name": "🤖 内容创作", "log": LOGS_DIR / f"creator_{today_str}.log"}, "scheduled_generate": {"name": "🤖 内容创作", "log": LOGS_DIR / f"creator_{today_str}.log"},
"scheduled_optimize": {"name": "🔍 内容优化", "log": LOGS_DIR / f"optimizer_{today_str}.log"}, "scheduled_optimize": {"name": "🔍 内容优化", "log": LOGS_DIR / f"optimizer_{today_str}.log"},
"scheduled_optimize_sources": {"name": "📡 信息源优化", "log": LOGS_DIR / f"optimizer_sources_{today_str}.log"}, "scheduled_optimize_sources": {"name": "📡 信息源优化", "log": LOGS_DIR / f"optimizer_sources_{today_str}.log"},
@@ -208,12 +231,21 @@ def get_modules_status():
total = task_count + content.count("失败") + content.count("failed") + content.count("ERROR") total = task_count + content.count("失败") + content.count("failed") + content.count("ERROR")
success_rate = round(task_count / total * 100) if total > 0 else None success_rate = round(task_count / total * 100) if total > 0 else None
status = "running" if mod_id in jobs else "stopped" status = "running" if mod_id in jobs else "stopped"
job = jobs.get(mod_id)
next_run = None
if job and job.get("next_run_time"):
try:
next_dt = datetime.fromisoformat(job["next_run_time"])
next_run = next_dt.strftime("%Y-%m-%d %H:%M")
except Exception:
next_run = job["next_run_time"]
modules.append({ modules.append({
"id": mod_id, "id": mod_id,
"title": cfg["name"], "title": cfg["name"],
"status": status, "status": status,
"last_run": last_run or "从未运行", "last_run": last_run or "从未运行",
"next_run": next_run or "待计划",
"task_count": task_count, "task_count": task_count,
"success_rate": success_rate, "success_rate": success_rate if success_rate is not None else 0,
}) })
return {"modules": modules, "scheduler": {"running": scheduler._started, "jobs": scheduler.get_jobs()}} return {"modules": modules, "scheduler": {"running": scheduler._started, "jobs": scheduler.get_jobs()}}
+20 -3
View File
@@ -70,12 +70,26 @@ def _get_provider_config(provider: Optional[str] = None) -> dict:
"base_url": base_url or fb.get("base_url", ""), "base_url": base_url or fb.get("base_url", ""),
} }
def _get_db_defaults(provider: Optional[str] = None) -> dict:
try:
from ..database import SessionLocal
from ..models import LLMConfig
db = SessionLocal()
p = provider or _get_active_provider()
cfg = db.query(LLMConfig).filter(LLMConfig.provider == p).order_by(LLMConfig.is_active.desc()).first()
db.close()
if cfg:
return {"temperature": cfg.temperature, "max_tokens": cfg.max_tokens}
except Exception:
pass
return {"temperature": 0.20, "max_tokens": 2048}
def call_llm( def call_llm(
prompt: str, prompt: str,
model: Optional[str] = None, model: Optional[str] = None,
system_prompt: str = "你是一个专业的内容创作助手。", system_prompt: str = "你是一个专业的内容创作助手。",
temperature: float = 0.20, temperature: Optional[float] = None,
max_tokens: int = 2048, max_tokens: Optional[int] = None,
top_p: float = 0.70, top_p: float = 0.70,
frequency_penalty: float = 0.00, frequency_penalty: float = 0.00,
presence_penalty: float = 0.00, presence_penalty: float = 0.00,
@@ -83,6 +97,9 @@ def call_llm(
provider: Optional[str] = None, provider: Optional[str] = None,
additional_params: Optional[Dict[str, Any]] = None, additional_params: Optional[Dict[str, Any]] = None,
) -> str: ) -> str:
defaults = _get_db_defaults(provider)
temperature = temperature if temperature is not None else defaults["temperature"]
max_tokens = max_tokens if max_tokens is not None else defaults["max_tokens"]
cfg = _get_provider_config(provider) cfg = _get_provider_config(provider)
endpoint = f"{cfg['base_url'].rstrip('/')}/chat/completions" endpoint = f"{cfg['base_url'].rstrip('/')}/chat/completions"
headers = { headers = {
@@ -186,7 +203,7 @@ def expand_content_with_llm(
prompt = f"# 参考资料\n{context}\n\n{prompt}" prompt = f"# 参考资料\n{context}\n\n{prompt}"
try: try:
result = call_llm(prompt, temperature=0.3, max_tokens=1500) result = call_llm(prompt, temperature=0.3)
return result.strip() return result.strip()
except Exception as e: except Exception as e:
return f"## {section_title}\n\nLLM 调用失败:{e},请手动补充)" return f"## {section_title}\n\nLLM 调用失败:{e},请手动补充)"
-20
View File
@@ -9,7 +9,6 @@ from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.cron import CronTrigger
from .generator import run_creator from .generator import run_creator
from .optimizer import run_optimizer from .optimizer import run_optimizer
from .sync import sync_all_topics
from .collector import run_collector from .collector import run_collector
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,17 +27,6 @@ class TaskScheduler:
self._run_collect, self._run_collect,
CronTrigger(hour=1, minute=30), CronTrigger(hour=1, minute=30),
id='scheduled_collect', id='scheduled_collect',
replace_existing=True,
max_instances=1,
coalesce=True
)
self.scheduler.add_job(
self._run_sync,
CronTrigger(hour=2, minute=30),
id='scheduled_sync',
replace_existing=True,
max_instances=1,
coalesce=True
) )
self.scheduler.add_job( self.scheduler.add_job(
self._run_generate, self._run_generate,
@@ -176,14 +164,6 @@ class TaskScheduler:
except Exception as e: except Exception as e:
logger.exception("[Scheduled] Source AI optimization failed: %s", e) logger.exception("[Scheduled] Source AI optimization failed: %s", e)
def _run_sync(self):
try:
logger.info("[Scheduled] Starting data sync...")
sync_all_topics()
logger.info("[Scheduled] Sync completed")
except Exception as e:
logger.exception("[Scheduled] Sync failed: %s", e)
def _run_metrics_sync(self): def _run_metrics_sync(self):
try: try:
logger.info("[Scheduled] Starting metrics sync...") logger.info("[Scheduled] Starting metrics sync...")
+1
View File
@@ -60,6 +60,7 @@ def init_db():
("platform_configs", "image_height", "INTEGER DEFAULT 0"), ("platform_configs", "image_height", "INTEGER DEFAULT 0"),
("platform_configs", "min_words", "INTEGER DEFAULT 0"), ("platform_configs", "min_words", "INTEGER DEFAULT 0"),
("platform_configs", "max_words", "INTEGER DEFAULT 0"), ("platform_configs", "max_words", "INTEGER DEFAULT 0"),
("platform_configs", "website_url", "VARCHAR"),
]: ]:
try: try:
conn.execute(text(f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS {col} {typ}")) conn.execute(text(f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS {col} {typ}"))
+5 -7
View File
@@ -38,22 +38,20 @@ def import_initial_data():
# 补充或更新 LLM 供应商配置(opencode-go 为主,nvidia 为备) # 补充或更新 LLM 供应商配置(opencode-go 为主,nvidia 为备)
expected = { expected = {
"opencode-go": dict(provider="opencode-go", model="deepseek-v4-flash", "opencode-go": dict(provider="opencode-go", model="deepseek-v4-flash",
base_url="https://opencode.ai/zen/go/v1", temperature=0.7, max_tokens=2000, is_active=True, base_url="https://opencode.ai/zen/go/v1", temperature=0.7, max_tokens=131072, is_active=True,
user_prompt_template="将以下内容扩展为完整文章:\n{topic_title}\n{core_concept}"), user_prompt_template="将以下内容扩展为完整文章:\n{topic_title}\n{core_concept}"),
"nvidia": dict(provider="nvidia", model="stepfun-ai/step-3.5-flash", "nvidia": dict(provider="nvidia", model="stepfun-ai/step-3.5-flash",
base_url="https://integrate.api.nvidia.com/v1", temperature=0.5, max_tokens=2000, is_active=False, base_url="https://integrate.api.nvidia.com/v1", temperature=0.5, max_tokens=131072, is_active=False,
user_prompt_template="将以下内容扩展为完整章节:\n{section_content}"), user_prompt_template="将以下内容扩展为完整章节:\n{section_content}"),
} }
existing = {c.name: c for c in db.query(LLMConfig).all()} existing = {c.name: c for c in db.query(LLMConfig).all()}
# 删除完全无意义的旧残留
for name in list(existing.keys()):
if name not in expected:
db.delete(existing[name]); existing.pop(name)
for name, cfg in expected.items(): for name, cfg in expected.items():
if name in existing: if name in existing:
c = existing[name] c = existing[name]
# 仅补缺失字段,不覆写用户已修改的值
for k, v in cfg.items(): for k, v in cfg.items():
setattr(c, k, v) if getattr(c, k, None) is None:
setattr(c, k, v)
else: else:
db.add(LLMConfig(name=name, **cfg)) db.add(LLMConfig(name=name, **cfg))
db.commit() db.commit()
+2 -1
View File
@@ -9,7 +9,7 @@ from pathlib import Path
from .database import engine, get_db, init_db from .database import engine, get_db, init_db
from .models import Base from .models import Base
from .api import topics, system, articles, publishing, auth, admin, audit, optimizer_logs, cases, task_logs, llm_configs, system_configs, topic_config, calendar, metrics, assets, tasks, platform_config, collector_mgmt from .api import topics, system, articles, publishing, auth, admin, audit, optimizer_logs, cases, task_logs, llm_configs, system_configs, topic_config, calendar, metrics, assets, tasks, platform_config, collector_mgmt, assistant
from .initial_data import import_initial_data from .initial_data import import_initial_data
from .core.scheduler import scheduler from .core.scheduler import scheduler
@@ -93,6 +93,7 @@ app.include_router(assets.router)
app.include_router(tasks.router) app.include_router(tasks.router)
app.include_router(platform_config.router) app.include_router(platform_config.router)
app.include_router(collector_mgmt.router) app.include_router(collector_mgmt.router)
app.include_router(assistant.router)
# 挂载前端 # 挂载前端
FRONTEND_DIR = Path(__file__).parent.parent.parent / "frontend" FRONTEND_DIR = Path(__file__).parent.parent.parent / "frontend"
+2
View File
@@ -355,6 +355,7 @@ class PlatformConfig(Base):
platform = Column(String, unique=True, nullable=False) # zhihu/wechat/xiaohongshu platform = Column(String, unique=True, nullable=False) # zhihu/wechat/xiaohongshu
name = Column(String, nullable=False) name = Column(String, nullable=False)
icon = Column(String, nullable=True) icon = Column(String, nullable=True)
website_url = Column(String, nullable=True)
api_endpoint = Column(String, nullable=True) api_endpoint = Column(String, nullable=True)
auth_config = Column(JSON, default=dict) auth_config = Column(JSON, default=dict)
format_template = Column(JSON, default=dict) format_template = Column(JSON, default=dict)
@@ -377,6 +378,7 @@ class PlatformConfig(Base):
"platform": self.platform, "platform": self.platform,
"name": self.name, "name": self.name,
"icon": self.icon, "icon": self.icon,
"website_url": self.website_url,
"api_endpoint": self.api_endpoint, "api_endpoint": self.api_endpoint,
"format_template": self.format_template or {}, "format_template": self.format_template or {},
"compliance_rules": self.compliance_rules or {}, "compliance_rules": self.compliance_rules or {},
+3 -1
View File
@@ -257,6 +257,7 @@ class PlatformConfigBase(BaseModel):
platform: str platform: str
name: str name: str
icon: Optional[str] = None icon: Optional[str] = None
website_url: Optional[str] = None
api_endpoint: Optional[str] = None api_endpoint: Optional[str] = None
auth_config: Dict[str, Any] = {} auth_config: Dict[str, Any] = {}
format_template: Dict[str, Any] = {} format_template: Dict[str, Any] = {}
@@ -279,6 +280,7 @@ class PlatformConfigCreate(PlatformConfigBase):
class PlatformConfigUpdate(BaseModel): class PlatformConfigUpdate(BaseModel):
name: Optional[str] = None name: Optional[str] = None
icon: Optional[str] = None icon: Optional[str] = None
website_url: Optional[str] = None
api_endpoint: Optional[str] = None api_endpoint: Optional[str] = None
auth_config: Optional[Dict[str, Any]] = None auth_config: Optional[Dict[str, Any]] = None
format_template: Optional[Dict[str, Any]] = None format_template: Optional[Dict[str, Any]] = None
@@ -476,7 +478,7 @@ class LLMConfigBase(BaseModel):
system_prompt: Optional[str] = None system_prompt: Optional[str] = None
user_prompt_template: str user_prompt_template: str
temperature: float = 0.7 temperature: float = 0.7
max_tokens: int = 2000 max_tokens: int = 131072
model: Optional[str] = None model: Optional[str] = None
provider: str = "opencode-go" provider: str = "opencode-go"
base_url: Optional[str] = None base_url: Optional[str] = None
+168 -18
View File
@@ -25,12 +25,14 @@
<el-button size="default" :type="activeTab === 'cases' ? 'primary' : ''" @click="switchTab('cases')">案例管理</el-button> <el-button size="default" :type="activeTab === 'cases' ? 'primary' : ''" @click="switchTab('cases')">案例管理</el-button>
<el-button size="default" :type="activeTab === 'tasklogs' ? 'primary' : ''" @click="switchTab('tasklogs')">任务日志</el-button> <el-button size="default" :type="activeTab === 'tasklogs' ? 'primary' : ''" @click="switchTab('tasklogs')">任务日志</el-button>
<el-button size="default" :type="activeTab === 'llmconfigs' ? 'primary' : ''" @click="switchTab('llmconfigs')">LLM配置</el-button> <el-button size="default" :type="activeTab === 'llmconfigs' ? 'primary' : ''" @click="switchTab('llmconfigs')">LLM配置</el-button>
<el-button size="default" :type="activeTab === 'platformconfigs' ? 'primary' : ''" @click="switchTab('platformconfigs')">平台配置</el-button>
<el-button size="default" :type="activeTab === 'systemconfigs' ? 'primary' : ''" @click="switchTab('systemconfigs')">系统配置</el-button> <el-button size="default" :type="activeTab === 'systemconfigs' ? 'primary' : ''" @click="switchTab('systemconfigs')">系统配置</el-button>
<el-button size="default" :type="activeTab === 'users' ? 'primary' : ''" @click="switchTab('users')">用户管理</el-button> <el-button size="default" :type="activeTab === 'users' ? 'primary' : ''" @click="switchTab('users')">用户管理</el-button>
<el-button size="default" :type="activeTab === 'categories' ? 'primary' : ''" @click="switchTab('categories')">采集类别</el-button> <el-button size="default" :type="activeTab === 'categories' ? 'primary' : ''" @click="switchTab('categories')">采集类别</el-button>
<el-button size="default" :type="activeTab === 'sources' ? 'primary' : ''" @click="switchTab('sources')">信息源</el-button> <el-button size="default" :type="activeTab === 'sources' ? 'primary' : ''" @click="switchTab('sources')">信息源</el-button>
<el-button size="default" :type="activeTab === 'orgs' ? 'primary' : ''" @click="switchTab('orgs')">组织管理</el-button> <el-button size="default" :type="activeTab === 'orgs' ? 'primary' : ''" @click="switchTab('orgs')">组织管理</el-button>
<el-button size="default" :type="activeTab === 'logs' ? 'primary' : ''" @click="switchTab('logs')">运行日志</el-button> <el-button size="default" :type="activeTab === 'logs' ? 'primary' : ''" @click="switchTab('logs')">运行日志</el-button>
<el-button size="default" :type="activeTab === 'assistant' ? 'primary' : ''" @click="switchTab('assistant')">AI 助手</el-button>
</div> </div>
</div> </div>
@@ -53,10 +55,10 @@
</div> </div>
<el-table :data="paginatedCases" border stripe class="data-table" style="width:100%"> <el-table :data="paginatedCases" border stripe class="data-table" style="width:100%">
<el-table-column prop="id" label="ID" width="70"></el-table-column> <el-table-column prop="id" label="ID" width="70"></el-table-column>
<el-table-column prop="title" label="标题" min-width="120" :show-overflow-tooltip="true"></el-table-column> <el-table-column prop="title" label="标题" min-width="80" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="field" label="领域" width="100"></el-table-column> <el-table-column prop="field" label="领域" width="140"></el-table-column>
<el-table-column prop="summary" label="概述" min-width="200" :show-overflow-tooltip="true"></el-table-column> <el-table-column prop="summary" label="概述" min-width="200" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="source" label="来源" width="100"></el-table-column> <el-table-column prop="source" label="来源" width="140"></el-table-column>
<el-table-column label="操作" width="140" fixed="right"> <el-table-column label="操作" width="140" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button size="small" @click="showCaseDialog(scope.row)">编辑</el-button> <el-button size="small" @click="showCaseDialog(scope.row)">编辑</el-button>
@@ -129,18 +131,25 @@
</template> </template>
<template v-else> <template v-else>
<el-table :data="llmConfigs" border stripe class="data-table" style="width:100%"> <el-table :data="llmConfigs" border stripe class="data-table" style="width:100%">
<el-table-column prop="id" label="ID" width="70"></el-table-column> <el-table-column prop="id" label="ID" width="60"></el-table-column>
<el-table-column prop="name" label="名称" min-width="120"></el-table-column> <el-table-column prop="name" label="名称" min-width="100"></el-table-column>
<el-table-column prop="model" label="模型" min-width="180"></el-table-column> <el-table-column prop="provider" label="供应商" width="110"></el-table-column>
<el-table-column prop="temperature" label="温度" width="80"></el-table-column> <el-table-column prop="model" label="模型" min-width="160"></el-table-column>
<el-table-column prop="max_tokens" label="最大Token" width="110"></el-table-column> <el-table-column prop="base_url" label="接口地址" min-width="200">
<el-table-column prop="is_active" label="激活" width="70"> <template #default="scope">{{ scope.row.base_url ? scope.row.base_url.replace(/^https?:\/\//, '') : '-' }}</template>
</el-table-column>
<el-table-column prop="temperature" label="温度" width="70"></el-table-column>
<el-table-column prop="max_tokens" label="最大Token" width="100"></el-table-column>
<el-table-column prop="is_active" label="激活" width="60">
<template #default="scope"> <template #default="scope">
<el-icon v-if="scope.row.is_active" style="color:#67C23A;"><IconCheck /></el-icon> <el-icon v-if="scope.row.is_active" style="color:#67C23A;"><IconCheck /></el-icon>
<el-icon v-else style="color:#F56C6C;"><IconClose /></el-icon> <el-icon v-else style="color:#F56C6C;"><IconClose /></el-icon>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="140" fixed="right"> <el-table-column label="系统提示词" min-width="150">
<template #default="scope">{{ (scope.row.system_prompt || '').slice(0, 30) }}{{ (scope.row.system_prompt || '').length > 30 ? '...' : '' }}</template>
</el-table-column>
<el-table-column label="操作" width="130" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button size="small" @click="showLLMConfigDialog(scope.row)">编辑</el-button> <el-button size="small" @click="showLLMConfigDialog(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteLLMConfig(scope.row.id)">删除</el-button> <el-button size="small" type="danger" @click="deleteLLMConfig(scope.row.id)">删除</el-button>
@@ -151,8 +160,9 @@
<div class="card-list-mobile"> <div class="card-list-mobile">
<div v-for="item in llmConfigs" :key="item.id" class="card-item"> <div v-for="item in llmConfigs" :key="item.id" class="card-item">
<div class="card-row"><span class="card-label">名称</span><span class="card-value">{{ item.name }}</span></div> <div class="card-row"><span class="card-label">名称</span><span class="card-value">{{ item.name }}</span></div>
<div class="card-row"><span class="card-label">供应商</span><span class="card-value">{{ item.provider }}</span></div>
<div class="card-row"><span class="card-label">模型</span><span class="card-value">{{ item.model }}</span></div> <div class="card-row"><span class="card-label">模型</span><span class="card-value">{{ item.model }}</span></div>
<div class="card-row"><span class="card-label">温度</span><span class="card-value">{{ item.temperature }}</span></div> <div class="card-row"><span class="card-label">温度/最大Token</span><span class="card-value">{{ item.temperature }} / {{ item.max_tokens }}</span></div>
<div class="card-row"><span class="card-label">激活</span><span class="card-value">{{ item.is_active ? '是' : '否' }}</span></div> <div class="card-row"><span class="card-label">激活</span><span class="card-value">{{ item.is_active ? '是' : '否' }}</span></div>
<div class="card-actions"> <div class="card-actions">
<el-button size="small" @click="showLLMConfigDialog(item)">编辑</el-button> <el-button size="small" @click="showLLMConfigDialog(item)">编辑</el-button>
@@ -162,6 +172,45 @@
</div> </div>
</div> </div>
<div v-if="activeTab === 'platformconfigs'">
<div class="toolbar">
<el-button type="primary" size="small" @click="showPlatformConfigDialog()">新增配置</el-button>
</div>
<div v-if="platformConfigsLoading" class="card-loading">加载中...</div>
<template v-else-if="platformConfigs.length === 0">
<div class="empty-state">
<el-icon style="font-size:48px;color:#c0c4cc;"><IconDocument /></el-icon>
<div class="empty-text">暂无平台配置</div>
</div>
</template>
<template v-else>
<el-table :data="platformConfigs" border stripe class="data-table" style="width:100%">
<el-table-column prop="platform" label="标识" width="80"></el-table-column>
<el-table-column prop="name" label="名称" width="80"></el-table-column>
<el-table-column prop="website_url" label="平台网址" min-width="160">
<template #default="scope"><a :href="scope.row.website_url" target="_blank" style="color:#409eff;">{{ scope.row.website_url }}</a></template>
</el-table-column>
<el-table-column label="字数范围" width="120">
<template #default="scope">{{ scope.row.min_words }} - {{ scope.row.max_words }}</template>
</el-table-column>
<el-table-column label="配图" width="100">
<template #default="scope">{{ scope.row.requires_image ? `${scope.row.image_count_min}-${scope.row.image_count_max}张` : '不需要' }}</template>
</el-table-column>
<el-table-column prop="api_endpoint" label="API地址" min-width="160"></el-table-column>
<el-table-column prop="default_format" label="默认格式" min-width="160">
<template #default="scope">{{ (scope.row.default_format || '').slice(0, 30) }}{{ (scope.row.default_format || '').length > 30 ? '...' : '' }}</template>
</el-table-column>
<el-table-column prop="is_active" label="激活" width="60">
<template #default="scope"><el-icon v-if="scope.row.is_active" style="color:#67C23A;"><IconCheck /></el-icon><el-icon v-else style="color:#F56C6C;"><IconClose /></el-icon></template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="scope">
<el-button size="small" @click="showPlatformConfigDialog(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</template>
</div>
<div v-if="activeTab === 'systemconfigs'"> <div v-if="activeTab === 'systemconfigs'">
<div class="toolbar"> <div class="toolbar">
<el-button type="primary" size="small" @click="showSystemConfigDialog()">新增配置</el-button> <el-button type="primary" size="small" @click="showSystemConfigDialog()">新增配置</el-button>
@@ -396,6 +445,21 @@
</div> </div>
</div> </div>
<div v-if="activeTab === 'assistant'">
<div class="toolbar">
<el-button type="primary" size="small" @click="saveAssistantPrompt" :loading="assistantSaving">保存配置</el-button>
<span style="font-size:13px;color:#909399;margin-left:8px;">AI 助手在各页面右下角显示悬浮按钮,点击可打开对话窗口</span>
</div>
<el-form label-width="120px" style="max-width:700px;margin-top:16px;">
<el-form-item label="系统提示词">
<el-input type="textarea" v-model="assistantPrompt" :rows="12" placeholder="设置 AI 助手的角色和行为说明,留空则使用默认提示词"></el-input>
</el-form-item>
<el-form-item label="启用助手" v-if="false">
<el-switch v-model="assistantEnabled"></el-switch>
</el-form-item>
</el-form>
</div>
<div v-if="activeTab === 'sources'"> <div v-if="activeTab === 'sources'">
<div class="toolbar"> <div class="toolbar">
<el-button type="primary" size="small" @click="showSourceDialog()">新增信息源</el-button> <el-button type="primary" size="small" @click="showSourceDialog()">新增信息源</el-button>
@@ -500,7 +564,7 @@
<el-col :span="12"><el-form-item label="温度"><el-input-number v-model="llmConfigForm.temperature" :min="0" :max="2" :step="0.1" style="width:100%"/></el-form-item></el-col> <el-col :span="12"><el-form-item label="温度"><el-input-number v-model="llmConfigForm.temperature" :min="0" :max="2" :step="0.1" style="width:100%"/></el-form-item></el-col>
</el-row> </el-row>
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="12"><el-form-item label="最大Token"><el-input-number v-model="llmConfigForm.max_tokens" :min="1" :max="10000" style="width:100%"/></el-form-item></el-col> <el-col :span="12"><el-form-item label="最大Token"><el-input-number v-model="llmConfigForm.max_tokens" :min="1" :max="999999" style="width:100%"/></el-form-item></el-col>
<el-col :span="12"><el-form-item label="激活"><el-switch v-model="llmConfigForm.is_active"/></el-form-item></el-col> <el-col :span="12"><el-form-item label="激活"><el-switch v-model="llmConfigForm.is_active"/></el-form-item></el-col>
</el-row> </el-row>
<el-row :gutter="16"> <el-row :gutter="16">
@@ -516,6 +580,39 @@
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="platformConfigDialogVisible" :title="platformConfigDialogTitle" width="750px" :close-on-click-modal="false">
<el-form :model="platformConfigForm" label-width="110px">
<el-row :gutter="16">
<el-col :span="8"><el-form-item label="标识"><el-input v-model="platformConfigForm.platform" :disabled="!!editingPlatformConfigPlatform" placeholder="zhihu"/></el-form-item></el-col>
<el-col :span="8"><el-form-item label="名称"><el-input v-model="platformConfigForm.name" placeholder="知乎"/></el-form-item></el-col>
<el-col :span="8"><el-form-item label="图标"><el-input v-model="platformConfigForm.icon" placeholder="📕"/></el-form-item></el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12"><el-form-item label="平台网址"><el-input v-model="platformConfigForm.website_url" placeholder="https://www.zhihu.com"/></el-form-item></el-col>
<el-col :span="12"><el-form-item label="API地址"><el-input v-model="platformConfigForm.api_endpoint" placeholder="https://api.zhihu.com"/></el-form-item></el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12"><el-form-item label="最少字数"><el-input-number v-model="platformConfigForm.min_words" :min="0" :max="50000" style="width:100%"/></el-form-item></el-col>
<el-col :span="12"><el-form-item label="最多字数"><el-input-number v-model="platformConfigForm.max_words" :min="0" :max="50000" style="width:100%"/></el-form-item></el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12"><el-form-item label="需要配图"><el-switch v-model="platformConfigForm.requires_image"/></el-form-item></el-col>
<el-col :span="6"><el-form-item label="最少"><el-input-number v-model="platformConfigForm.image_count_min" :min="0" :max="20" style="width:100%"/></el-form-item></el-col>
<el-col :span="6"><el-form-item label="最多"><el-input-number v-model="platformConfigForm.image_count_max" :min="0" :max="20" style="width:100%"/></el-form-item></el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24"><el-form-item label="默认格式"><el-input type="textarea" v-model="platformConfigForm.default_format" :rows="2" placeholder="图文笔记,300-800字,emoji+标签"/></el-form-item></el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24"><el-form-item label="活跃"><el-switch v-model="platformConfigForm.is_active"/></el-form-item></el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="platformConfigDialogVisible=false">取消</el-button>
<el-button type="primary" @click="savePlatformConfig">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="systemConfigDialogVisible" :title="systemConfigDialogTitle" width="600px" :close-on-click-modal="false"> <el-dialog v-model="systemConfigDialogVisible" :title="systemConfigDialogTitle" width="600px" :close-on-click-modal="false">
<el-form :model="systemConfigForm" label-width="80px"> <el-form :model="systemConfigForm" label-width="80px">
<el-row :gutter="16"> <el-row :gutter="16">
@@ -666,7 +763,7 @@
const llmConfigsLoading = ref(false); const llmConfigsLoading = ref(false);
const llmConfigDialogVisible = ref(false); const llmConfigDialogVisible = ref(false);
const llmConfigDialogTitle = ref('新增配置'); const llmConfigDialogTitle = ref('新增配置');
const llmConfigForm = reactive({ id: null, name: '', system_prompt: '', user_prompt_template: '', temperature: 0.7, max_tokens: 2000, model: '', provider: 'opencode-go', base_url: '', api_key: '', is_active: true }); const llmConfigForm = reactive({ id: null, name: '', system_prompt: '', user_prompt_template: '', temperature: 0.7, max_tokens: 131072, model: '', provider: 'opencode-go', base_url: '', api_key: '', is_active: true });
const editingLLMConfigId = ref(null); const editingLLMConfigId = ref(null);
const loadLLMConfigs = async () => { const loadLLMConfigs = async () => {
@@ -679,14 +776,14 @@
llmConfigDialogTitle.value = '编辑配置'; editingLLMConfigId.value = row.id; llmConfigDialogTitle.value = '编辑配置'; editingLLMConfigId.value = row.id;
llmConfigForm.id = row.id; llmConfigForm.name = row.name; llmConfigForm.provider = row.provider || 'opencode-go'; llmConfigForm.id = row.id; llmConfigForm.name = row.name; llmConfigForm.provider = row.provider || 'opencode-go';
llmConfigForm.model = row.model || ''; llmConfigForm.base_url = row.base_url || ''; llmConfigForm.api_key = ''; llmConfigForm.model = row.model || ''; llmConfigForm.base_url = row.base_url || ''; llmConfigForm.api_key = '';
llmConfigForm.temperature = row.temperature ?? 0.7; llmConfigForm.max_tokens = row.max_tokens ?? 2000; llmConfigForm.temperature = row.temperature ?? 0.7; llmConfigForm.max_tokens = row.max_tokens ?? 131072;
llmConfigForm.system_prompt = row.system_prompt || ''; llmConfigForm.user_prompt_template = row.user_prompt_template || ''; llmConfigForm.system_prompt = row.system_prompt || ''; llmConfigForm.user_prompt_template = row.user_prompt_template || '';
llmConfigForm.is_active = row.is_active ?? true; llmConfigForm.is_active = row.is_active ?? true;
} else { } else {
llmConfigDialogTitle.value = '新增配置'; editingLLMConfigId.value = null; llmConfigDialogTitle.value = '新增配置'; editingLLMConfigId.value = null;
llmConfigForm.id = null; llmConfigForm.name = ''; llmConfigForm.provider = 'opencode-go'; llmConfigForm.model = 'deepseek-v4-pro'; llmConfigForm.id = null; llmConfigForm.name = ''; llmConfigForm.provider = 'opencode-go'; llmConfigForm.model = 'deepseek-v4-pro';
llmConfigForm.base_url = 'https://opencode.ai/zen/go/v1'; llmConfigForm.api_key = ''; llmConfigForm.temperature = 0.7; llmConfigForm.base_url = 'https://opencode.ai/zen/go/v1'; llmConfigForm.api_key = ''; llmConfigForm.temperature = 0.7;
llmConfigForm.max_tokens = 2000; llmConfigForm.system_prompt = ''; llmConfigForm.user_prompt_template = ''; llmConfigForm.is_active = true; llmConfigForm.max_tokens = 131072; llmConfigForm.system_prompt = ''; llmConfigForm.user_prompt_template = ''; llmConfigForm.is_active = true;
} }
llmConfigDialogVisible.value = true; llmConfigDialogVisible.value = true;
}; };
@@ -702,6 +799,37 @@
catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); } catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); }
}; };
const platformConfigs = ref([]);
const platformConfigsLoading = ref(false);
const platformConfigDialogVisible = ref(false);
const platformConfigDialogTitle = ref('新增配置');
const platformConfigForm = reactive({ platform: '', name: '', icon: '', website_url: '', api_endpoint: '', min_words: 300, max_words: 3000, requires_image: false, image_count_min: 0, image_count_max: 0, default_format: '', is_active: true });
const editingPlatformConfigPlatform = ref(null);
const loadPlatformConfigs = async () => {
platformConfigsLoading.value = true;
try { platformConfigs.value = await api.get('/api/platform-config'); } catch (e) { ElMessage.error('加载平台配置失败: ' + e.message); }
finally { platformConfigsLoading.value = false; }
};
const showPlatformConfigDialog = (row = null) => {
if (row) {
platformConfigDialogTitle.value = '编辑配置'; editingPlatformConfigPlatform.value = row.platform;
Object.assign(platformConfigForm, { ...row });
} else {
platformConfigDialogTitle.value = '新增配置'; editingPlatformConfigPlatform.value = null;
platformConfigForm.platform = ''; platformConfigForm.name = ''; platformConfigForm.icon = ''; platformConfigForm.website_url = ''; platformConfigForm.api_endpoint = '';
platformConfigForm.min_words = 300; platformConfigForm.max_words = 3000; platformConfigForm.requires_image = false;
platformConfigForm.image_count_min = 0; platformConfigForm.image_count_max = 0; platformConfigForm.default_format = ''; platformConfigForm.is_active = true;
}
platformConfigDialogVisible.value = true;
};
const savePlatformConfig = async () => {
try {
if (editingPlatformConfigPlatform.value) { await api.put(`/api/platform-config/${platformConfigForm.platform}`, platformConfigForm); ElMessage.success('更新成功'); }
else { await api.post('/api/platform-config', platformConfigForm); ElMessage.success('创建成功'); }
platformConfigDialogVisible.value = false; await loadPlatformConfigs();
} catch (e) { ElMessage.error('保存失败: ' + e.message); }
};
const systemConfigs = ref([]); const systemConfigs = ref([]);
const systemConfigsLoading = ref(false); const systemConfigsLoading = ref(false);
const systemConfigDialogVisible = ref(false); const systemConfigDialogVisible = ref(false);
@@ -841,6 +969,25 @@
catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); } catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); }
}; };
const assistantPrompt = ref('');
const assistantEnabled = ref(true);
const assistantSaving = ref(false);
const loadAssistantConfig = async () => {
try {
const configs = await api.get('/api/admin/systemconfigs');
const cfg = configs.find(c => c.key === 'assistant_system_prompt');
assistantPrompt.value = cfg ? (typeof cfg.value === 'string' ? cfg.value : '') : '';
} catch (e) { console.error('加载AI助手配置失败', e); }
};
const saveAssistantPrompt = async () => {
assistantSaving.value = true;
try {
await api.post('/api/admin/systemconfigs', { key: 'assistant_system_prompt', value: assistantPrompt.value, description: 'AI 助手系统提示词' });
ElementPlus.ElMessage.success('保存成功');
} catch (e) { ElementPlus.ElMessage.error('保存失败: ' + e.message); }
finally { assistantSaving.value = false; }
};
const loadCategories = async () => { const loadCategories = async () => {
categoriesLoading.value = true; categoriesLoading.value = true;
try { categories.value = await api.get('/api/admin/collector/categories'); } catch (e) { ElMessage.error('加载类别失败: ' + e.message); } try { categories.value = await api.get('/api/admin/collector/categories'); } catch (e) { ElMessage.error('加载类别失败: ' + e.message); }
@@ -896,10 +1043,10 @@
const tabLoaders = { const tabLoaders = {
cases: loadCases, tasklogs: loadTaskLogs, cases: loadCases, tasklogs: loadTaskLogs,
llmconfigs: loadLLMConfigs, systemconfigs: loadSystemConfigs, llmconfigs: loadLLMConfigs, platformconfigs: loadPlatformConfigs, systemconfigs: loadSystemConfigs,
users: fetchUsers, users: fetchUsers,
categories: loadCategories, sources: loadSources, categories: loadCategories, sources: loadSources,
orgs: loadOrgs, orgs: loadOrgs, assistant: loadAssistantConfig,
}; };
const loadedTabs = new Set(['cases']); const loadedTabs = new Set(['cases']);
@@ -924,6 +1071,7 @@
casePage, casePageSize, paginatedCases, casePage, casePageSize, paginatedCases,
taskLogs, taskLogsLoading, loadTaskLogs, taskLogs, taskLogsLoading, loadTaskLogs,
llmConfigs, llmConfigsLoading, llmConfigDialogVisible, llmConfigForm, llmConfigDialogTitle, showLLMConfigDialog, saveLLMConfig, deleteLLMConfig, llmConfigs, llmConfigsLoading, llmConfigDialogVisible, llmConfigForm, llmConfigDialogTitle, showLLMConfigDialog, saveLLMConfig, deleteLLMConfig,
platformConfigs, platformConfigsLoading, platformConfigDialogVisible, platformConfigForm, platformConfigDialogTitle, showPlatformConfigDialog, savePlatformConfig, editingPlatformConfigPlatform,
systemConfigs, systemConfigsLoading, systemConfigDialogVisible, systemConfigForm, systemConfigDialogTitle, showSystemConfigDialog, saveSystemConfig, deleteSystemConfig, systemConfigs, systemConfigsLoading, systemConfigDialogVisible, systemConfigForm, systemConfigDialogTitle, showSystemConfigDialog, saveSystemConfig, deleteSystemConfig,
categories, categoriesLoading, categoryDialogVisible, categoryForm, categoryDialogTitle, showCategoryDialog, saveCategory, deleteCategory, categories, categoriesLoading, categoryDialogVisible, categoryForm, categoryDialogTitle, showCategoryDialog, saveCategory, deleteCategory,
categoryPage, categoryPageSize, paginatedCategories, categoryPage, categoryPageSize, paginatedCategories,
@@ -934,7 +1082,8 @@
logout, currentUser, isAdmin, redirectToPage, logout, currentUser, isAdmin, redirectToPage,
users, usersLoading, userDialogVisible, userSubmitting, userForm, userPage, userPageSize, paginatedUsers, users, usersLoading, userDialogVisible, userSubmitting, userForm, userPage, userPageSize, paginatedUsers,
fetchUsers, addUser, submitUser, deleteUser, fetchUsers, addUser, submitUser, deleteUser,
logType, logDate, logContent, logsLoading, fetchLogs logType, logDate, logContent, logsLoading, fetchLogs,
assistantPrompt, assistantEnabled, assistantSaving, saveAssistantPrompt,
}; };
} }
}); });
@@ -944,5 +1093,6 @@
if (window.installUniNav) { window.installUniNav(app); } if (window.installUniNav) { window.installUniNav(app); }
app.mount('#app'); app.mount('#app');
</script> </script>
<script src="ai-assistant.js"></script>
</body> </body>
</html> </html>
+361
View File
@@ -0,0 +1,361 @@
(function () {
if (document.getElementById('ai-assistant-styles')) return;
var style = document.createElement('style');
style.id = 'ai-assistant-styles';
style.textContent = '\
.ai-assistant-btn {\
position: fixed; bottom: 24px; right: 24px;\
z-index: 99999;\
width: 52px; height: 52px;\
border-radius: 50%;\
background: linear-gradient(135deg, #2563eb, #1d4ed8);\
color: #fff;\
border: none;\
cursor: pointer;\
box-shadow: 0 4px 16px rgba(37,99,235,0.35);\
display: flex; align-items: center; justify-content: center;\
font-size: 22px;\
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\
}\
.ai-assistant-btn:hover { transform: scale(1.1); box-shadow: 0 6px 24px rgba(37,99,235,0.45); }\
.ai-assistant-btn.open { transform: rotate(45deg); background: #f56c6c; box-shadow: 0 4px 16px rgba(245,108,108,0.35); }\
.ai-assistant-panel {\
position: fixed; bottom: 88px; right: 24px;\
z-index: 99998;\
width: 380px; height: 560px;\
background: #fff;\
border-radius: 16px;\
box-shadow: 0 8px 40px rgba(0,0,0,0.15);\
display: none;\
flex-direction: column;\
overflow: hidden;\
animation: aiSlideUp 0.3s ease-out;\
border: 1px solid #e5e7eb;\
}\
.ai-assistant-panel.open { display: flex; }\
@keyframes aiSlideUp {\
from { opacity: 0; transform: translateY(20px) scale(0.95); }\
to { opacity: 1; transform: translateY(0) scale(1); }\
}\
.ai-assistant-header {\
padding: 14px 16px;\
background: linear-gradient(135deg, #2563eb, #1d4ed8);\
color: #fff;\
font-weight: 600;\
font-size: 15px;\
display: flex; align-items: center; gap: 8px;\
flex-shrink: 0;\
}\
.ai-assistant-header .ai-badge {\
background: rgba(255,255,255,0.2);\
padding: 2px 8px; border-radius: 10px;\
font-size: 10px; font-weight: 400;\
}\
.ai-assistant-messages {\
flex: 1; overflow-y: auto;\
padding: 12px 16px;\
background: #f8f9fb;\
display: flex; flex-direction: column; gap: 10px;\
}\
.ai-assistant-msg {\
max-width: 85%;\
padding: 10px 14px;\
border-radius: 12px;\
font-size: 13px;\
line-height: 1.6;\
word-break: break-word;\
white-space: pre-wrap;\
}\
.ai-assistant-msg.ai {\
background: #fff;\
border: 1px solid #e5e7eb;\
color: #303133;\
align-self: flex-start;\
border-bottom-left-radius: 4px;\
}\
.ai-assistant-msg.user {\
background: #2563eb;\
color: #fff;\
align-self: flex-end;\
border-bottom-right-radius: 4px;\
}\
.ai-assistant-msg .msg-time {\
font-size: 10px; opacity: 0.6;\
margin-top: 4px; display: block;\
}\
.ai-assistant-typing {\
align-self: flex-start;\
display: flex; gap: 4px;\
padding: 12px 16px;\
background: #fff;\
border: 1px solid #e5e7eb;\
border-radius: 12px;\
border-bottom-left-radius: 4px;\
}\
.ai-assistant-typing span {\
width: 6px; height: 6px;\
background: #909399;\
border-radius: 50%;\
animation: aiBounce 1.4s ease-in-out infinite;\
}\
.ai-assistant-typing span:nth-child(2) { animation-delay: 0.2s; }\
.ai-assistant-typing span:nth-child(3) { animation-delay: 0.4s; }\
@keyframes aiBounce {\
0%, 60%, 100% { transform: translateY(0); }\
30% { transform: translateY(-6px); }\
}\
.ai-assistant-input {\
display: flex; align-items: center;\
padding: 10px 12px;\
border-top: 1px solid #e5e7eb;\
background: #fff;\
flex-shrink: 0;\
gap: 8px;\
}\
.ai-assistant-input textarea {\
flex: 1;\
border: 1px solid #d1d5db;\
border-radius: 8px;\
padding: 8px 12px;\
font-size: 13px;\
outline: none;\
resize: none;\
height: 36px;\
line-height: 20px;\
font-family: inherit;\
transition: border-color 0.2s;\
}\
.ai-assistant-input textarea:focus { border-color: #2563eb; }\
.ai-assistant-send {\
width: 36px; height: 36px;\
border-radius: 8px;\
background: #2563eb;\
color: #fff;\
border: none;\
cursor: pointer;\
font-size: 16px;\
display: flex; align-items: center; justify-content: center;\
transition: all 0.2s;\
flex-shrink: 0;\
}\
.ai-assistant-send:hover { background: #1d4ed8; }\
.ai-assistant-send:disabled { background: #c0c4cc; cursor: not-allowed; }\
.ai-assistant-greeting {\
text-align: center;\
padding: 20px 16px;\
color: #909399;\
font-size: 13px;\
}\
.ai-assistant-greeting .greeting-icon {\
font-size: 36px; margin-bottom: 8px;\
}\
.ai-assistant-greeting h4 {\
color: #303133; font-size: 15px;\
margin: 0 0 6px;\
}\
@media (max-width: 480px) {\
.ai-assistant-panel {\
right: 12px; left: 12px;\
width: auto;\
height: 480px;\
bottom: 80px;\
}\
.ai-assistant-btn { right: 16px; bottom: 16px; }\
}\
';
document.head.appendChild(style);
var token = localStorage.getItem('authToken');
if (!token) return;
var pageMap = {
'/': 'dashboard', '/index.html': 'dashboard',
'topics.html': 'topics', 'metrics.html': 'metrics',
'calendar.html': 'calendar', 'assets.html': 'assets',
'tasks.html': 'tasks', 'platforms.html': 'platforms',
'admin.html': 'admin'
};
var path = window.location.pathname;
var currentPage = pageMap[path] || pageMap['/'];
var greettings = {
'dashboard': '👋 欢迎回来!我可以帮你了解仪表盘上的统计数据、模块状态的含义,或指导你如何快速定位待处理选题。',
'topics': '📝 这里是选题管理。我可以帮你了解如何创建选题、设置评分、编辑内容,或如何使用预览功能查看各平台文章效果。',
'metrics': '📊 数据分析页展示内容表现。我可以帮你解读图表数据、分析各平台效果差异,或建议如何提升内容互动率。',
'calendar': '📅 内容日历帮你规划发布节奏。我可以教你如何创建计划、调整日期,或优化内容排期策略。',
'assets': '🖼️ 素材库管理图片和文件。我可以指导你如何上传素材、添加标签分类,或关联到相关选题。',
'tasks': '⚡ 任务页面跟踪所有创作任务。我可以解释各任务的含义、查看执行进度,或排查任务失败原因。',
'platforms': '🔗 平台配置管理各发布渠道。我可以帮你了解如何配置平台参数、设置发布格式。',
'admin': '⚙️ 系统管理页面。我可以帮你管理用户、配置 LLM、查看运行日志,或设置采集类别和信息源。',
};
var HISTORY_KEY = 'ai_assistant_history_' + currentPage;
var saved = [];
try { saved = JSON.parse(localStorage.getItem(HISTORY_KEY)) || []; } catch(e) {}
var container = document.createElement('div');
container.innerHTML = '\
<button class="ai-assistant-btn" id="aiAssistantBtn" title="AI 助手">\
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\
<path d="M12 2a4 4 0 0 1 4 4v2a4 4 0 0 1-8 0V6a4 4 0 0 1 4-4z"/>\
<path d="M16 14H8a4 4 0 0 0-4 4v2h16v-2a4 4 0 0 0-4-4z"/>\
<circle cx="9" cy="10" r="1" fill="currentColor"/>\
<circle cx="15" cy="10" r="1" fill="currentColor"/>\
<path d="M12 14v2"/>\
</svg>\
</button>\
<div class="ai-assistant-panel" id="aiAssistantPanel">\
<div class="ai-assistant-header">\
<span>🤖 AI 助手</span>\
<span class="ai-badge">' + currentPage + '</span>\
</div>\
<div class="ai-assistant-messages" id="aiMessages">\
</div>\
<div class="ai-assistant-input">\
<textarea id="aiInput" rows="1" placeholder="输入你的问题..." maxlength="500"></textarea>\
<button class="ai-assistant-send" id="aiSendBtn">\
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\
<line x1="22" y1="2" x2="11" y2="13"/>\
<polygon points="22 2 15 22 11 13 2 9 22 2"/>\
</svg>\
</button>\
</div>\
</div>\
';
document.body.appendChild(container);
var btn = document.getElementById('aiAssistantBtn');
var panel = document.getElementById('aiAssistantPanel');
var messagesEl = document.getElementById('aiMessages');
var input = document.getElementById('aiInput');
var sendBtn = document.getElementById('aiSendBtn');
function addMessage(role, content) {
var div = document.createElement('div');
div.className = 'ai-assistant-msg ' + role;
div.textContent = content;
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
function showGreeting() {
messagesEl.innerHTML = '\
<div class="ai-assistant-greeting">\
<div class="greeting-icon">🤖</div>\
<h4>宇之然 AI 助手</h4>\
<p>' + (greettings[currentPage] || '👋 你好!有什么可以帮助你的?') + '</p>\
</div>';
}
function showTyping() {
var div = document.createElement('div');
div.className = 'ai-assistant-typing';
div.id = 'aiTyping';
div.innerHTML = '<span></span><span></span><span></span>';
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
function removeTyping() {
var t = document.getElementById('aiTyping');
if (t) t.remove();
}
function saveHistory(history) {
try {
localStorage.setItem(HISTORY_KEY, JSON.stringify(history.slice(-50)));
} catch(e) {}
}
async function sendMessage(text) {
if (!text.trim()) return;
input.value = '';
input.style.height = '36px';
addMessage('user', text);
if (messagesEl.querySelector('.ai-assistant-greeting')) {
messagesEl.querySelector('.ai-assistant-greeting').remove();
}
sendBtn.disabled = true;
var history = [];
var msgs = messagesEl.querySelectorAll('.ai-assistant-msg');
msgs.forEach(function(m) {
history.push({ role: m.classList.contains('user') ? 'user' : 'assistant', content: m.textContent });
});
showTyping();
try {
var resp = await fetch('/api/assistant/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
body: JSON.stringify({ message: text, page: currentPage, history: history })
});
removeTyping();
if (resp.ok) {
var data = await resp.json();
addMessage('assistant', data.reply || '已收到');
saveHistory(history.concat([
{ role: 'user', content: text },
{ role: 'assistant', content: data.reply || '已收到' }
]));
} else {
addMessage('assistant', '抱歉,请求失败,请稍后重试。');
}
} catch(e) {
removeTyping();
addMessage('assistant', '网络异常,请检查连接后重试。');
}
sendBtn.disabled = false;
input.focus();
}
// Open/close
var isOpen = false;
btn.addEventListener('click', function() {
isOpen = !isOpen;
btn.classList.toggle('open', isOpen);
panel.classList.toggle('open', isOpen);
if (isOpen) {
var msgs = messagesEl.querySelectorAll('.ai-assistant-msg');
if (msgs.length === 0) {
// Load saved or show greeting
if (saved.length > 0) {
saved.forEach(function(m) { addMessage(m.role, m.content); });
} else {
showGreeting();
}
}
input.focus();
}
});
// Send on click
sendBtn.addEventListener('click', function() { sendMessage(input.value); });
// Send on Enter (Shift+Enter for newline)
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage(input.value);
}
});
// Auto-resize textarea
input.addEventListener('input', function() {
this.style.height = '36px';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
// Close on Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && isOpen) {
isOpen = false;
btn.classList.remove('open');
panel.classList.remove('open');
}
});
window.aiAssistantOpen = function() { btn.click(); };
window.aiAssistantClose = function() { if (isOpen) btn.click(); };
})();
+1
View File
@@ -257,5 +257,6 @@ if (window.installIcons) { window.installIcons(app); }
if (window.installUniNav) { window.installUniNav(app); } if (window.installUniNav) { window.installUniNav(app); }
app.mount('#app'); app.mount('#app');
</script> </script>
<script src="ai-assistant.js"></script>
</body> </body>
</html> </html>
+1
View File
@@ -246,5 +246,6 @@ if (window.installIcons) { window.installIcons(app); }
if (window.installUniNav) { window.installUniNav(app); } if (window.installUniNav) { window.installUniNav(app); }
app.mount('#app'); app.mount('#app');
</script> </script>
<script src="ai-assistant.js"></script>
</body> </body>
</html> </html>
+3 -2
View File
@@ -96,9 +96,9 @@
<div class="calendar-header"> <div class="calendar-header">
<h2 class="calendar-title"><el-icon style="vertical-align:-2px;"><IconCalendar /></el-icon> 内容日历</h2> <h2 class="calendar-title"><el-icon style="vertical-align:-2px;"><IconCalendar /></el-icon> 内容日历</h2>
<div class="calendar-nav"> <div class="calendar-nav">
<el-button @click="prevMonth" size="large"><el-icon><IconArrowLeft /></el-icon></el-button> <el-button @click="prevMonth" size="large"><el-icon><IconArrowLeft /></el-icon> 上月</el-button>
<span style="font-size: 20px; font-weight: 600;">{{ currentYear }}年 {{ currentMonth }}月</span> <span style="font-size: 20px; font-weight: 600;">{{ currentYear }}年 {{ currentMonth }}月</span>
<el-button @click="nextMonth" size="large"><el-icon><IconArrowRight /></el-icon></el-button> <el-button @click="nextMonth" size="large">下月 <el-icon><IconArrowRight /></el-icon></el-button>
<el-button type="primary" @click="goToday">今天</el-button> <el-button type="primary" @click="goToday">今天</el-button>
</div> </div>
</div> </div>
@@ -490,5 +490,6 @@ function getDayMeta(year, month, day) {
if (window.installUniNav) { window.installUniNav(app); } if (window.installUniNav) { window.installUniNav(app); }
app.mount('#app'); app.mount('#app');
</script> </script>
<script src="ai-assistant.js"></script>
</body> </body>
</html> </html>
+27 -2
View File
@@ -102,8 +102,13 @@
</div> </div>
<div class="module-content"> <div class="module-content">
<div><span>最后运行</span><span>{{ mod.last_run }}</span></div> <div><span>最后运行</span><span>{{ mod.last_run }}</span></div>
<div><span>下次运行</span><span>{{ mod.next_run }}</span></div>
<div><span>今日任务</span><span>{{ mod.task_count }} 个</span></div> <div><span>今日任务</span><span>{{ mod.task_count }} 个</span></div>
<div v-if="mod.success_rate !== null"><span>成功率</span><span>{{ mod.success_rate }}%</span></div> <div><span>成功率</span><span>{{ mod.success_rate > 0 ? mod.success_rate + '%' : '暂无' }}</span></div>
<div style="margin-top:10px; border-bottom:none;">
<el-button v-if="mod.id === 'scheduled_optimize_sources'" size="small" type="primary" @click="triggerModule(mod.id)" :loading="runningModule === mod.id">立即运行</el-button>
<el-button v-if="mod.id === 'scheduled_metrics_sync'" size="small" type="primary" @click="triggerModule(mod.id)" :loading="runningModule === mod.id">立即运行</el-button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -158,7 +163,8 @@
schedulerStatus: '', schedulerStatus: '',
schedulerRunning: false, schedulerRunning: false,
upcomingEntries: [], upcomingEntries: [],
loadingPlan: false loadingPlan: false,
runningModule: null
}; };
}, },
methods: { methods: {
@@ -276,6 +282,24 @@
const url = filter ? '/topics.html?filter=' + encodeURIComponent(filter) : '/topics.html'; const url = filter ? '/topics.html?filter=' + encodeURIComponent(filter) : '/topics.html';
window.location.href = url; window.location.href = url;
}, },
async triggerModule(modId) {
this.runningModule = modId;
try {
const token = localStorage.getItem('authToken');
const endpoint = modId === 'scheduled_optimize_sources' ? '/api/system/optimize-sources/run' : '/api/system/metrics-sync/run';
const resp = await fetch(endpoint, { method: 'POST', headers: { 'Authorization': 'Bearer ' + token } });
if (resp.ok) {
const data = await resp.json();
this.$message.success(data.message || '执行成功');
this.fetchModules();
} else {
const err = await resp.json().catch(() => ({}));
this.$message.error(err.detail || '执行失败');
}
} catch (e) {
this.$message.error('执行失败: ' + e.message);
} finally { this.runningModule = null; }
},
redirectToPage(page) { redirectToPage(page) {
window.location.href = page.startsWith('/') ? page : '/' + page; window.location.href = page.startsWith('/') ? page : '/' + page;
} }
@@ -312,5 +336,6 @@
if (window.installUniNav) { window.installUniNav(app); } if (window.installUniNav) { window.installUniNav(app); }
app.mount('#app'); app.mount('#app');
</script> </script>
<script src="ai-assistant.js"></script>
</body> </body>
</html> </html>
+1
View File
@@ -369,5 +369,6 @@ if (window.installIcons) { window.installIcons(app); }
if (window.installUniNav) { window.installUniNav(app); } if (window.installUniNav) { window.installUniNav(app); }
app.mount('#app'); app.mount('#app');
</script> </script>
<script src="ai-assistant.js"></script>
</body> </body>
</html> </html>
+1
View File
@@ -246,5 +246,6 @@ if (window.installIcons) { window.installIcons(app); }
if (window.installUniNav) { window.installUniNav(app); } if (window.installUniNav) { window.installUniNav(app); }
app.mount('#app'); app.mount('#app');
</script> </script>
<script src="ai-assistant.js"></script>
</body> </body>
</html> </html>
+1
View File
@@ -292,5 +292,6 @@ if (window.installIcons) { window.installIcons(app); }
if (window.installUniNav) { window.installUniNav(app); } if (window.installUniNav) { window.installUniNav(app); }
app.mount('#app'); app.mount('#app');
</script> </script>
<script src="ai-assistant.js"></script>
</body> </body>
</html> </html>
+26 -11
View File
@@ -8,6 +8,9 @@
<link rel="stylesheet" href="theme-modern.css"> <link rel="stylesheet" href="theme-modern.css">
<style> <style>
.selected-count { color: var(--color-text-secondary); font-size: var(--font-size-body); margin-left: auto; } .selected-count { color: var(--color-text-secondary); font-size: var(--font-size-body); margin-left: auto; }
.preview-dialog-custom .el-dialog__body { overflow-y: auto; max-height: calc(90vh - 120px); padding: 16px 20px; }
.preview-dialog-custom .preview-body { max-height: none !important; min-height: 300px; overflow: visible; }
.preview-dialog-custom .preview-body img { max-width: 100%; }
.topic-card-list { display: none; } .topic-card-list { display: none; }
@media (max-width: 768px) { @media (max-width: 768px) {
.el-table { display: none; } .el-table { display: none; }
@@ -50,7 +53,7 @@
<el-button size="default" :type="filterStatus === 'ready' ? 'primary' : ''" @click="filterStatus = 'ready'; fetchTopics()">待发布 ({{ statusStats.ready }})</el-button> <el-button size="default" :type="filterStatus === 'ready' ? 'primary' : ''" @click="filterStatus = 'ready'; fetchTopics()">待发布 ({{ statusStats.ready }})</el-button>
<el-button size="default" :type="filterStatus === 'published' ? 'primary' : ''" @click="filterStatus = 'published'; fetchTopics()">已发布 ({{ statusStats.published }})</el-button> <el-button size="default" :type="filterStatus === 'published' ? 'primary' : ''" @click="filterStatus = 'published'; fetchTopics()">已发布 ({{ statusStats.published }})</el-button>
</div> </div>
<el-table :data="paginatedTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event.map(item => item.id)"> <el-table ref="topicTable" :data="paginatedTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event.map(item => item.id)">
<el-table-column type="selection" width="55"></el-table-column> <el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="70" fixed></el-table-column> <el-table-column prop="id" label="ID" width="70" fixed></el-table-column>
<el-table-column prop="title" label="标题" min-width="200"></el-table-column> <el-table-column prop="title" label="标题" min-width="200"></el-table-column>
@@ -160,7 +163,7 @@
</div> </div>
</div> </div>
<div style="width:100%;"> <div style="width:100%;">
<div v-if="!editing" class="preview-body" style="min-height:300px; height:68vh; border:1px solid #ebeef5; border-radius:8px; background:#fff; padding:20px; overflow-y:auto; width:100%; line-height:1.8;" v-html="currentPreviewBody"></div> <div v-if="!editing" class="preview-body" style="border:1px solid #ebeef5; border-radius:8px; background:#fff; padding:20px; width:100%; line-height:1.8;" v-html="currentPreviewBody"></div>
<div v-else ref="editor" contenteditable="true" style="min-height:300px; height:68vh; border:1px solid #409eff; border-radius:8px; background:#fff; padding:20px; overflow-y:auto; width:100%; line-height:1.8; outline:none; box-shadow:0 0 0 2px rgba(64,158,255,0.2);" @input="editContent = $event.target.innerHTML"></div> <div v-else ref="editor" contenteditable="true" style="min-height:300px; height:68vh; border:1px solid #409eff; border-radius:8px; background:#fff; padding:20px; overflow-y:auto; width:100%; line-height:1.8; outline:none; box-shadow:0 0 0 2px rgba(64,158,255,0.2);" @input="editContent = $event.target.innerHTML"></div>
</div> </div>
</div> </div>
@@ -237,11 +240,13 @@ const TopicsApp = {
currentPreviewBody() { currentPreviewBody() {
const html = this.platformContents[this.previewPlatform]; const html = this.platformContents[this.previewPlatform];
if (!html) return ''; if (!html) return '';
if (html.length < 50) return '<div style="padding:40px;text-align:center;color:#909399;"><p style="font-size:36px;margin:0 0 12px;">📝</p><p>该平台内容暂未生成或内容为空</p><p style="font-size:13px;margin-top:8px;">可点击上方「批量创作」重新生成,或在编辑模式下手动添加内容</p></div>';
try { try {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html'); const doc = parser.parseFromString(html, 'text/html');
const body = doc.body; const body = doc.body;
if (!body) return html; if (!body) return html;
if (body.innerHTML.length < 30) return '<div style="padding:40px;text-align:center;color:#909399;"><p style="font-size:36px;margin:0 0 12px;">📝</p><p>内容为空,请编辑或重新生成</p></div>';
body.querySelectorAll('script, nav, .header, .tags, footer, .interaction').forEach(el => el.remove()); body.querySelectorAll('script, nav, .header, .tags, footer, .interaction').forEach(el => el.remove());
return body.innerHTML; return body.innerHTML;
} catch (e) { console.error('解析 HTML 失败:', e); return html; } } catch (e) { console.error('解析 HTML 失败:', e); return html; }
@@ -340,8 +345,18 @@ const TopicsApp = {
}, },
platformName(platform) { return { zhihu: '知乎', wechat: '微信公众号', xiaohongshu: '小红书' }[platform] || platform; }, platformName(platform) { return { zhihu: '知乎', wechat: '微信公众号', xiaohongshu: '小红书' }[platform] || platform; },
copyContent(platform) { copyContent(platform) {
if (!this.previewTopic || !this.previewTopic.content) { this.$message.warning('暂无内容可复制'); return; } const html = this.platformContents[platform];
navigator.clipboard.writeText(`标题:${this.previewTopic.title}\n\n内容:\n${this.previewTopic.content}`) if (!html) { this.$message.warning('暂无内容可复制'); return; }
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const titleEl = doc.querySelector('h1');
const title = titleEl ? titleEl.textContent.trim() : (this.previewTopic?.title || '');
const body = doc.body;
if (body) {
body.querySelectorAll('script, nav, .header, .tags, footer, .interaction').forEach(el => el.remove());
}
const text = body ? body.textContent.trim() : html.replace(/<[^>]+>/g, '').trim();
navigator.clipboard.writeText(`标题:${title}\n\n内容:\n${text}`)
.then(() => this.$message.success(`已复制内容,请前往${platform}粘贴发布`)) .then(() => this.$message.success(`已复制内容,请前往${platform}粘贴发布`))
.catch(() => this.$message.error('复制失败,请手动复制')); .catch(() => this.$message.error('复制失败,请手动复制'));
}, },
@@ -433,13 +448,12 @@ const TopicsApp = {
} }
}, },
toggleSelectAll() { toggleSelectAll() {
const visible = this.filteredTopics.map(t => t.id); const table = this.$refs.topicTable;
const allChecked = visible.every(id => this.selectedTopicIds.includes(id)); if (!table) return;
if (allChecked) { const visible = this.filteredTopics;
this.selectedTopicIds = this.selectedTopicIds.filter(id => !visible.includes(id)); const allChecked = visible.every(t => this.selectedTopicIds.includes(t.id));
} else { for (const row of visible) {
const existing = new Set(this.selectedTopicIds); table.toggleRowSelection(row, !allChecked);
for (const id of visible) { if (!existing.has(id)) { this.selectedTopicIds.push(id); } }
} }
}, },
formatDate(dateStr) { formatDate(dateStr) {
@@ -467,5 +481,6 @@ if (window.installIcons) { window.installIcons(app); }
if (window.installUniNav) { window.installUniNav(app); } if (window.installUniNav) { window.installUniNav(app); }
app.mount('#app'); app.mount('#app');
</script> </script>
<script src="ai-assistant.js"></script>
</body> </body>
</html> </html>
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
AI 配图生成器 - 使用 sensenova API 生成文章配图
支持 OpenAI 兼容格式的图片生成 API
"""
import os, sys, json, datetime, logging, requests, base64
from pathlib import Path
PROJECT_ROOT = Path('/root/openclaw-workspace/projects/yu-zhi-ran')
sys.path.insert(0, str(PROJECT_ROOT))
sys.path.insert(0, str(PROJECT_ROOT / 'platform' / 'backend'))
from dotenv import load_dotenv
load_dotenv(str(PROJECT_ROOT / 'platform' / 'backend' / '.env'))
LOGS_DIR = PROJECT_ROOT / "automation" / "logs"
TODAY = datetime.datetime.now().strftime("%Y-%m-%d")
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOGS_DIR / f"ai_image_{TODAY}.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
SENSENOVA_API_KEY = os.getenv("SENSENOVA_API_KEY", "sk-2Bbcf8pSTSl1x2BV5fKtDsUIGdfjKX7M")
SENSENOVA_BASE_URL = os.getenv("SENSENOVA_BASE_URL", "https://token.sensenova.cn/v1")
SENSENOVA_MODEL = os.getenv("SENSENOVA_IMAGE_MODEL", "sensenova-u1-fast")
# Platform image size mapping
PLATFORM_SIZES = {
"zhihu": "1760x2368",
"wechat": "1760x2368",
"xiaohongshu": "1664x2496",
}
def generate_image(prompt: str, platform: str = "zhihu", size: str = None) -> str:
if not size:
size = PLATFORM_SIZES.get(platform, "2048x2048")
headers = {
"Authorization": f"Bearer {SENSENOVA_API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": SENSENOVA_MODEL,
"prompt": prompt,
"n": 1,
"size": size,
"response_format": "b64_json",
}
try:
resp = requests.post(
f"{SENSENOVA_BASE_URL}/images/generations",
headers=headers,
json=payload,
timeout=120,
)
if resp.status_code == 200:
data = resp.json()
if "data" in data and len(data["data"]) > 0:
item = data["data"][0]
if "b64_json" in item:
return item["b64_json"]
if "url" in item:
img_resp = requests.get(item["url"], timeout=60)
if img_resp.status_code == 200:
return base64.b64encode(img_resp.content).decode("ascii")
logger.error(f"API 返回格式异常: {json.dumps(data, ensure_ascii=False)[:500]}")
else:
logger.error(f"API 请求失败: {resp.status_code} {resp.text[:500]}")
except requests.Timeout:
logger.error("API 请求超时")
except Exception as e:
logger.error(f"生成图片失败: {e}")
return None
def generate_article_images(topic_id: str, title: str, field: str = "", platforms: list = None) -> dict:
if platforms is None:
platforms = ["zhihu", "wechat", "xiaohongshu"]
output_dir = PROJECT_ROOT / "automation" / "images" / "generated" / TODAY
output_dir.mkdir(parents=True, exist_ok=True)
results = {}
for platform in platforms:
platform_results = {}
for img_type, prompt_template in [
("cover", f"为文章《{title}》生成一张高质量的封面配图,领域:{field}。风格:简约现代,色彩柔和专业,适合{platform}平台。不要文字。"),
("illustration", f"为文章《{title}》(领域:{field})生成一张内容插图,表现核心概念。风格:清新自然,适合{platform}平台。"),
]:
img_b64 = generate_image(prompt_template, platform)
if img_b64:
filename = f"{img_type}_{platform}_{topic_id}.png"
filepath = output_dir / filename
with open(filepath, "wb") as f:
f.write(base64.b64decode(img_b64))
platform_results[img_type] = str(filepath)
logger.info(f"[{platform}] {img_type} 生成成功: {filename}")
else:
logger.warning(f"[{platform}] {img_type} 生成失败,跳过")
results[platform] = platform_results
return results
def save_images_to_db(topic_id: str, platform: str, images: dict):
try:
from app.database import SessionLocal
from app.models import Article
db = SessionLocal()
article_id = f"{platform}_{topic_id}"
article = db.query(Article).filter(Article.id == article_id).first()
if article:
existing = article.images or {}
existing.update(images)
article.images = existing
db.commit()
logger.info(f"[{platform}] 图片路径已保存到 DB")
db.close()
except Exception as e:
logger.error(f"保存图片路径到 DB 失败: {e}")
def main():
import argparse
parser = argparse.ArgumentParser(description="AI 配图生成器")
parser.add_argument("--topic-id", required=True, help="选题ID")
parser.add_argument("--title", help="文章标题")
parser.add_argument("--field", default="", help="领域")
args = parser.parse_args()
from db_helper import get_topic_by_id
topic = get_topic_by_id(args.topic_id)
if not topic:
print(json.dumps({"ok": False, "error": f"Topic {args.topic_id} not found"}))
sys.exit(1)
title = args.title or topic.get("title", "")
field = args.field or topic.get("field", "")
logger.info(f"为选题 {args.topic_id}{title}》生成配图...")
results = generate_article_images(args.topic_id, title, field)
for platform, imgs in results.items():
if imgs:
save_images_to_db(args.topic_id, platform, imgs)
output = {"ok": True, "topic_id": args.topic_id, "images": results}
print(json.dumps(output, ensure_ascii=False))
if __name__ == "__main__":
main()
+1 -1
View File
@@ -431,7 +431,7 @@ class SustainabilityCollector:
只输出JSON,不要其他文字。""" 只输出JSON,不要其他文字。"""
try: try:
resp = call_llm(prompt, temperature=0.7, max_tokens=800) resp = call_llm(prompt, temperature=0.7)
resp = resp.strip() resp = resp.strip()
if resp.startswith("```"): if resp.startswith("```"):
resp = resp.split("\n", 1)[1].rsplit("\n", 1)[0] resp = resp.split("\n", 1)[1].rsplit("\n", 1)[0]
+5 -2
View File
@@ -163,8 +163,11 @@ def polish_with_llm(html: str, platform: str, remaining_issues: Optional[List[Di
- 短句化,读起来更流畅""" - 短句化,读起来更流畅"""
polished = call_llm(polish_prompt, model=model, temperature=temperature, max_tokens=max_tokens, system_prompt=system_prompt) polished = call_llm(polish_prompt, model=model, temperature=temperature, max_tokens=max_tokens, system_prompt=system_prompt)
if '<h2' in polished or '<p>' in polished: if '<h2' in polished or '<p>' in polished:
tag = "针对性修复" if remaining_issues else "常规润色" if len(polished) > len(html) * 0.3 and len(polished) > 100:
return polished, f"LLM {tag}" if not any(kw in polished[:100] for kw in ['保留', '建议', '可以', '应该', '推荐']):
tag = "针对性修复" if remaining_issues else "常规润色"
return polished, f"LLM {tag}"
logger.warning(f"LLM 优化输出异常(过短或含建议性文字),保留原文 (len={len(polished)})")
except Exception as e: except Exception as e:
logger.warning(f"LLM 优化失败: {e}") logger.warning(f"LLM 优化失败: {e}")
return html, None return html, None
+4 -2
View File
@@ -102,8 +102,10 @@ def run_pipeline(topic_id: str = None) -> Dict:
update_topic_status(tid, 'pending') update_topic_status(tid, 'pending')
return {"ok": False, "error": "writer step failed"} return {"ok": False, "error": "writer step failed"}
# 4. 配图生成 # 4. 配图生成AI版,失败时回退PIL版)
image_ok = run_step("image_generator.py", tid) image_ok = run_step("ai_image_generator.py", tid)
if not image_ok:
image_ok = run_step("image_generator.py", tid)
# 5. 合规优化(自动审核并标记为「待发布」) # 5. 合规优化(自动审核并标记为「待发布」)
if not run_optimizer_step(tid): if not run_optimizer_step(tid):
+1 -1
View File
@@ -102,7 +102,7 @@ class Outliner:
直接输出大纲,不要输出思考过程。""" 直接输出大纲,不要输出思考过程。"""
try: try:
outline = call_llm(prompt, temperature=0.6, max_tokens=2000, system_prompt="你是一个有经验的内容编辑,擅长为不同选题设计差异化的文章结构。") outline = call_llm(prompt, temperature=0.6, system_prompt="你是一个有经验的内容编辑,擅长为不同选题设计差异化的文章结构。")
logger.info(f"LLM 大纲生成成功,长度:{len(outline)}") logger.info(f"LLM 大纲生成成功,长度:{len(outline)}")
return f"# 文章大纲:{title}\n\n{outline}\n\n---\n*大纲生成时间:{TODAY}*" return f"# 文章大纲:{title}\n\n{outline}\n\n---\n*大纲生成时间:{TODAY}*"
except Exception as e: except Exception as e:
+1 -1
View File
@@ -102,7 +102,7 @@ class Researcher:
风格:说人话,直击要点,像资深编辑在给作者做 briefing。避免「首先其次最后」「综上所述」。直接输出内容,不要输出思考过程。""" 风格:说人话,直击要点,像资深编辑在给作者做 briefing。避免「首先其次最后」「综上所述」。直接输出内容,不要输出思考过程。"""
try: try:
return call_llm(prompt, temperature=0.5, max_tokens=1200, system_prompt="你是一个行业研究员,擅长从案例中发现真洞察。") return call_llm(prompt, temperature=0.5, system_prompt="你是一个行业研究员,擅长从案例中发现真洞察。")
except Exception as e: except Exception as e:
logger.warning(f"LLM 研究发现摘要生成失败: {e}") logger.warning(f"LLM 研究发现摘要生成失败: {e}")
return "" return ""
+1 -1
View File
@@ -120,7 +120,7 @@ def generate_new_topics(gaps: List[Dict]) -> List[Dict]:
只输出 JSON 数组,不要其他文字。""" 只输出 JSON 数组,不要其他文字。"""
try: try:
resp = call_llm(prompt, temperature=0.4, max_tokens=2000) resp = call_llm(prompt, temperature=0.4)
resp = resp.strip() resp = resp.strip()
if resp.startswith("```"): if resp.startswith("```"):
resp = resp.split("\n", 1)[1].rsplit("\n", 1)[0] resp = resp.split("\n", 1)[1].rsplit("\n", 1)[0]
+1 -1
View File
@@ -50,7 +50,7 @@ def fetch_llm_trends() -> List[Dict]:
只输出 JSON,不要其他文字。""" 只输出 JSON,不要其他文字。"""
try: try:
resp = call_llm(prompt, temperature=0.4, max_tokens=2000) resp = call_llm(prompt, temperature=0.4)
resp = resp.strip() resp = resp.strip()
if resp.startswith("```"): if resp.startswith("```"):
resp = resp.split("\n", 1)[1].rsplit("\n", 1)[0] resp = resp.split("\n", 1)[1].rsplit("\n", 1)[0]
+85 -62
View File
@@ -35,21 +35,33 @@ logger = logging.getLogger(__name__)
_md_parser = mistune.create_markdown() _md_parser = mistune.create_markdown()
PLATFORM_CONFIG = { _FALLBACK_PLATFORM_CONFIG = {
"zhihu": { "zhihu": {"max_chars": 3000, "style": "深度长文分析", "min_chars": 1500},
"max_chars": 3000, "wechat": {"max_chars": 1500, "style": "亲切口语化", "min_chars": 800},
"style": "深度长文分析", "xiaohongshu": {"max_chars": 800, "style": "图文笔记,emoji+标签", "min_chars": 300},
},
"wechat": {
"max_chars": 1500,
"style": "亲切口语化",
},
"xiaohongshu": {
"max_chars": 800,
"style": "图文笔记,emoji+标签",
},
} }
def _load_platform_config() -> dict:
try:
from app.database import SessionLocal
from app.models import PlatformConfig
db = SessionLocal()
configs = db.query(PlatformConfig).all()
db.close()
result = {}
for c in configs:
result[c.platform] = {
"max_chars": c.max_words or _FALLBACK_PLATFORM_CONFIG.get(c.platform, {}).get("max_chars", 3000),
"style": c.default_format or _FALLBACK_PLATFORM_CONFIG.get(c.platform, {}).get("style", "深度内容"),
"min_chars": c.min_words or _FALLBACK_PLATFORM_CONFIG.get(c.platform, {}).get("min_chars", 300),
}
return result
except Exception:
pass
return dict(_FALLBACK_PLATFORM_CONFIG)
PLATFORM_CONFIG = _load_platform_config()
class Writer: class Writer:
def __init__(self, topic_id: str): def __init__(self, topic_id: str):
self.topic_id = topic_id self.topic_id = topic_id
@@ -96,86 +108,97 @@ class Writer:
sections.append(current) sections.append(current)
return sections return sections
@staticmethod
def _clean_markdown(text: str) -> str:
lines = text.split('\n')
cleaned = []
for line in lines:
line = re.sub(r'^#{1,6}\s+', '', line)
line = re.sub(r'^[\-\*\+]\s+', '', line)
line = re.sub(r'^\d+[\.\)]\s+', '', line)
line = re.sub(r'\*{1,3}([^*]+)\*{1,3}', r'\1', line)
cleaned.append(line)
return '\n'.join(cleaned).strip()
@staticmethod
def _is_outline_noise(line: str) -> bool:
stripped = line.strip()
if not stripped:
return True
if stripped.startswith('---'):
return True
if '大纲生成时间' in stripped:
return True
if stripped.startswith('*大纲'):
return True
return False
def _expand_section(self, section: Dict) -> str: def _expand_section(self, section: Dict) -> str:
content = section.get('content', '').strip() content = section.get('content', '').strip()
if len(content) > 200: if len(content) > 200:
return content return content
if HAVE_LLM and len(content) < 150: if HAVE_LLM and len(content) < 150:
logger.info(f"使用 LLM 扩写章节: {section['title']}") logger.info(f"使用 LLM 扩写章节: {section['title']}")
prompt = f"""你是一个真人写作者+行业观察者,正在写一篇关于「{self.topic['title']}」的文章。现在写「{section['title']}」这一节。 prompt = f"""你是一个资深作者,正在写一篇关于「{self.topic['title']}」的文章。写「{section['title']}」这一节。
⚠️ 今天日期:{datetime.datetime.now().strftime('%Y年%m月%d')}当前年份:{datetime.datetime.now().year}年。 今天日期:{datetime.datetime.now().strftime('%Y年%m月%d')}
笔记要点: 笔记要点:
{content} {content}
要求(逐条对照,每一条都不能跳过): 【输出要求】
### 热点与时效 输出3-5段纯粹、流畅的段落文字,共400-800字。
- **必须引用{datetime.datetime.now().year-1}-{datetime.datetime.now().year}年最新数据/事件/政策/行业报告**,禁用一切过时数据
- 体现当前国内外正在讨论什么、最新的趋势变化
- 每个论点必须配一个真实发生的最新案例(附数据来源),严禁使用虚构数据
- 写作前先确认:这个数据和案例是否是最近{datetime.datetime.now().year-1}-{datetime.datetime.now().year}年的?
### 独特观点 格式:
- 有自己的判断和立场,拒绝"车轱辘话""正确废话" - 禁止任何标题/列表/格式标记(#、-、*、1.、**等)
- 至少提供一个"大多数人没想到"的角度 - 每段3-5句,段间空行分隔
- 宁可尖锐,也不要平庸 - 用「你」或「我们」视角,自然口语化
### 价值 内容要求(让文章在各平台能被推荐):
- 回答读者一个具体问题或解决一个困惑 - 开头直接切入痛点或反常识观点,抓住注意力
- 每段回答一个"所以呢?"——读者看完能带走什么 - 每个观点配具体案例或数据(用「据统计」「调研显示」等),不要空泛说理
- 结束时读者要有「学到了+想转发」的感觉 - 有独特判断和立场,避免正确废话
- 回答「所以呢」——读者看完能带走什么
- 结尾有情绪感召力,让人想点赞/收藏/转发
### 真人感 直接输出段落正文,不要任何附加说明。"""
- 用「你」或「我们」视角,不要用「我」
- 像人在自然说话,不是AI组装文字
- 避免「首先」「其次」「总的来说」「综上所述」「值得注意的是」
- 段落短,2-4句一段,节奏有变化
- 适当用反问或口语化表达
### SEO
- 自然融入1-2个目标搜索词,不生硬堆砌
- 第一句包含核心关键词
### 专业与简洁
- 语言精准,不注水,不为了字数凑内容
- 200-400字,写到点子上就停
### 平台推荐友好
- 节奏不能平,要有起承转合
- 结尾要让人有点赞/收藏/转发的冲动
直接输出段落正文。"""
try: try:
expanded = call_llm(prompt, temperature=0.6, max_tokens=1500) expanded = call_llm(prompt, temperature=0.6)
if expanded and len(expanded.strip()) > len(content): if expanded:
return expanded.strip() cleaned = self._clean_markdown(expanded.strip())
if cleaned:
return cleaned
except Exception as e: except Exception as e:
logger.warning(f"LLM 扩写失败: {e}") logger.warning(f"LLM 扩写失败: {e}")
# Fallback: 将 bullet points 展开为段落 # Fallback: 将 bullet points 展开为段落(过滤噪音行)
lines = [l.strip() for l in content.split('\n') if l.strip()] lines = [l.strip() for l in content.split('\n') if not self._is_outline_noise(l)]
if lines: if lines:
sentences = [] sentences = []
for line in lines: for line in lines:
text = line.lstrip('- *').strip() text = line
for prefix in ['- ', '* ', '1. ', '2. ', '3. ', '4. ', '5. ']:
if line.startswith(prefix):
text = line[len(prefix):]
break
text = text.strip()
if text: if text:
for prefix in ['- ', '* ', '1. ', '2. ', '3. ', '4. ', '5. ']:
if line.startswith(prefix):
text = line[len(prefix):]
break
if text[-1] not in '。!?;': if text[-1] not in '。!?;':
text += '' text += ''
sentences.append(text) sentences.append(text)
if sentences: if sentences:
return ' '.join(sentences) return ' '.join(sentences)
return content return ''
def generate_full_markdown(self) -> str: def generate_full_markdown(self) -> str:
sections = self._parse_outline_sections() sections = self._parse_outline_sections()
parts = [] parts = []
for sec in sections: for sec in sections:
if sec['level'] == 1: if sec['level'] == 1:
if sec.get('content'):
expanded = self._expand_section(sec)
if expanded:
parts.append(expanded + "\n")
continue continue
heading = f"{'#' * sec['level']} {sec['title']}" heading = f"{'#' * sec['level']} {sec['title']}"
parts.append(heading) parts.append(heading)
@@ -246,7 +269,7 @@ class Writer:
if HAVE_LLM: if HAVE_LLM:
prompt = tag_prompts.get(platform, f"根据文章信息生成适合{platform}的标签。标题:{title} 领域:{field} 核心观点:{core} 直接输出标签,空格分隔。") prompt = tag_prompts.get(platform, f"根据文章信息生成适合{platform}的标签。标题:{title} 领域:{field} 核心观点:{core} 直接输出标签,空格分隔。")
try: try:
tags_text = call_llm(prompt, temperature=0.2, max_tokens=1000) tags_text = call_llm(prompt, temperature=0.2)
if tags_text: if tags_text:
tags = [t.strip('#') for t in tags_text.strip().split() if t.strip('#')] tags = [t.strip('#') for t in tags_text.strip().split() if t.strip('#')]
if tags: if tags:
@@ -342,7 +365,7 @@ class Writer:
prompt = title_templates.get(platform, f"给以下文章改个吸引人的{platform}标题:{original}") prompt = title_templates.get(platform, f"给以下文章改个吸引人的{platform}标题:{original}")
try: try:
resp = call_llm(prompt, temperature=0.7, max_tokens=1000) resp = call_llm(prompt, temperature=0.7)
titles = [] titles = []
for line in resp.strip().split('\n'): for line in resp.strip().split('\n'):
line = line.strip() line = line.strip()