codebee 0.1.62 → 0.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +507 -490
  2. package/LICENSE +21 -21
  3. package/README.md +487 -486
  4. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +88 -0
  5. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +126 -0
  6. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  7. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +210 -0
  8. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  9. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  10. package/app/.playwright-cli/run-detail-after.png +0 -0
  11. package/app/core/aiflavor.py +197 -197
  12. package/app/core/artifacts.py +148 -0
  13. package/app/core/attachments.py +532 -532
  14. package/app/core/automation.py +758 -758
  15. package/app/core/backup.py +470 -470
  16. package/app/core/beekeeper.py +183 -183
  17. package/app/core/board.py +209 -209
  18. package/app/core/bookmeta.py +215 -19
  19. package/app/core/bookmeta_catalog.py +180 -180
  20. package/app/core/branching.py +137 -137
  21. package/app/core/builtin_agent.py +1191 -1191
  22. package/app/core/capability.py +144 -144
  23. package/app/core/catalog.py +19 -14
  24. package/app/core/chaptersafe.py +32 -32
  25. package/app/core/cleanup.py +303 -303
  26. package/app/core/compaction.py +186 -186
  27. package/app/core/covergen.py +356 -356
  28. package/app/core/diagnostics.py +114 -114
  29. package/app/core/dispatch.py +173 -173
  30. package/app/core/dispatch_log.py +113 -113
  31. package/app/core/env_scrub.py +85 -85
  32. package/app/core/error_codes.py +64 -64
  33. package/app/core/errorlog.py +137 -137
  34. package/app/core/flows.py +355 -355
  35. package/app/core/goal_service.py +167 -167
  36. package/app/core/health.py +341 -341
  37. package/app/core/history.py +38 -38
  38. package/app/core/hooks.py +156 -156
  39. package/app/core/jobs.py +1022 -1022
  40. package/app/core/manager.py +1996 -1996
  41. package/app/core/market.py +362 -362
  42. package/app/core/market_remote.py +66 -66
  43. package/app/core/mocks.py +64 -64
  44. package/app/core/modelhub.py +3332 -3266
  45. package/app/core/notify.py +106 -106
  46. package/app/core/paihang.py +109 -109
  47. package/app/core/paths.py +2 -2
  48. package/app/core/pipeline.py +4483 -4426
  49. package/app/core/planner.py +27 -27
  50. package/app/core/portguard.py +111 -111
  51. package/app/core/preview.py +293 -293
  52. package/app/core/publish/__init__.py +7 -7
  53. package/app/core/publish/auto.py +367 -367
  54. package/app/core/publish/browser.py +533 -533
  55. package/app/core/publish/fanqie.py +128 -128
  56. package/app/core/publish/flow.py +406 -406
  57. package/app/core/publish/ledger.py +198 -198
  58. package/app/core/publish/manager.py +590 -590
  59. package/app/core/publish/qimao.py +141 -141
  60. package/app/core/publish/ws.py +139 -139
  61. package/app/core/redact.py +38 -38
  62. package/app/core/repeat_guard.py +124 -124
  63. package/app/core/router.py +20 -3
  64. package/app/core/runner.py +2019 -1991
  65. package/app/core/selfupdate.py +384 -384
  66. package/app/core/session_log.py +161 -161
  67. package/app/core/sessions.py +312 -312
  68. package/app/core/settings.py +149 -149
  69. package/app/core/settings_schema.py +262 -262
  70. package/app/core/share_page.py +114 -114
  71. package/app/core/skill_scan.py +106 -106
  72. package/app/core/skillpacks/fanqie-novel.md +80 -80
  73. package/app/core/skillpacks/market/character-bible.md +66 -66
  74. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  75. package/app/core/skillpacks/market/git-workflow.md +57 -57
  76. package/app/core/skillpacks/market/release-notes.md +72 -72
  77. package/app/core/skillpacks/market/weekly-report.md +71 -71
  78. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  79. package/app/core/step_runner.py +132 -132
  80. package/app/core/store.py +13 -70
  81. package/app/core/task_compile.py +199 -199
  82. package/app/core/telemetry.py +292 -292
  83. package/app/core/tlsctx.py +64 -64
  84. package/app/core/token_meter.py +147 -147
  85. package/app/core/volumes.py +355 -355
  86. package/app/core/wxdigest.py +858 -858
  87. package/app/core/zentao.py +2447 -2090
  88. package/app/main.py +2832 -2826
  89. package/app/pick_dialog.py +110 -110
  90. package/app/ui/app.js +13977 -13977
  91. package/app/ui/board.html +162 -162
  92. package/app/ui/board.js +305 -305
  93. package/app/ui/i18n.js +2711 -2711
  94. package/app/ui/icons/bee.svg +79 -79
  95. package/app/ui/index.html +1 -1
  96. package/app/ui/manifest.json +16 -16
  97. package/app/ui/qrcode.js +2297 -2297
  98. package/app/ui/style.css +6125 -6125
  99. package/bin/tutti.js +147 -147
  100. package/package.json +41 -41
  101. package/tools/wx_reader.py +261 -261
@@ -1,106 +1,106 @@
1
- # -*- coding: utf-8 -*-
2
- """运行结果群推送(借鉴 agency-orchestrator 的 --notify):任务跑完把结果摘要
3
- 推到钉钉/飞书/企业微信群机器人,配合定时自动化就是「AI 团队每天定点交活」。
4
-
5
- webhook 一个地址全包——按域名自动适配三种机器人格式(同 agency-orchestrator
6
- 思路);也接受任意 https 地址(按钉钉 text 形状发,自建 n8n 等自选)。
7
- 推送在后台线程、失败只记日志——通知永远不影响任务本身。
8
- """
9
- from __future__ import annotations
10
-
11
- import logging
12
-
13
- log = logging.getLogger(__name__)
14
-
15
-
16
- def _payload_for(webhook, text):
17
- """按 webhook 域名适配群机器人消息体。返回 None = 无法识别的地址。"""
18
- from urllib.parse import urlparse
19
- host = (urlparse(webhook).hostname or "").lower()
20
- if "dingtalk" in host:
21
- return {"msgtype": "text", "text": {"content": text}}
22
- if "feishu" in host or "larksuite" in host:
23
- return {"msg_type": "text", "content": {"text": text}}
24
- if "weixin" in host or "wechat" in host or "work.weixin" in host:
25
- return {"msgtype": "text", "text": {"content": text}}
26
- if host:
27
- return {"msgtype": "text", "text": {"content": text}} # 未知域名按钉钉形状
28
- return None
29
-
30
-
31
- def _webhook():
32
- try:
33
- from . import settings
34
- return str(settings.load().get("notify_webhook") or "").strip()
35
- except Exception:
36
- return ""
37
-
38
-
39
- def _post(hook, text):
40
- """curl 子进程 POST(Python 不经手响应体);网络失败只记日志。"""
41
- from . import runner
42
- import json as _json
43
- body = _json.dumps(_payload_for(hook, text), ensure_ascii=False)
44
- r = runner.run_process(
45
- argv=["curl", "-sS", "--max-time", "20",
46
- "-H", "Content-Type: application/json",
47
- "-d", body, hook],
48
- timeout=30)
49
- return bool(r["ok"])
50
-
51
-
52
- def push_text(text):
53
- """推一条文本到群。配置了 webhook 才推;失败返回 False 不抛错。"""
54
- hook = _webhook()
55
- if not hook:
56
- return False
57
- if not hook.startswith("https://"):
58
- log.warning("notify webhook 必须是 https")
59
- return False
60
- try:
61
- return _post(hook, text)
62
- except Exception as e:
63
- log.warning("notify push failed: %s", e)
64
- return False
65
-
66
-
67
- def push_run_async(run_id):
68
- """任务收尾后异步推送结果摘要(jobs 层调用;绝不阻塞/影响任务)。"""
69
- threading.Thread(target=push_run, daemon=True,
70
- name="notify-%s" % run_id, args=(run_id,)).start()
71
-
72
-
73
- def push_run(run_id):
74
- """组装 run 结果摘要并推送(webhook 未配置时静默跳过)。
75
-
76
- 摘要末尾附分享页路径(notify_base_url 设置非空时拼完整链接,否则只给
77
- run_id 供在本机 CodeBee 界面查找)。"""
78
- hook = _webhook()
79
- if not hook:
80
- return False
81
- from . import store
82
- run = store.get_run(run_id) or {}
83
- if not run:
84
- return False
85
- status = str(run.get("status") or "")
86
- mark = {"done": "✅", "failed": "❌", "cancelled": "⚪"}.get(status, "🔔")
87
- lines = ["%s CodeBee 任务%s" % (mark, {"done": "完成", "failed": "失败",
88
- "cancelled": "已取消"}.get(status, status))]
89
- lines.append("任务:%s" % (run.get("title") or run_id))
90
- if run.get("error"):
91
- lines.append("错误:%s" % str(run["error"])[:200])
92
- v = run.get("verdict") or {}
93
- if v.get("overall") is not None:
94
- lines.append("综合评分 %.1f(%s)" % (
95
- float(v["overall"]), "达标" if v.get("publishable") else "未达标"))
96
- try:
97
- from .settings import load as _sload
98
- base = str(_sload().get("notify_base_url") or "").strip().rstrip("/")
99
- if base:
100
- lines.append("📄 详情:%s/api/runs/%s/share" % (base, run_id))
101
- except Exception:
102
- pass
103
- return push_text("\n".join(lines))
104
-
105
-
106
- import threading # noqa: E402 (push_run_async 依赖;置底避免顶部循环导入)
1
+ # -*- coding: utf-8 -*-
2
+ """运行结果群推送(借鉴 agency-orchestrator 的 --notify):任务跑完把结果摘要
3
+ 推到钉钉/飞书/企业微信群机器人,配合定时自动化就是「AI 团队每天定点交活」。
4
+
5
+ webhook 一个地址全包——按域名自动适配三种机器人格式(同 agency-orchestrator
6
+ 思路);也接受任意 https 地址(按钉钉 text 形状发,自建 n8n 等自选)。
7
+ 推送在后台线程、失败只记日志——通知永远不影响任务本身。
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+
13
+ log = logging.getLogger(__name__)
14
+
15
+
16
+ def _payload_for(webhook, text):
17
+ """按 webhook 域名适配群机器人消息体。返回 None = 无法识别的地址。"""
18
+ from urllib.parse import urlparse
19
+ host = (urlparse(webhook).hostname or "").lower()
20
+ if "dingtalk" in host:
21
+ return {"msgtype": "text", "text": {"content": text}}
22
+ if "feishu" in host or "larksuite" in host:
23
+ return {"msg_type": "text", "content": {"text": text}}
24
+ if "weixin" in host or "wechat" in host or "work.weixin" in host:
25
+ return {"msgtype": "text", "text": {"content": text}}
26
+ if host:
27
+ return {"msgtype": "text", "text": {"content": text}} # 未知域名按钉钉形状
28
+ return None
29
+
30
+
31
+ def _webhook():
32
+ try:
33
+ from . import settings
34
+ return str(settings.load().get("notify_webhook") or "").strip()
35
+ except Exception:
36
+ return ""
37
+
38
+
39
+ def _post(hook, text):
40
+ """curl 子进程 POST(Python 不经手响应体);网络失败只记日志。"""
41
+ from . import runner
42
+ import json as _json
43
+ body = _json.dumps(_payload_for(hook, text), ensure_ascii=False)
44
+ r = runner.run_process(
45
+ argv=["curl", "-sS", "--max-time", "20",
46
+ "-H", "Content-Type: application/json",
47
+ "-d", body, hook],
48
+ timeout=30)
49
+ return bool(r["ok"])
50
+
51
+
52
+ def push_text(text):
53
+ """推一条文本到群。配置了 webhook 才推;失败返回 False 不抛错。"""
54
+ hook = _webhook()
55
+ if not hook:
56
+ return False
57
+ if not hook.startswith("https://"):
58
+ log.warning("notify webhook 必须是 https")
59
+ return False
60
+ try:
61
+ return _post(hook, text)
62
+ except Exception as e:
63
+ log.warning("notify push failed: %s", e)
64
+ return False
65
+
66
+
67
+ def push_run_async(run_id):
68
+ """任务收尾后异步推送结果摘要(jobs 层调用;绝不阻塞/影响任务)。"""
69
+ threading.Thread(target=push_run, daemon=True,
70
+ name="notify-%s" % run_id, args=(run_id,)).start()
71
+
72
+
73
+ def push_run(run_id):
74
+ """组装 run 结果摘要并推送(webhook 未配置时静默跳过)。
75
+
76
+ 摘要末尾附分享页路径(notify_base_url 设置非空时拼完整链接,否则只给
77
+ run_id 供在本机 CodeBee 界面查找)。"""
78
+ hook = _webhook()
79
+ if not hook:
80
+ return False
81
+ from . import store
82
+ run = store.get_run(run_id) or {}
83
+ if not run:
84
+ return False
85
+ status = str(run.get("status") or "")
86
+ mark = {"done": "✅", "failed": "❌", "cancelled": "⚪"}.get(status, "🔔")
87
+ lines = ["%s CodeBee 任务%s" % (mark, {"done": "完成", "failed": "失败",
88
+ "cancelled": "已取消"}.get(status, status))]
89
+ lines.append("任务:%s" % (run.get("title") or run_id))
90
+ if run.get("error"):
91
+ lines.append("错误:%s" % str(run["error"])[:200])
92
+ v = run.get("verdict") or {}
93
+ if v.get("overall") is not None:
94
+ lines.append("综合评分 %.1f(%s)" % (
95
+ float(v["overall"]), "达标" if v.get("publishable") else "未达标"))
96
+ try:
97
+ from .settings import load as _sload
98
+ base = str(_sload().get("notify_base_url") or "").strip().rstrip("/")
99
+ if base:
100
+ lines.append("📄 详情:%s/api/runs/%s/share" % (base, run_id))
101
+ except Exception:
102
+ pass
103
+ return push_text("\n".join(lines))
104
+
105
+
106
+ import threading # noqa: E402 (push_run_async 依赖;置底避免顶部循环导入)
@@ -1,109 +1,109 @@
1
- # -*- coding: utf-8 -*-
2
- """扫榜选材:抓取公开排行榜页,产出选题分析素材。
3
-
4
- 数据源(双源聚合,任一失败不影响另一源):
5
- - 七猫:www.qimao.com/paihang/(公开可抓,2026-09-19 实测 200/106KB)
6
- - 番茄 Web 版:fanqienovel.com/rank(公开可抓,题材分类+书名在
7
- 中文 a 标签里;App 端接口有 SecuritySign/X-Argus 签名——那才是难路,
8
- Web 版榜单页不需要签名,别走弯路)
9
-
10
- 下载走 curl 子进程(与 covergen 同款),解析用宽松正则——页面结构变了
11
- 宁可返回空(调用方回落普通直连提示词),不做脆弱的强解析。
12
- """
13
- from __future__ import annotations
14
-
15
- import logging
16
- import re
17
-
18
- log = logging.getLogger(__name__)
19
-
20
- QIMAO_RANK_URL = "https://www.qimao.com/paihang/"
21
- FANQIE_RANK_URL = "https://fanqienovel.com/rank"
22
- _UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
23
- "(KHTML, like Gecko) Chrome/126 Safari/537.36")
24
-
25
- # 页面 UI 噪音词(非书目/分类):页面 chrome、导航、tab 名、登录/搜索等
26
- _NOISE = {"加入书架", "立即阅读", "开始阅读", "免费阅读", "全部", "分类", "排行",
27
- "排行榜", "完本", "连载中", "阅读", "书城", "男生", "女生",
28
- "帮助中心", "作家助手", "客服", "登录", "注册", "立即登录",
29
- "搜索", "热门作品排行", "大热榜", "新书榜", "完结榜", "收藏榜",
30
- "更新榜", "日榜", "月榜", "蝉联", "榜首", "热度", "免费", "畅销",
31
- "下载", "客户端", "首页", "我的", "设置", "更多", "返回", "关于"}
32
-
33
- _CJK_RE = re.compile(r">([\u4e00-\u9fa5]{2,12})<")
34
-
35
-
36
- def _curl_text(url):
37
- """curl 子进程抓页面文本;失败返回空串。"""
38
- from . import runner
39
- try:
40
- r = runner.run_process(
41
- argv=["curl", "-sS", "--max-time", "20", "-A", _UA, url],
42
- timeout=30)
43
- except Exception:
44
- return ""
45
- if not r.get("ok"):
46
- return ""
47
- return r.get("stdout") or ""
48
-
49
-
50
- def _extract_cjk(html, limit):
51
- """从 HTML 提取中文元素材(去 UI 噪音、保序去重)。"""
52
- items, seen = [], set()
53
- for t in _CJK_RE.findall(html or ""):
54
- if t in _NOISE or t in seen:
55
- continue
56
- seen.add(t)
57
- items.append(t)
58
- if len(items) >= limit:
59
- break
60
- return items
61
-
62
-
63
- def fetch_qimao_rank(limit=30):
64
- """七猫排行榜:返回元素列表,抓取失败返回 []。"""
65
- return _extract_cjk(_curl_text(QIMAO_RANK_URL), limit)
66
-
67
-
68
- def fetch_fanqie_rank(limit=30):
69
- """番茄 Web 版榜单:返回元素列表,抓取失败返回 []。"""
70
- return _extract_cjk(_curl_text(FANQIE_RANK_URL), limit)
71
-
72
-
73
- def fetch_rank_items(limit=40):
74
- """双源聚合:七猫+番茄各抓一份(去重合并,七猫在前)。
75
-
76
- 两源都失败才返回 []——单源挂了另一源仍可用,扫榜永不因单点挡任务。"""
77
- qm = fetch_qimao_rank(limit)
78
- fq = fetch_fanqie_rank(limit)
79
- merged, seen = [], set(qm)
80
- for t in fq:
81
- if t not in seen:
82
- seen.add(t)
83
- merged.append(t)
84
- return qm + merged
85
-
86
-
87
- def rank_scan_prompt(goal):
88
- """组装扫榜选材分析提示词。双源都空返回 None(调用方回落直连提示词)。"""
89
- qm = fetch_qimao_rank(30)
90
- fq = fetch_fanqie_rank(30)
91
- qm = [t for t in qm if t not in fq] # 双源重合的只留七猫份
92
- if not qm and not fq:
93
- return None
94
- blocks = []
95
- if qm:
96
- blocks.append("### 七猫排行榜素材\n" + "、".join(qm))
97
- if fq:
98
- blocks.append("### 番茄排行榜素材\n" + "、".join(fq))
99
- material = "\n\n".join(blocks)
100
- return (
101
- "你是网文选题分析师。以下是刚刚抓取的两个平台排行榜页面的书目与分类素材:\n\n"
102
- "## 榜单元素材\n%s\n\n"
103
- "## 用户想写的方向\n%s\n\n"
104
- "## 你的产出(Markdown 报告)\n"
105
- "1. **热门题材 Top3**:各自的共同特征与上榜代表书目(跨平台重合的题材单独点出——双平台都热说明是真风口)\n"
106
- "2. **高频人设/套路总结**:3-5 条,点名反复出现的元素\n"
107
- "3. **差异化切入建议**:2-3 个,结合用户方向给出「题材+人设」组合与一句话理由\n"
108
- "直接输出分析报告,不要复述素材清单,不要输出与报告无关的内容。"
109
- % (material, goal or "(用户未指定方向,按大盘热门分析)"))
1
+ # -*- coding: utf-8 -*-
2
+ """扫榜选材:抓取公开排行榜页,产出选题分析素材。
3
+
4
+ 数据源(双源聚合,任一失败不影响另一源):
5
+ - 七猫:www.qimao.com/paihang/(公开可抓,2026-09-19 实测 200/106KB)
6
+ - 番茄 Web 版:fanqienovel.com/rank(公开可抓,题材分类+书名在
7
+ 中文 a 标签里;App 端接口有 SecuritySign/X-Argus 签名——那才是难路,
8
+ Web 版榜单页不需要签名,别走弯路)
9
+
10
+ 下载走 curl 子进程(与 covergen 同款),解析用宽松正则——页面结构变了
11
+ 宁可返回空(调用方回落普通直连提示词),不做脆弱的强解析。
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ import re
17
+
18
+ log = logging.getLogger(__name__)
19
+
20
+ QIMAO_RANK_URL = "https://www.qimao.com/paihang/"
21
+ FANQIE_RANK_URL = "https://fanqienovel.com/rank"
22
+ _UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
23
+ "(KHTML, like Gecko) Chrome/126 Safari/537.36")
24
+
25
+ # 页面 UI 噪音词(非书目/分类):页面 chrome、导航、tab 名、登录/搜索等
26
+ _NOISE = {"加入书架", "立即阅读", "开始阅读", "免费阅读", "全部", "分类", "排行",
27
+ "排行榜", "完本", "连载中", "阅读", "书城", "男生", "女生",
28
+ "帮助中心", "作家助手", "客服", "登录", "注册", "立即登录",
29
+ "搜索", "热门作品排行", "大热榜", "新书榜", "完结榜", "收藏榜",
30
+ "更新榜", "日榜", "月榜", "蝉联", "榜首", "热度", "免费", "畅销",
31
+ "下载", "客户端", "首页", "我的", "设置", "更多", "返回", "关于"}
32
+
33
+ _CJK_RE = re.compile(r">([\u4e00-\u9fa5]{2,12})<")
34
+
35
+
36
+ def _curl_text(url):
37
+ """curl 子进程抓页面文本;失败返回空串。"""
38
+ from . import runner
39
+ try:
40
+ r = runner.run_process(
41
+ argv=["curl", "-sS", "--max-time", "20", "-A", _UA, url],
42
+ timeout=30)
43
+ except Exception:
44
+ return ""
45
+ if not r.get("ok"):
46
+ return ""
47
+ return r.get("stdout") or ""
48
+
49
+
50
+ def _extract_cjk(html, limit):
51
+ """从 HTML 提取中文元素材(去 UI 噪音、保序去重)。"""
52
+ items, seen = [], set()
53
+ for t in _CJK_RE.findall(html or ""):
54
+ if t in _NOISE or t in seen:
55
+ continue
56
+ seen.add(t)
57
+ items.append(t)
58
+ if len(items) >= limit:
59
+ break
60
+ return items
61
+
62
+
63
+ def fetch_qimao_rank(limit=30):
64
+ """七猫排行榜:返回元素列表,抓取失败返回 []。"""
65
+ return _extract_cjk(_curl_text(QIMAO_RANK_URL), limit)
66
+
67
+
68
+ def fetch_fanqie_rank(limit=30):
69
+ """番茄 Web 版榜单:返回元素列表,抓取失败返回 []。"""
70
+ return _extract_cjk(_curl_text(FANQIE_RANK_URL), limit)
71
+
72
+
73
+ def fetch_rank_items(limit=40):
74
+ """双源聚合:七猫+番茄各抓一份(去重合并,七猫在前)。
75
+
76
+ 两源都失败才返回 []——单源挂了另一源仍可用,扫榜永不因单点挡任务。"""
77
+ qm = fetch_qimao_rank(limit)
78
+ fq = fetch_fanqie_rank(limit)
79
+ merged, seen = [], set(qm)
80
+ for t in fq:
81
+ if t not in seen:
82
+ seen.add(t)
83
+ merged.append(t)
84
+ return qm + merged
85
+
86
+
87
+ def rank_scan_prompt(goal):
88
+ """组装扫榜选材分析提示词。双源都空返回 None(调用方回落直连提示词)。"""
89
+ qm = fetch_qimao_rank(30)
90
+ fq = fetch_fanqie_rank(30)
91
+ qm = [t for t in qm if t not in fq] # 双源重合的只留七猫份
92
+ if not qm and not fq:
93
+ return None
94
+ blocks = []
95
+ if qm:
96
+ blocks.append("### 七猫排行榜素材\n" + "、".join(qm))
97
+ if fq:
98
+ blocks.append("### 番茄排行榜素材\n" + "、".join(fq))
99
+ material = "\n\n".join(blocks)
100
+ return (
101
+ "你是网文选题分析师。以下是刚刚抓取的两个平台排行榜页面的书目与分类素材:\n\n"
102
+ "## 榜单元素材\n%s\n\n"
103
+ "## 用户想写的方向\n%s\n\n"
104
+ "## 你的产出(Markdown 报告)\n"
105
+ "1. **热门题材 Top3**:各自的共同特征与上榜代表书目(跨平台重合的题材单独点出——双平台都热说明是真风口)\n"
106
+ "2. **高频人设/套路总结**:3-5 条,点名反复出现的元素\n"
107
+ "3. **差异化切入建议**:2-3 个,结合用户方向给出「题材+人设」组合与一句话理由\n"
108
+ "直接输出分析报告,不要复述素材清单,不要输出与报告无关的内容。"
109
+ % (material, goal or "(用户未指定方向,按大盘热门分析)"))
package/app/core/paths.py CHANGED
@@ -54,8 +54,8 @@ def default_data_dir(repo=None, environ=None) -> Path:
54
54
  DATA_DIR = default_data_dir()
55
55
  TASKS_DIR = DATA_DIR / "tasks"
56
56
  RUNS_DIR = DATA_DIR / "runs"
57
- USAGE_DIR = DATA_DIR / "usage"
58
- ERRORS_DIR = DATA_DIR / "errors"
57
+ USAGE_DIR = DATA_DIR / "usage"
58
+ ERRORS_DIR = DATA_DIR / "errors"
59
59
  PUBLISH_DIR = DATA_DIR / "publish" # 一键发布:台账/作品登记/流程选择器覆盖/浏览器 profile
60
60
  CATALOG_FILE = DATA_DIR / "catalog.json"
61
61
  ENABLED_FILE = DATA_DIR / "orchestration.json"