仰望星辰工作室

clearlove2.1

clearlove2.1/ plugins/ai-polish/main.js 4.9 KB · 133 行 原始文件
1// AI 语句美化 —— 应用型插件(kind=app)
2//
3// 组合了两种能力,兼顾性能与功能:
4// 1. 声明式 hook(footer_html):静态注入按钮脚本,页面渲染零额外开销
5// 2. 应用型路由:GET /x/ai-polish/config 探测可用性;POST /x/ai-polish/polish 调用 AI 润色
6//
7// 复用站点「网站设置 → AI 模型接入」里已配置的接口,无需重复填写密钥。
8
9function setup() {
10 clv.route("GET", "/config", "readConfig", { auth: "none", json: true });
11 clv.route("POST", "/polish", "polish", { auth: "none", json: true });
12}
13
14/* ---------------- 配置与可用性 ---------------- */
15
16function aiReady() {
17 if (clv.setting.get("ai_enabled") !== "1") return false;
18 var base = clv.setting.get("ai_base");
19 var key = clv.setting.get("ai_key");
20 var model = clv.setting.get("ai_model");
21 return !!(base && key && model);
22}
23
24// readConfig 前端加载时探测一次:不可用时直接不显示按钮
25function readConfig(ctx) {
26 var requireLogin = clv.plugin.config("require_login") === "1";
27 var loggedIn = !!ctx.userId;
28 var ready = aiReady();
29 return {
30 json: {
31 ok: true,
32 available: ready && (!requireLogin || loggedIn),
33 label: clv.plugin.config("button_text") || "AI 美化",
34 max_chars: intOr(clv.plugin.config("max_chars"), 600),
35 reason: ready ? (requireLogin && !loggedIn ? "登录后可使用 AI 美化" : "") : "站点尚未接入 AI 模型"
36 }
37 };
38}
39
40/* ---------------- 核心:调用 AI 润色 ---------------- */
41
42function polish(ctx) {
43 if (!aiReady()) {
44 return { status: 400, json: { ok: false, msg: "站点尚未开启 AI 模型,请联系管理员在「网站设置 → AI 模型接入」中配置" } };
45 }
46 if (clv.plugin.config("require_login") === "1" && !ctx.userId) {
47 return { status: 401, json: { ok: false, msg: "请先登录后再使用 AI 美化" } };
48 }
49
50 var text = String((ctx.req && ctx.req.json && ctx.req.json.text) || "").trim();
51 var maxChars = intOr(clv.plugin.config("max_chars"), 600);
52 if (text.length < 2) {
53 return { status: 400, json: { ok: false, msg: "内容太短,无需美化" } };
54 }
55 if (text.length > maxChars) {
56 return { status: 400, json: { ok: false, msg: "内容超过 " + maxChars + " 字,请分段美化" } };
57 }
58
59 // 按访客 IP 限流(每分钟)
60 var limit = intOr(clv.plugin.config("rate_limit"), 3);
61 var rk = "rate:" + ((ctx.req && ctx.req.ip) || "unknown");
62 var used = Number(clv.cache.get(rk)) || 0;
63 if (used >= limit) {
64 return { status: 429, json: { ok: false, msg: "操作过于频繁,请稍后再试" } };
65 }
66 clv.cache.set(rk, used + 1, 60);
67
68 var base = String(clv.setting.get("ai_base") || "").replace(/\/+$/, "");
69 var key = clv.setting.get("ai_key") || "";
70 var model = clv.setting.get("ai_model") || "";
71 var temp = parseFloat(clv.plugin.config("temperature"));
72 if (isNaN(temp) || temp < 0 || temp > 1) temp = 0.7;
73 var prompt = clv.plugin.config("prompt") ||
74 "请在不改变原意的前提下润色下面的文字,只输出润色后的正文。";
75
76 var payload = JSON.stringify({
77 model: model,
78 messages: [
79 { role: "system", content: prompt },
80 { role: "user", content: text }
81 ],
82 temperature: temp
83 });
84
85 var resp;
86 try {
87 resp = clv.http.request({
88 method: "POST",
89 url: base + "/chat/completions",
90 headers: {
91 "Content-Type": "application/json",
92 "Authorization": "Bearer " + key
93 },
94 body: payload,
95 timeout_ms: 25000,
96 max_bytes: 262144
97 });
98 } catch (e) {
99 clv.log.warn("AI 请求失败:" + e);
100 return { status: 502, json: { ok: false, msg: "AI 服务连接失败,请稍后重试" } };
101 }
102
103 if (!resp || resp.status < 200 || resp.status >= 300) {
104 var code = resp ? resp.status : "无响应";
105 clv.log.warn("AI 返回异常状态:" + code);
106 return { status: 502, json: { ok: false, msg: "AI 服务返回异常(" + code + ")" } };
107 }
108
109 var data = null;
110 try { data = JSON.parse(resp.body); } catch (e) { data = null; }
111 var out = "";
112 if (data && data.choices && data.choices.length && data.choices[0].message) {
113 out = String(data.choices[0].message.content || "").trim();
114 }
115 // 去掉模型有时会加上的引号包裹
116 out = out.replace(/^["'“”‘’\s]+|["'“”‘’\s]+$/g, "").trim();
117 if (!out) {
118 return { status: 502, json: { ok: false, msg: "AI 未返回有效内容,请稍后重试" } };
119 }
120 // 防止模型输出失控(例如复述要求、长篇解释)
121 var cap = maxChars * 3;
122 if (out.length > cap) out = out.slice(0, cap) + "……";
123
124 clv.log.info("AI 美化完成:" + text.length + " 字 → " + out.length + " 字(用户 " + (ctx.userId || "匿名") + ")");
125 return { json: { ok: true, text: out } };
126}
127
128/* ---------------- 小工具 ---------------- */
129
130function intOr(v, def) {
131 var n = parseInt(v, 10);
132 return (isNaN(n) || n <= 0) ? def : n;
133}