@cloud411716/fancy-webnovel 0.1.33 → 0.1.35
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.
package/cordis.patch.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* registry.js — 合并注册所有命令
|
|
3
|
-
* fancy-bootstrap + fancy-scan
|
|
4
|
-
*
|
|
5
|
-
* 所有消息模板 → scripts/templates.js
|
|
6
|
-
* 所有基础设施 → scripts/infra.js
|
|
7
|
-
*/
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { dirname, join } from 'path';
|
|
10
|
-
import { existsSync } from 'fs';
|
|
11
|
-
import { notify, llmFollowup, spawnScript } from './scripts/infra.js';
|
|
12
|
-
import { scan, bootstrap, PLATFORM_TABLE, platformLabel } from './scripts/templates.js';
|
|
13
|
-
|
|
14
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const SCRIPTS_DIR = join(__dirname, 'scripts');
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// 路径工具
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
function isValidPath(p) {
|
|
22
|
-
return !p.includes('..') && !/[<>:"'|?*[\x00-\x1f]/.test(p) && !/\s/.test(p);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// fancy-bootstrap
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
async function applyBootstrap(ctx) {
|
|
30
|
-
ctx.commands.register({
|
|
31
|
-
name: 'fancy-bootstrap',
|
|
32
|
-
description: '📁 初始化项目根 ( usage: /fancy-bootstrap [项目根] )',
|
|
33
|
-
handler: async (invocation) => {
|
|
34
|
-
const session = invocation.agent.session;
|
|
35
|
-
const rawInput = invocation.rawInput ? invocation.rawInput.trim() : '';
|
|
36
|
-
|
|
37
|
-
let projectRoot = rawInput;
|
|
38
|
-
|
|
39
|
-
// 无参数 → 弹窗
|
|
40
|
-
if (!projectRoot) {
|
|
41
|
-
const result = await ctx.userQuestions.ask({
|
|
42
|
-
questions: [bootstrap.askPath()],
|
|
43
|
-
});
|
|
44
|
-
projectRoot = result.answers[0]?.custom?.trim() || '';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!projectRoot) {
|
|
48
|
-
notify(session, bootstrap.notify({ type: 'no_path' }));
|
|
49
|
-
return { kind: 'success', text: '' };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (projectRoot === '.') {
|
|
53
|
-
projectRoot = process.cwd();
|
|
54
|
-
notify(session, bootstrap.notify({ type: 'using_cwd', projectRoot }));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!isValidPath(projectRoot)) {
|
|
58
|
-
notify(session, bootstrap.notify({ type: 'invalid_path' }));
|
|
59
|
-
return { kind: 'success', text: '' };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (existsSync(join(projectRoot, '.fancy-deployed'))) {
|
|
63
|
-
notify(session, bootstrap.notify({ type: 'already_initialized' }));
|
|
64
|
-
return { kind: 'success', text: '' };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const confirm = await ctx.userQuestions.ask({
|
|
68
|
-
questions: [{
|
|
69
|
-
id: 'confirm',
|
|
70
|
-
question: bootstrap.notify({ type: 'confirm', projectRoot }),
|
|
71
|
-
detail: projectRoot,
|
|
72
|
-
hideCustomInput: true,
|
|
73
|
-
options: bootstrap.confirmOptions(),
|
|
74
|
-
}],
|
|
75
|
-
});
|
|
76
|
-
const ans = confirm.answers[0];
|
|
77
|
-
const chosen = ans?.selected?.[0] ?? ans?.custom;
|
|
78
|
-
if (chosen !== '确认创建') {
|
|
79
|
-
notify(session, bootstrap.notify({ type: 'cancelled' }));
|
|
80
|
-
return { kind: 'success', text: '' };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
notify(session, bootstrap.notify({ type: 'initializing', projectRoot }));
|
|
84
|
-
const result = await spawnScript('node', [join(SCRIPTS_DIR, 'init.js'), projectRoot], { timeout: 30000 });
|
|
85
|
-
|
|
86
|
-
if (result.ok) {
|
|
87
|
-
notify(session, bootstrap.notify({ type: 'success' }));
|
|
88
|
-
} else {
|
|
89
|
-
const err = (typeof result.error === 'object' && result.error !== null)
|
|
90
|
-
? (result.error.message || JSON.stringify(result.error))
|
|
91
|
-
: String(result.error || '未知错误');
|
|
92
|
-
notify(session, bootstrap.notify({ type: 'error', err }));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { kind: 'success', text: '' };
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
// fancy-scan
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
|
|
104
|
-
async function applyScan(ctx) {
|
|
105
|
-
ctx.commands.register({
|
|
106
|
-
name: 'fancy-scan',
|
|
107
|
-
description: '📊 扫榜分析 ( usage: /fancy-scan [平台] [篇幅] )',
|
|
108
|
-
handler: async (invocation) => {
|
|
109
|
-
const session = invocation.agent.session;
|
|
110
|
-
const rawInput = invocation.rawInput ? invocation.rawInput.trim() : '';
|
|
111
|
-
|
|
112
|
-
// 获取项目根
|
|
113
|
-
let projectRoot = rawInput ? rawInput.split(/\s+/)[0] : '';
|
|
114
|
-
if (!projectRoot) projectRoot = process.cwd();
|
|
115
|
-
|
|
116
|
-
if (!existsSync(join(projectRoot, '.fancy-deployed'))) {
|
|
117
|
-
notify(session, scan.notify({ type: 'not_initialized' }));
|
|
118
|
-
return { kind: 'success', text: '' };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// 解析参数
|
|
122
|
-
let platform = '', length = '';
|
|
123
|
-
if (rawInput) {
|
|
124
|
-
const parts = rawInput.split(/\s+/);
|
|
125
|
-
platform = parts[0] || '';
|
|
126
|
-
length = parts[1] || '';
|
|
127
|
-
const numMatch = platform.match(/^(\d+)$/);
|
|
128
|
-
if (numMatch) {
|
|
129
|
-
const row = PLATFORM_TABLE.find(r => r[0] === numMatch[1]);
|
|
130
|
-
if (row) {
|
|
131
|
-
platform = row[1];
|
|
132
|
-
if (platform === 'zhihu' || platform === 'qimao') {
|
|
133
|
-
length = parts[1] || 'long';
|
|
134
|
-
} else {
|
|
135
|
-
length = row[2] === '长篇' ? 'long' : (row[2] === '短篇' ? 'short' : 'long');
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 参数不全 → 弹窗选择
|
|
142
|
-
if (!platform || !length || !PLATFORM_TABLE.find(r => r[1] === platform)) {
|
|
143
|
-
notify(session, scan.platformList());
|
|
144
|
-
|
|
145
|
-
const choice = await ctx.userQuestions.ask({
|
|
146
|
-
questions: [scan.askPlatform()],
|
|
147
|
-
});
|
|
148
|
-
const answer = choice.answers[0]?.custom?.trim();
|
|
149
|
-
if (!answer) {
|
|
150
|
-
notify(session, scan.notify({ type: 'cancelled_with_hint' }));
|
|
151
|
-
return { kind: 'success', text: '' };
|
|
152
|
-
}
|
|
153
|
-
const numMatch = answer.match(/^(\d+)$/);
|
|
154
|
-
const row = numMatch ? PLATFORM_TABLE.find(r => r[0] === numMatch[1]) : null;
|
|
155
|
-
if (!row) {
|
|
156
|
-
notify(session, scan.notify({ type: 'invalid_choice' }));
|
|
157
|
-
return { kind: 'success', text: '' };
|
|
158
|
-
}
|
|
159
|
-
platform = row[1];
|
|
160
|
-
length = (platform === 'zhihu' || platform === 'qimao')
|
|
161
|
-
? null
|
|
162
|
-
: (row[2] === '长篇' ? 'long' : 'short');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 知乎/七猫二次选择篇幅
|
|
166
|
-
if (platform === 'zhihu' || platform === 'qimao') {
|
|
167
|
-
if (!length || !['long', 'short'].includes(length)) {
|
|
168
|
-
const lenResult = await ctx.userQuestions.ask({
|
|
169
|
-
questions: [scan.askLength(platform)],
|
|
170
|
-
});
|
|
171
|
-
const lenAnswer = lenResult.answers[0]?.selected?.[0] ?? lenResult.answers[0]?.custom;
|
|
172
|
-
if (!lenAnswer || lenAnswer === '取消') {
|
|
173
|
-
notify(session, scan.notify({ type: 'cancelled' }));
|
|
174
|
-
return { kind: 'success', text: '' };
|
|
175
|
-
}
|
|
176
|
-
length = (lenAnswer === '长篇') ? 'long' : 'short';
|
|
177
|
-
}
|
|
178
|
-
} else if (!length) {
|
|
179
|
-
length = 'long';
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
notify(session, scan.notify({ type: 'collecting', platform, length }));
|
|
183
|
-
|
|
184
|
-
const scriptResult = await spawnScript(
|
|
185
|
-
'node',
|
|
186
|
-
[join(SCRIPTS_DIR, '..', 'fancy-scan', 'scripts', 'run-scan.js'),
|
|
187
|
-
'--project-root', projectRoot, '--platform', platform, '--length', length],
|
|
188
|
-
{ timeout: 120000 }
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
if (!scriptResult.ok) {
|
|
192
|
-
const err = (typeof scriptResult.error === 'object' && scriptResult.error !== null)
|
|
193
|
-
? (scriptResult.error.message || JSON.stringify(scriptResult.error))
|
|
194
|
-
: String(scriptResult.error || '未知错误');
|
|
195
|
-
notify(session, scan.notify({ type: 'failed', err }));
|
|
196
|
-
return { kind: 'success', text: '' };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 解析 receipt
|
|
200
|
-
let receipt;
|
|
201
|
-
try { receipt = JSON.parse(scriptResult.output); } catch (_) {
|
|
202
|
-
notify(session, scan.notify({ type: 'parse_error' }));
|
|
203
|
-
return { kind: 'success', text: '' };
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const files = receipt.scan_files || [];
|
|
207
|
-
const topicFile = receipt.topic_decision || '';
|
|
208
|
-
|
|
209
|
-
// 通知用户 + 触发 LLM 分析
|
|
210
|
-
notify(session, scan.notify({ type: 'handover', platform, length, files, topicFile, projectRoot }));
|
|
211
|
-
|
|
212
|
-
const prompt = scan.llmAnalysisPrompt({
|
|
213
|
-
platform, length,
|
|
214
|
-
date: receipt.date || '',
|
|
215
|
-
files,
|
|
216
|
-
topicFile,
|
|
217
|
-
projectRoot,
|
|
218
|
-
});
|
|
219
|
-
llmFollowup(invocation, prompt);
|
|
220
|
-
|
|
221
|
-
return { kind: 'success', text: '' };
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ---------------------------------------------------------------------------
|
|
227
|
-
// 导出
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
229
|
-
|
|
230
|
-
export const inject = ['commands', 'userQuestions'];
|
|
231
|
-
|
|
232
|
-
export async function apply(ctx) {
|
|
233
|
-
try {
|
|
234
|
-
await applyBootstrap(ctx);
|
|
235
|
-
await applyScan(ctx);
|
|
236
|
-
} catch (e) {
|
|
237
|
-
const { writeFileSync } = await import('fs');
|
|
238
|
-
writeFileSync('/tmp/dsh-apply-error.txt', String(e) + '\n' + e.stack, 'utf-8');
|
|
239
|
-
throw e;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* infra.js — DSH 插件核心基础设施
|
|
3
|
-
*
|
|
4
|
-
* 提供:
|
|
5
|
-
* notify(session, text) — 打印消息给用户(不触发 LLM)
|
|
6
|
-
* llmFollowup(invocation, prompt) — 发送消息给 LLM,让 LLM 继续处理
|
|
7
|
-
* spawnScript(cmd, args, opts) — spawn 子进程,解析 stdout JSON
|
|
8
|
-
*
|
|
9
|
-
* 所有 fancy-* 插件均直接 import 此文件。
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// notify — 打印消息给用户(queueMicrotask 避免打断 LLM 输出顺序)
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
export function notify(session, text) {
|
|
17
|
-
queueMicrotask(() => {
|
|
18
|
-
session.append(
|
|
19
|
-
'user/message',
|
|
20
|
-
{ content: [{ type: 'text', text }], source: { kind: 'user' } },
|
|
21
|
-
{ surfaceOp: 'append' }
|
|
22
|
-
);
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// llmFollowup — 发送消息给 LLM,让 LLM 在同一 session 继续处理
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
export function llmFollowup(invocation, content) {
|
|
31
|
-
const message = typeof content === 'string'
|
|
32
|
-
? { content: [{ type: 'text', text: content }], source: { kind: 'user' } }
|
|
33
|
-
: content; // 允许传入完整消息对象
|
|
34
|
-
invocation.agent.followup(message);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// spawnScript — spawn 子进程,stdout 只收集 JSON,stderr 单独收集
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
export function spawnScript(cmd, args, { timeout = 30000, cwd } = {}) {
|
|
42
|
-
return new Promise(async (resolve) => {
|
|
43
|
-
const { spawn } = await import('child_process');
|
|
44
|
-
const proc = spawn(cmd, args, { timeout, cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
45
|
-
const outParts = [], errParts = [];
|
|
46
|
-
proc.stdout.on('data', (d) => { outParts.push(d.toString()); });
|
|
47
|
-
proc.stderr.on('data', (d) => { errParts.push(d.toString()); });
|
|
48
|
-
proc.on('close', (code) => {
|
|
49
|
-
const stdout = outParts.join(''), stderr = errParts.join('');
|
|
50
|
-
try {
|
|
51
|
-
const parsed = JSON.parse(stdout);
|
|
52
|
-
resolve({
|
|
53
|
-
ok: parsed.ok === true,
|
|
54
|
-
output: stdout,
|
|
55
|
-
error: parsed.error
|
|
56
|
-
? (parsed.error.message || JSON.stringify(parsed.error))
|
|
57
|
-
: stderr || undefined,
|
|
58
|
-
});
|
|
59
|
-
} catch (_) {
|
|
60
|
-
resolve({ ok: false, output: stdout, error: stderr || ('exit ' + code) });
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
proc.on('error', (err) => resolve({ ok: false, output: '', error: err.message }));
|
|
64
|
-
});
|
|
65
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* templates.js — 所有插件模板字符串
|
|
3
|
-
*
|
|
4
|
-
* 包括:
|
|
5
|
-
* bootstrap 提示消息
|
|
6
|
-
* scan 提示消息
|
|
7
|
-
* LLM 分析 prompt 模板
|
|
8
|
-
*
|
|
9
|
-
* 原则:所有展示给用户的文字、发送给 LLM 的 prompt
|
|
10
|
-
* 均从此文件导出,插件主代码只做逻辑和拼装。
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// 共享常量
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
export const PLATFORM_TABLE = [
|
|
18
|
-
['1', 'qidian', '长篇', '起点'],
|
|
19
|
-
['2', 'fanqie', '长篇', '番茄'],
|
|
20
|
-
['3', 'jinjiang', '长篇', '晋江'],
|
|
21
|
-
['4', 'zhihu', '长/短','知乎'],
|
|
22
|
-
['5', 'dianzhong','短篇', '点众'],
|
|
23
|
-
['6', 'qimao', '长/短','七猫'],
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
export function platformLabel(platform) {
|
|
27
|
-
const row = PLATFORM_TABLE.find(r => r[1] === platform);
|
|
28
|
-
return row ? row[3] : platform;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// fancy-bootstrap 模板
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
export const bootstrap = {
|
|
36
|
-
askPath(question = '📂 请输入项目根路径 ( 或者输入 . 代表当前路径 )') {
|
|
37
|
-
return { question };
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
notify({ type, projectRoot }) {
|
|
41
|
-
switch (type) {
|
|
42
|
-
case 'no_path':
|
|
43
|
-
return '⚠️ 未提供路径,已取消';
|
|
44
|
-
case 'using_cwd':
|
|
45
|
-
return `📂 使用当前目录:${projectRoot}`;
|
|
46
|
-
case 'invalid_path':
|
|
47
|
-
return '❌ 路径不合法(不能含空格、.. 或特殊字符)';
|
|
48
|
-
case 'already_initialized':
|
|
49
|
-
return '⚠️ 该目录已初始化(.fancy-deployed 已存在)';
|
|
50
|
-
case 'confirm':
|
|
51
|
-
return `❓ 确认将以下路径作为项目根?\n${projectRoot}`;
|
|
52
|
-
case 'cancelled':
|
|
53
|
-
return '🚫 已取消';
|
|
54
|
-
case 'initializing':
|
|
55
|
-
return `⏳ 正在初始化 ${projectRoot}...`;
|
|
56
|
-
case 'success':
|
|
57
|
-
return '✅ 项目初始化完成!\n下一步:运行 /fancy-scan 扫榜';
|
|
58
|
-
case 'error':
|
|
59
|
-
return (err) => '❌ 初始化失败:' + err;
|
|
60
|
-
default:
|
|
61
|
-
return String(type);
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
confirmOptions() {
|
|
66
|
-
return [
|
|
67
|
-
{ label: '确认创建', description: '开始在此目录初始化项目' },
|
|
68
|
-
{ label: '取消', description: '取消本次操作' },
|
|
69
|
-
];
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
// fancy-scan 模板
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
|
|
77
|
-
export const scan = {
|
|
78
|
-
platformList() {
|
|
79
|
-
return (
|
|
80
|
-
'📋 支持的平台:\n' +
|
|
81
|
-
PLATFORM_TABLE.map(r => ` ${r[0]}. ${r[3]}(${r[2]})`).join('\n') +
|
|
82
|
-
'\n\n- 长篇:关注 追读、月票、订阅 等指标\n' +
|
|
83
|
-
'- 短篇:关注 传播、完读率 等指标\n' +
|
|
84
|
-
'- 知乎和七猫同时支持长篇和短篇'
|
|
85
|
-
);
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
askPlatform() {
|
|
89
|
-
return { question: '📊 请选择要扫的平台(输入编号 1-6):' };
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
askLength(platform) {
|
|
93
|
-
return {
|
|
94
|
-
question: `${platformLabel(platform)} 同时支持长篇和短篇,请选择:` ,
|
|
95
|
-
hideCustomInput: true,
|
|
96
|
-
options: [
|
|
97
|
-
{ label: '长篇', description: '' },
|
|
98
|
-
{ label: '短篇', description: '' },
|
|
99
|
-
],
|
|
100
|
-
};
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
notify({ type, platform, length, files, receipt, topicFile, projectRoot, err }) {
|
|
104
|
-
const lenStr = length === 'long' ? '长篇' : '短篇';
|
|
105
|
-
const pLabel = platformLabel(platform);
|
|
106
|
-
|
|
107
|
-
switch (type) {
|
|
108
|
-
case 'not_initialized':
|
|
109
|
-
return '⚠️ 当前目录尚未初始化。请先运行 /fancy-bootstrap 进行初始化。';
|
|
110
|
-
|
|
111
|
-
case 'invalid_choice':
|
|
112
|
-
return '❌ 无效选择,请输入 1-6 的编号。';
|
|
113
|
-
|
|
114
|
-
case 'cancelled':
|
|
115
|
-
return '🚫 已取消';
|
|
116
|
-
|
|
117
|
-
case 'cancelled_with_hint':
|
|
118
|
-
return '⚠️ 已取消。请再次调用 /fancy-scan 继续。';
|
|
119
|
-
|
|
120
|
-
case 'collecting':
|
|
121
|
-
return `⏳ 正在采集 ${pLabel}(${lenStr})...`;
|
|
122
|
-
|
|
123
|
-
case 'scan_complete':
|
|
124
|
-
if (!files || files.length === 0) return `✅ ${pLabel}(${lenStr})采集完成`;
|
|
125
|
-
const fileList = files.map(f => ' - ' + f.replace(projectRoot + '/', '')).join('\n');
|
|
126
|
-
return `✅ ${pLabel}(${lenStr})采集完成\n\n📁 生成文件:\n${fileList}`;
|
|
127
|
-
|
|
128
|
-
case 'failed':
|
|
129
|
-
return '❌ 采集失败:' + err;
|
|
130
|
-
|
|
131
|
-
case 'parse_error':
|
|
132
|
-
return '❌ 采集结果解析失败';
|
|
133
|
-
|
|
134
|
-
case 'handover':
|
|
135
|
-
return (
|
|
136
|
-
`📊 采集完成,正在将数据交给 LLM 进行分析...\n` +
|
|
137
|
-
`📁 生成文件:\n${(files || []).map(f => '- ' + f.replace(projectRoot + '/', '')).join('\n')}\n` +
|
|
138
|
-
`💡 分析报告将写入:${(topicFile || '').replace(projectRoot + '/', '')}`
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
default:
|
|
142
|
-
return String(type);
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* 发送给 LLM 的分析 prompt 模板
|
|
148
|
-
* @param {object} params
|
|
149
|
-
* @param params.platform
|
|
150
|
-
* @param params.length
|
|
151
|
-
* @param params.date
|
|
152
|
-
* @param params.files string[] — 完整绝对路径列表
|
|
153
|
-
* @param params.topicFile string — topic_decision 文件完整路径
|
|
154
|
-
* @param params.projectRoot string
|
|
155
|
-
*/
|
|
156
|
-
llmAnalysisPrompt({ platform, length, date, files, topicFile, projectRoot }) {
|
|
157
|
-
const pLabel = platformLabel(platform);
|
|
158
|
-
const lenStr = length === 'long' ? '长篇' : '短篇';
|
|
159
|
-
const fileList = (files || [])
|
|
160
|
-
.map(f => '- ' + f.replace(projectRoot + '/', ''))
|
|
161
|
-
.join('\n');
|
|
162
|
-
|
|
163
|
-
return [
|
|
164
|
-
`## 扫榜数据分析任务`,
|
|
165
|
-
``,
|
|
166
|
-
`平台:${pLabel}(${lenStr})`,
|
|
167
|
-
`时间:${date || ''}`,
|
|
168
|
-
``,
|
|
169
|
-
`已生成的文件:`,
|
|
170
|
-
fileList,
|
|
171
|
-
``,
|
|
172
|
-
`请执行以下步骤:`,
|
|
173
|
-
`1. 读取上述生成的原始数据文件(扫榜结果目录下的 .md 文件)`,
|
|
174
|
-
`2. 分析爆款书籍的题材、卖点、节奏、人设等特征`,
|
|
175
|
-
`3. 生成扫榜报告(包含:市场趋势、热门题材分析、用户画像、竞争度评估)`,
|
|
176
|
-
`4. 给出 3-5 个可行的选题方向建议`,
|
|
177
|
-
`5. 将完整分析报告追加写入:${topicFile}`,
|
|
178
|
-
``,
|
|
179
|
-
`报告要求:`,
|
|
180
|
-
`- 客观分析数据,不臆测`,
|
|
181
|
-
`- 选题建议要有差异化竞争力`,
|
|
182
|
-
`- 报告语言:中文`,
|
|
183
|
-
].join('\n');
|
|
184
|
-
},
|
|
185
|
-
};
|