@stylewx/service 0.1.0
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/dist/analyze.d.ts +18 -0
- package/dist/analyze.d.ts.map +1 -0
- package/dist/analyze.js +109 -0
- package/dist/analyze.js.map +1 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +53 -0
- package/dist/errors.js.map +1 -0
- package/dist/generate.d.ts +32 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +229 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.d.ts +54 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +190 -0
- package/dist/llm.js.map +1 -0
- package/dist/optimize.d.ts +12 -0
- package/dist/optimize.d.ts.map +1 -0
- package/dist/optimize.js +21 -0
- package/dist/optimize.js.map +1 -0
- package/dist/publish.d.ts +13 -0
- package/dist/publish.d.ts.map +1 -0
- package/dist/publish.js +19 -0
- package/dist/publish.js.map +1 -0
- package/dist/render.d.ts +17 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +35 -0
- package/dist/render.js.map +1 -0
- package/dist/service.test.d.ts +2 -0
- package/dist/service.test.d.ts.map +1 -0
- package/dist/service.test.js +163 -0
- package/dist/service.test.js.map +1 -0
- package/dist/theme-store.d.ts +28 -0
- package/dist/theme-store.d.ts.map +1 -0
- package/dist/theme-store.js +93 -0
- package/dist/theme-store.js.map +1 -0
- package/dist/theme-store.test.d.ts +2 -0
- package/dist/theme-store.test.d.ts.map +1 -0
- package/dist/theme-store.test.js +70 -0
- package/dist/theme-store.test.js.map +1 -0
- package/dist/themes.d.ts +15 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +38 -0
- package/dist/themes.js.map +1 -0
- package/package.json +36 -0
package/dist/llm.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI 兼容的 LLM 客户端(结构化输出)。
|
|
3
|
+
* 通过 JSON Schema 约束模型输出,供 generate_theme 使用。
|
|
4
|
+
*
|
|
5
|
+
* 环境变量:
|
|
6
|
+
* - LLM_BASE_URL OpenAI 兼容 API 基地址(如 https://api.openai.com/v1,或 opencode go 的 https://opencode.ai/zen/go/v1)
|
|
7
|
+
* - LLM_API_KEY API 密钥
|
|
8
|
+
* - LLM_MODEL 模型名
|
|
9
|
+
* - LLM_API_STYLE 端点风格:'chat'(/chat/completions,默认)或 'responses'(/responses,如 opencode go)
|
|
10
|
+
* 任何凭据都只从环境读取,绝不硬编码。
|
|
11
|
+
*/
|
|
12
|
+
/** 从环境变量加载 LLM 配置;缺少关键项或 style 不合法时给出明确错误。 */
|
|
13
|
+
export function loadLlmConfigFromEnv(env = process.env) {
|
|
14
|
+
const baseUrl = env.LLM_BASE_URL?.trim() ?? '';
|
|
15
|
+
const apiKey = env.LLM_API_KEY?.trim() ?? '';
|
|
16
|
+
const model = env.LLM_MODEL?.trim() ?? '';
|
|
17
|
+
const rawStyle = (env.LLM_API_STYLE?.trim() ?? 'chat').toLowerCase();
|
|
18
|
+
const apiStyle = rawStyle === 'responses' ? 'responses' : 'chat';
|
|
19
|
+
const missing = [];
|
|
20
|
+
if (!baseUrl)
|
|
21
|
+
missing.push('LLM_BASE_URL');
|
|
22
|
+
if (!apiKey)
|
|
23
|
+
missing.push('LLM_API_KEY');
|
|
24
|
+
if (!model)
|
|
25
|
+
missing.push('LLM_MODEL');
|
|
26
|
+
if (missing.length > 0) {
|
|
27
|
+
throw new Error(`缺少 LLM 环境变量:${missing.join('、')}。generate_theme 需要一个 OpenAI 兼容接口。示例:LLM_BASE_URL=https://api.openai.com/v1 LLM_API_KEY=... LLM_MODEL=gpt-4o-mini`);
|
|
28
|
+
}
|
|
29
|
+
return { baseUrl, apiKey, model, apiStyle };
|
|
30
|
+
}
|
|
31
|
+
function extractJson(text) {
|
|
32
|
+
const start = text.indexOf('{');
|
|
33
|
+
const end = text.lastIndexOf('}');
|
|
34
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
35
|
+
throw new Error('模型输出中未找到 JSON 对象。');
|
|
36
|
+
}
|
|
37
|
+
return JSON.parse(text.slice(start, end + 1));
|
|
38
|
+
}
|
|
39
|
+
export class LlmClient {
|
|
40
|
+
config;
|
|
41
|
+
fetchImpl;
|
|
42
|
+
constructor(config) {
|
|
43
|
+
this.config = config;
|
|
44
|
+
this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
45
|
+
}
|
|
46
|
+
get apiStyle() {
|
|
47
|
+
return this.config.apiStyle ?? 'chat';
|
|
48
|
+
}
|
|
49
|
+
/** 组装 JSON 结构输出请求体(按端点风格差异)。 */
|
|
50
|
+
buildJsonPayload(messages, options, fallbackToText) {
|
|
51
|
+
const temperature = options.temperature ?? 0.4;
|
|
52
|
+
const maxTokens = options.maxTokens ?? 1200;
|
|
53
|
+
if (this.apiStyle === 'responses') {
|
|
54
|
+
const textFormat = fallbackToText
|
|
55
|
+
? { type: 'text' }
|
|
56
|
+
: {
|
|
57
|
+
type: 'json_schema',
|
|
58
|
+
name: options.name,
|
|
59
|
+
strict: true,
|
|
60
|
+
schema: options.schema,
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
model: this.config.model,
|
|
64
|
+
input: toResponsesInput(messages),
|
|
65
|
+
temperature,
|
|
66
|
+
max_output_tokens: maxTokens,
|
|
67
|
+
stream: false,
|
|
68
|
+
text: { format: textFormat },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// chat / completions
|
|
72
|
+
return {
|
|
73
|
+
model: this.config.model,
|
|
74
|
+
messages,
|
|
75
|
+
temperature,
|
|
76
|
+
max_tokens: maxTokens,
|
|
77
|
+
response_format: fallbackToText
|
|
78
|
+
? { type: 'text' }
|
|
79
|
+
: {
|
|
80
|
+
type: 'json_schema',
|
|
81
|
+
json_schema: {
|
|
82
|
+
name: options.name,
|
|
83
|
+
strict: true,
|
|
84
|
+
schema: options.schema,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 请求结构化 JSON 输出。
|
|
91
|
+
* 优先用 json_schema 约束模型;若端点报错,则退化到普通文本输出并从文本中提取 JSON。
|
|
92
|
+
*/
|
|
93
|
+
async completeJson(messages, options) {
|
|
94
|
+
try {
|
|
95
|
+
const text = await this.request(this.buildJsonPayload(messages, options, false));
|
|
96
|
+
return extractJson(text);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// 退化:不约束 schema,交由 extractJson 从纯文本中提取
|
|
100
|
+
const text = await this.request(this.buildJsonPayload(messages, options, true));
|
|
101
|
+
return extractJson(text);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** 请求纯文本输出。 */
|
|
105
|
+
async completeText(messages, options = {}) {
|
|
106
|
+
const temperature = options.temperature ?? 0.4;
|
|
107
|
+
const maxTokens = options.maxTokens ?? 1024;
|
|
108
|
+
const payload = this.apiStyle === 'responses'
|
|
109
|
+
? {
|
|
110
|
+
model: this.config.model,
|
|
111
|
+
input: toResponsesInput(messages),
|
|
112
|
+
temperature,
|
|
113
|
+
max_output_tokens: maxTokens,
|
|
114
|
+
stream: false,
|
|
115
|
+
text: { format: { type: 'text' } },
|
|
116
|
+
}
|
|
117
|
+
: {
|
|
118
|
+
model: this.config.model,
|
|
119
|
+
messages,
|
|
120
|
+
temperature,
|
|
121
|
+
max_tokens: maxTokens,
|
|
122
|
+
};
|
|
123
|
+
return this.request(payload);
|
|
124
|
+
}
|
|
125
|
+
endpoint() {
|
|
126
|
+
const path = this.apiStyle === 'responses' ? '/responses' : '/chat/completions';
|
|
127
|
+
return this.config.baseUrl.replace(/\/+$/, '') + path;
|
|
128
|
+
}
|
|
129
|
+
async request(payload) {
|
|
130
|
+
const response = await this.fetchImpl(this.endpoint(), {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'content-type': 'application/json',
|
|
134
|
+
authorization: `Bearer ${this.config.apiKey}`,
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify(payload),
|
|
137
|
+
});
|
|
138
|
+
const text = await response.text();
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
let detail = text;
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(text);
|
|
143
|
+
detail = parsed.error?.message ?? text;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// keep raw
|
|
147
|
+
}
|
|
148
|
+
throw new Error(`LLM 请求失败(HTTP ${response.status}):${detail}`);
|
|
149
|
+
}
|
|
150
|
+
const data = JSON.parse(text);
|
|
151
|
+
return extractOutputText(data, this.apiStyle);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** 把 chat 消息列表转换成 Responses API 的 input 结构。 */
|
|
155
|
+
function toResponsesInput(messages) {
|
|
156
|
+
return messages.map((m) => ({
|
|
157
|
+
role: m.role,
|
|
158
|
+
content: [{ type: 'input_text', text: m.content }],
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
/** 按端点风格从响应 JSON 中提取正文文本。 */
|
|
162
|
+
function extractOutputText(data, apiStyle) {
|
|
163
|
+
if (apiStyle === 'responses') {
|
|
164
|
+
const output = Array.isArray(data.output) ? data.output : [];
|
|
165
|
+
const chunks = [];
|
|
166
|
+
for (const item of output) {
|
|
167
|
+
if (item.type === 'message') {
|
|
168
|
+
const content = Array.isArray(item.content) ? item.content : [];
|
|
169
|
+
for (const c of content) {
|
|
170
|
+
if (c.type === 'output_text' && typeof c.text === 'string')
|
|
171
|
+
chunks.push(c.text);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const text = chunks.join('');
|
|
176
|
+
if (text.trim())
|
|
177
|
+
return text;
|
|
178
|
+
// 兜底:某些端点把 output_text 放顶层
|
|
179
|
+
const top = data.output_text;
|
|
180
|
+
if (typeof top === 'string' && top.trim())
|
|
181
|
+
return top;
|
|
182
|
+
throw new Error('LLM 返回内容为空(Responses 响应中未找到 output_text)。');
|
|
183
|
+
}
|
|
184
|
+
const choices = Array.isArray(data.choices) ? data.choices : [];
|
|
185
|
+
const content = choices[0]?.message?.content;
|
|
186
|
+
if (typeof content === 'string' && content.trim())
|
|
187
|
+
return content;
|
|
188
|
+
throw new Error('LLM 返回内容为空。');
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=llm.js.map
|
package/dist/llm.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI 优化文章正文(供本地编辑器「AI 优化」按钮使用)。
|
|
3
|
+
* 用 LLM 在不改变事实与主旨的前提下改进标题、结构与文风,返回优化后的 Markdown。
|
|
4
|
+
* 需要配置 LLM 环境变量;缺 LLM 时抛出清晰错误。
|
|
5
|
+
*/
|
|
6
|
+
import type { LlmClient } from './llm.js';
|
|
7
|
+
export interface OptimizeResult {
|
|
8
|
+
/** 优化后的完整 Markdown 正文。 */
|
|
9
|
+
markdown: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function optimizeArticle(markdown: string, llm: LlmClient): Promise<OptimizeResult>;
|
|
12
|
+
//# sourceMappingURL=optimize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../src/optimize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,UAAU,CAAA;AAGrD,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACjB;AAWD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAU/F"}
|
package/dist/optimize.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { serviceError } from './errors.js';
|
|
2
|
+
const SYSTEM_PROMPT = [
|
|
3
|
+
'你是公众号文章优化助手。请在不改变事实与主旨的前提下,优化下面这篇 Markdown 文章:',
|
|
4
|
+
'1. 改进第一行的标题(通常以 # 开头),让它更有吸引力;',
|
|
5
|
+
'2. 优化小标题的层级与结构,让逻辑更清晰;',
|
|
6
|
+
'3. 让语句更通顺、段落衔接更自然;',
|
|
7
|
+
'4. 保留所有 Markdown 语法(列表、引用、表格、代码块、加粗、斜体、高亮等)与结构。',
|
|
8
|
+
'只输出优化后的完整 Markdown 正文本身,不要任何解释、前言,也不要额外用三反引号把整篇包起来。',
|
|
9
|
+
].join('\n');
|
|
10
|
+
export async function optimizeArticle(markdown, llm) {
|
|
11
|
+
if (!markdown.trim()) {
|
|
12
|
+
throw serviceError('missing_content', '缺少文章正文。', '请先在编辑器里输入 Markdown 内容,再点「AI 优化」。');
|
|
13
|
+
}
|
|
14
|
+
const messages = [
|
|
15
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
16
|
+
{ role: 'user', content: markdown },
|
|
17
|
+
];
|
|
18
|
+
const out = await llm.completeText(messages, { temperature: 0.7 });
|
|
19
|
+
return { markdown: out.trim() };
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=optimize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimize.js","sourceRoot":"","sources":["../src/optimize.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAO1C,MAAM,aAAa,GAAG;IACpB,gDAAgD;IAChD,gCAAgC;IAChC,wBAAwB;IACxB,oBAAoB;IACpB,iDAAiD;IACjD,qDAAqD;CACtD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEZ,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,GAAc;IACpE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACrB,MAAM,YAAY,CAAC,iBAAiB,EAAE,SAAS,EAAE,kCAAkC,CAAC,CAAA;IACtF,CAAC;IACD,MAAM,QAAQ,GAAiB;QAC7B,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE;QAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE;KACpC,CAAA;IACD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAA;IAClE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,CAAA;AACjC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ValidationReport } from '@stylewx/validator';
|
|
2
|
+
import type { PublishParams, PublishResult } from '@stylewx/publisher';
|
|
3
|
+
import type { WeChatClient } from '@stylewx/publisher';
|
|
4
|
+
import type { ServiceError } from './errors.js';
|
|
5
|
+
export declare function validateArticle(html: string): {
|
|
6
|
+
report: ValidationReport;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* 发布草稿(包装 publisher)。微信客户端由调用方注入。
|
|
10
|
+
* 任何异常都会被包装为统一错误格式。
|
|
11
|
+
*/
|
|
12
|
+
export declare function publishDraft(wechat: WeChatClient, params: PublishParams): Promise<PublishResult | ServiceError>;
|
|
13
|
+
//# sourceMappingURL=publish.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAE1E;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,aAAa,GAAG,YAAY,CAAC,CAMvC"}
|
package/dist/publish.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { validateHtml } from '@stylewx/validator';
|
|
2
|
+
import { publishDraft as publisherPublishDraft } from '@stylewx/publisher';
|
|
3
|
+
import { asServiceError } from './errors.js';
|
|
4
|
+
export function validateArticle(html) {
|
|
5
|
+
return { report: validateHtml(html) };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 发布草稿(包装 publisher)。微信客户端由调用方注入。
|
|
9
|
+
* 任何异常都会被包装为统一错误格式。
|
|
10
|
+
*/
|
|
11
|
+
export async function publishDraft(wechat, params) {
|
|
12
|
+
try {
|
|
13
|
+
return await publisherPublishDraft(wechat, params);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return asServiceError(error, 'publish_failed');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=publish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish.js","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAG1E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAG5C,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAA;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAoB,EACpB,MAAqB;IAErB,IAAI,CAAC;QACH,OAAO,MAAM,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,cAAc,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAA;IAChD,CAAC;AACH,CAAC"}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Theme } from '@stylewx/theme';
|
|
2
|
+
import type { ValidationReport } from '@stylewx/validator';
|
|
3
|
+
export interface RenderPreviewResult {
|
|
4
|
+
html: string;
|
|
5
|
+
theme: Theme;
|
|
6
|
+
validation: ValidationReport;
|
|
7
|
+
/** 模拟 iPhone 视口(390px)的截图 PNG(若已安装 Chromium)。 */
|
|
8
|
+
screenshotPng?: Buffer;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 渲染预览:Markdown + 主题 → 内联样式 HTML → 校验报告 → iPhone 视口截图。
|
|
12
|
+
* 截图失败不影响 HTML 与校验结果(降级返回无截图)。
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderPreview(markdown: string, theme: Theme, options?: {
|
|
15
|
+
includeScreenshot?: boolean;
|
|
16
|
+
}): Promise<RenderPreviewResult>;
|
|
17
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAK1D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,KAAK,CAAA;IACZ,UAAU,EAAE,gBAAgB,CAAA;IAC5B,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,EACZ,OAAO,GAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC5C,OAAO,CAAC,mBAAmB,CAAC,CA+B9B"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { renderMarkdownToHtml } from '@stylewx/core';
|
|
2
|
+
import { validateTheme } from '@stylewx/theme';
|
|
3
|
+
import { validateHtml } from '@stylewx/validator';
|
|
4
|
+
import { renderIphonePreview } from '@stylewx/preview';
|
|
5
|
+
import { serviceError } from './errors.js';
|
|
6
|
+
/**
|
|
7
|
+
* 渲染预览:Markdown + 主题 → 内联样式 HTML → 校验报告 → iPhone 视口截图。
|
|
8
|
+
* 截图失败不影响 HTML 与校验结果(降级返回无截图)。
|
|
9
|
+
*/
|
|
10
|
+
export async function renderPreview(markdown, theme, options = {}) {
|
|
11
|
+
const themeCheck = validateTheme(theme);
|
|
12
|
+
if (!themeCheck.ok || !themeCheck.theme) {
|
|
13
|
+
const detail = themeCheck.issues.map((i) => `${i.path}: ${i.message}`).join(';');
|
|
14
|
+
throw serviceError('invalid_theme', `主题不合法:${detail}`, '请用符合主题 Schema 的 JSON 传入 theme,或先用 generate_theme 生成主题。');
|
|
15
|
+
}
|
|
16
|
+
const { html } = renderMarkdownToHtml(markdown, themeCheck.theme);
|
|
17
|
+
const validation = validateHtml(html);
|
|
18
|
+
const result = {
|
|
19
|
+
html,
|
|
20
|
+
theme: themeCheck.theme,
|
|
21
|
+
validation,
|
|
22
|
+
};
|
|
23
|
+
if (options.includeScreenshot !== false) {
|
|
24
|
+
try {
|
|
25
|
+
const { png } = await renderIphonePreview(html);
|
|
26
|
+
result.screenshotPng = png;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// 截图失败不致命:保留 HTML 与校验结果,仅提示。
|
|
30
|
+
void error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAW1C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,KAAY,EACZ,UAA2C,EAAE;IAE7C,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;IACvC,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAChF,MAAM,YAAY,CAChB,eAAe,EACf,SAAS,MAAM,EAAE,EACjB,wDAAwD,CACzD,CAAA;IACH,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,GAAG,oBAAoB,CAAC,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,CAAA;IACjE,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IAErC,MAAM,MAAM,GAAwB;QAClC,IAAI;QACJ,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,UAAU;KACX,CAAA;IAED,IAAI,OAAO,CAAC,iBAAiB,KAAK,KAAK,EAAE,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAA;YAC/C,MAAM,CAAC,aAAa,GAAG,GAAG,CAAA;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,6BAA6B;YAC7B,KAAK,KAAK,CAAA;QACZ,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.test.d.ts","sourceRoot":"","sources":["../src/service.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { listThemes, analyzeArticle, renderPreview, validateArticle, publishDraft, generateTheme, optimizeArticle, serviceError, } from './index.js';
|
|
3
|
+
import { getPresetTheme, validateTheme } from '@stylewx/theme';
|
|
4
|
+
import { WeChatClient } from '@stylewx/publisher';
|
|
5
|
+
describe('optimizeArticle', () => {
|
|
6
|
+
it('用桩 LLM 返回优化后的 Markdown', async () => {
|
|
7
|
+
const stub = { completeText: async () => '# 优化标题\n\n优化后的正文。' };
|
|
8
|
+
const r = await optimizeArticle('# 标题\n\n正文。', stub);
|
|
9
|
+
expect(r.markdown).toContain('优化标题');
|
|
10
|
+
});
|
|
11
|
+
it('空正文抛 missing_content', async () => {
|
|
12
|
+
const stub = { completeText: async () => '' };
|
|
13
|
+
await expect(optimizeArticle(' ', stub)).rejects.toMatchObject({ error: { code: 'missing_content' } });
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe('listThemes', () => {
|
|
17
|
+
it('返回至少 6 套预置主题', () => {
|
|
18
|
+
const { themes } = listThemes();
|
|
19
|
+
expect(themes.length).toBeGreaterThanOrEqual(6);
|
|
20
|
+
expect(themes[0]).toHaveProperty('name');
|
|
21
|
+
expect(themes[0]).toHaveProperty('description');
|
|
22
|
+
expect(themes[0]?.tokens).toHaveProperty('primaryColor');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('analyzeArticle', () => {
|
|
26
|
+
it('技术类内容被识别并给出建议主题', () => {
|
|
27
|
+
const r = analyzeArticle('本文介绍前端框架的部署与性能优化,包含 function 概念。');
|
|
28
|
+
expect(r.content.type).toBe('tech');
|
|
29
|
+
expect(r.suggestedTheme.name).toBe('tech-minimal');
|
|
30
|
+
expect(r.content.readingMinutes).toBeGreaterThanOrEqual(1);
|
|
31
|
+
});
|
|
32
|
+
it('政务类内容被识别', () => {
|
|
33
|
+
const r = analyzeArticle('关于落实党建精神的通知,请各党委组织学习。');
|
|
34
|
+
expect(r.content.type).toBe('government');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('renderPreview', () => {
|
|
38
|
+
it('渲染并校验通过,无 <style>/class=', { timeout: 30000 }, async () => {
|
|
39
|
+
const theme = getPresetTheme('tech-minimal');
|
|
40
|
+
const r = await renderPreview('# 标题\n\n正文内容。\n\n- 项一\n', theme);
|
|
41
|
+
expect(r.html).not.toContain('<style');
|
|
42
|
+
expect(r.html).not.toContain('class=');
|
|
43
|
+
expect(r.validation.pass).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
it('非法主题抛出统一错误', async () => {
|
|
46
|
+
const bad = structuredClone(getPresetTheme('tech-minimal'));
|
|
47
|
+
bad.blocks.p['position'] = 'absolute';
|
|
48
|
+
await expect(renderPreview('# x', bad)).rejects.toMatchObject({ error: { code: 'invalid_theme' } });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('validateArticle', () => {
|
|
52
|
+
it('返回结构化报告', () => {
|
|
53
|
+
const { report } = validateArticle('<section><p style="color:#333">x</p></section>');
|
|
54
|
+
expect(report.pass).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
it('检测微信不兼容内容', () => {
|
|
57
|
+
const { report } = validateArticle('<section><script>x</script></section>');
|
|
58
|
+
expect(report.pass).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('publishDraft (service)', () => {
|
|
62
|
+
function makeClient(failDraft = false) {
|
|
63
|
+
const fetchMock = async (input) => {
|
|
64
|
+
const url = String(input);
|
|
65
|
+
if (url.includes('/cgi-bin/token'))
|
|
66
|
+
return Response.json({ access_token: 't', expires_in: 7200 });
|
|
67
|
+
if (url.includes('/cgi-bin/material/add_material'))
|
|
68
|
+
return Response.json({ media_id: 'm', url: 'https://mmbiz.qpic.cn/u' });
|
|
69
|
+
if (url.includes('/cgi-bin/draft/add')) {
|
|
70
|
+
if (failDraft)
|
|
71
|
+
return Response.json({ errcode: 48001, errmsg: 'forbidden' }, { status: 200 });
|
|
72
|
+
return Response.json({ media_id: 'draft_x' }, { status: 200 });
|
|
73
|
+
}
|
|
74
|
+
if (url.includes('example.com'))
|
|
75
|
+
return new Response(new Uint8Array([1]), { status: 200, headers: { 'content-type': 'image/png' } });
|
|
76
|
+
return Response.json({});
|
|
77
|
+
};
|
|
78
|
+
return new WeChatClient({
|
|
79
|
+
appId: 'a', appSecret: 's', baseUrl: 'https://api.weixin.qq.com',
|
|
80
|
+
fetchImpl: fetchMock,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
it('发布成功返回媒体 id', async () => {
|
|
84
|
+
const r = await publishDraft(makeClient(), {
|
|
85
|
+
title: '标题', content: '<section><p>hello</p></section>', coverImage: 'https://example.com/c.png',
|
|
86
|
+
});
|
|
87
|
+
expect(r).not.toHaveProperty('error');
|
|
88
|
+
expect(r.media_id).toBe('draft_x');
|
|
89
|
+
});
|
|
90
|
+
it('微信接口失败被包装为统一错误', async () => {
|
|
91
|
+
const r = await publishDraft(makeClient(true), {
|
|
92
|
+
title: '标题', content: '<section><p>x</p></section>', coverImage: 'https://example.com/c.png',
|
|
93
|
+
});
|
|
94
|
+
expect(r).toHaveProperty('error');
|
|
95
|
+
expect(r.error.message).toContain('errcode');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('预置主题(含 WeMD 移植)渲染后都通过微信白名单校验', { timeout: 60000 }, () => {
|
|
99
|
+
it('每一套预置主题渲染出的 HTML 都通过 validator(无白名单外属性)', async () => {
|
|
100
|
+
const { themes } = listThemes();
|
|
101
|
+
expect(themes.length).toBeGreaterThanOrEqual(26);
|
|
102
|
+
const sample = '# 标题\n\n正文 **加粗** 与 [链接](https://a.com)。\n\n> 引用\n\n- 项一\n- 项二\n\n```\ncode\n```\n\n\n\n---\n';
|
|
103
|
+
for (const theme of themes) {
|
|
104
|
+
const r = await renderPreview(sample, theme, { includeScreenshot: false });
|
|
105
|
+
expect(r.validation.pass, `${theme.name} 渲染后应通过校验: ${JSON.stringify(r.validation.issues.slice(0, 3))}`).toBe(true);
|
|
106
|
+
expect(r.html).not.toContain('<style');
|
|
107
|
+
expect(r.html).not.toContain('class=');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('serviceError', () => {
|
|
112
|
+
it('生成统一错误格式', () => {
|
|
113
|
+
const e = serviceError('x', 'm', 'h');
|
|
114
|
+
expect(e).toEqual({ error: { code: 'x', message: 'm', hint: 'h' } });
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe('generateTheme (LLM 修复循环)', () => {
|
|
118
|
+
function fakeLlm(responses) {
|
|
119
|
+
let i = 0;
|
|
120
|
+
const completeJson = async (_m, _o) => {
|
|
121
|
+
const r = responses[i];
|
|
122
|
+
i += 1;
|
|
123
|
+
if (r instanceof Error)
|
|
124
|
+
throw r;
|
|
125
|
+
return r;
|
|
126
|
+
};
|
|
127
|
+
return { completeJson };
|
|
128
|
+
}
|
|
129
|
+
const validTheme = getPresetTheme('tech-minimal');
|
|
130
|
+
const invalidTheme = () => {
|
|
131
|
+
const t = structuredClone(validTheme);
|
|
132
|
+
delete t.blocks.h1; // 缺 h1 → schema 失败
|
|
133
|
+
return t;
|
|
134
|
+
};
|
|
135
|
+
it('LLM 直接返回合法主题:success, fallback=false', { timeout: 30000 }, async () => {
|
|
136
|
+
const r = await generateTheme({ prompt: '科技风' }, fakeLlm([validTheme]));
|
|
137
|
+
expect(r.fallback).toBe(false);
|
|
138
|
+
expect(r.repairAttempts).toBe(1);
|
|
139
|
+
expect(validateTheme(r.theme).ok).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
it('第一次非法、第二次合法:修复成功', { timeout: 30000 }, async () => {
|
|
142
|
+
const r = await generateTheme({ prompt: '科技风' }, fakeLlm([invalidTheme(), validTheme]));
|
|
143
|
+
expect(r.fallback).toBe(false);
|
|
144
|
+
expect(r.repairAttempts).toBe(2);
|
|
145
|
+
});
|
|
146
|
+
it('始终非法:降级到预置主题并标记 fallback', { timeout: 30000 }, async () => {
|
|
147
|
+
const r = await generateTheme({ prompt: '科技风' }, fakeLlm([invalidTheme(), invalidTheme(), invalidTheme()]));
|
|
148
|
+
expect(r.fallback).toBe(true);
|
|
149
|
+
expect(r.errorDetail).toContain('h1');
|
|
150
|
+
expect(validateTheme(r.theme).ok).toBe(true);
|
|
151
|
+
expect(PRESET_NAMES).toContain(r.theme.name);
|
|
152
|
+
});
|
|
153
|
+
it('提供文章时先生成内容分析,并返回分析结论', { timeout: 30000 }, async () => {
|
|
154
|
+
const analysis = {
|
|
155
|
+
contentType: 'tech', tone: '中性', industry: '互联网', designDirection: '冷色简洁',
|
|
156
|
+
};
|
|
157
|
+
const r = await generateTheme({ article: '这篇讲前端技术。' }, fakeLlm([analysis, validTheme]));
|
|
158
|
+
expect(r.analysis?.contentType).toBe('tech');
|
|
159
|
+
expect(r.fallback).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
const PRESET_NAMES = listThemes().themes.map((t) => t.name);
|
|
163
|
+
//# sourceMappingURL=service.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.test.js","sourceRoot":"","sources":["../src/service.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Theme } from '@stylewx/theme';
|
|
2
|
+
export interface ThemeStoreOptions {
|
|
3
|
+
/** 覆盖存储文件路径(测试用)。 */
|
|
4
|
+
file?: string;
|
|
5
|
+
}
|
|
6
|
+
/** 列出所有已保存的自定义主题。 */
|
|
7
|
+
export declare function listSavedThemes(opts?: ThemeStoreOptions): {
|
|
8
|
+
themes: Theme[];
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* 保存一个主题(校验通过后写入本地库)。同名会覆盖。
|
|
12
|
+
* @returns 保存后的主题(已通过 schema + 白名单校验)。
|
|
13
|
+
*/
|
|
14
|
+
export declare function saveTheme(input: unknown, opts?: ThemeStoreOptions): {
|
|
15
|
+
theme: Theme;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 导出主题为完整 JSON(供复用/分享)。支持:已保存的自定义主题名、预置主题名、或完整主题对象。
|
|
19
|
+
* @returns 可直接传给 render_preview / publish_draft 复用的完整主题对象。
|
|
20
|
+
*/
|
|
21
|
+
export declare function exportTheme(input: unknown, opts?: ThemeStoreOptions): {
|
|
22
|
+
theme: Theme;
|
|
23
|
+
};
|
|
24
|
+
/** 删除一个已保存的主题。 */
|
|
25
|
+
export declare function deleteTheme(name: string, opts?: ThemeStoreOptions): {
|
|
26
|
+
deleted: string;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=theme-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-store.d.ts","sourceRoot":"","sources":["../src/theme-store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAM3C,MAAM,WAAW,iBAAiB;IAChC,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AA4BD,qBAAqB;AACrB,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG;IAAE,MAAM,EAAE,KAAK,EAAE,CAAA;CAAE,CAE7E;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAkBpF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAgBtF;AAED,kBAAkB;AAClB,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAOvF"}
|