@astro-minimax/ai 0.7.4 → 0.8.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/components/AIChatContainer.d.ts +9 -0
- package/dist/components/AIChatContainer.d.ts.map +1 -0
- package/dist/components/AIChatContainer.js +936 -0
- package/{src → dist}/components/AIChatWidget.astro +1 -1
- package/dist/components/ChatPanel.d.ts +19 -0
- package/dist/components/ChatPanel.d.ts.map +1 -0
- package/dist/components/ChatPanel.js +914 -0
- package/dist/data/index.js +18 -1
- package/dist/fact-registry/index.js +16 -3
- package/dist/index.js +11 -30
- package/dist/intelligence/evidence-analysis.d.ts.map +1 -1
- package/dist/intelligence/index.js +56 -5
- package/dist/intelligence/keyword-extract.d.ts.map +1 -1
- package/dist/middleware/index.js +10 -1
- package/dist/prompt/index.js +10 -4
- package/dist/provider-manager/base.d.ts +1 -0
- package/dist/provider-manager/base.d.ts.map +1 -1
- package/dist/provider-manager/types.d.ts +1 -0
- package/dist/provider-manager/types.d.ts.map +1 -1
- package/dist/providers/index.js +5 -1
- package/dist/search/index.js +48 -6
- package/dist/server/dev-server.js +236 -259
- package/dist/server/index.js +39 -6
- package/dist/stream/index.js +8 -2
- package/package.json +16 -10
- package/dist/cache/global-cache.js +0 -141
- package/dist/cache/index.js +0 -62
- package/dist/cache/kv-adapter.js +0 -102
- package/dist/cache/memory-adapter.js +0 -95
- package/dist/cache/response-cache.js +0 -85
- package/dist/cache/types.js +0 -16
- package/dist/data/metadata-loader.js +0 -66
- package/dist/data/types.js +0 -1
- package/dist/fact-registry/fact-matcher.js +0 -94
- package/dist/fact-registry/prompt-injector.js +0 -57
- package/dist/fact-registry/registry.js +0 -38
- package/dist/fact-registry/types.js +0 -5
- package/dist/intelligence/citation-appender.js +0 -65
- package/dist/intelligence/citation-guard.js +0 -125
- package/dist/intelligence/evidence-analysis.js +0 -88
- package/dist/intelligence/intent-detect.js +0 -131
- package/dist/intelligence/keyword-extract.js +0 -114
- package/dist/intelligence/response-templates.js +0 -116
- package/dist/intelligence/types.js +0 -1
- package/dist/middleware/rate-limiter.js +0 -129
- package/dist/prompt/dynamic-layer.js +0 -67
- package/dist/prompt/prompt-builder.js +0 -12
- package/dist/prompt/semi-static-layer.js +0 -29
- package/dist/prompt/static-layer.js +0 -150
- package/dist/prompt/types.js +0 -1
- package/dist/provider-manager/base.js +0 -47
- package/dist/provider-manager/config.js +0 -134
- package/dist/provider-manager/index.js +0 -6
- package/dist/provider-manager/manager.js +0 -121
- package/dist/provider-manager/mock.js +0 -56
- package/dist/provider-manager/openai.js +0 -112
- package/dist/provider-manager/types.js +0 -6
- package/dist/provider-manager/workers.js +0 -74
- package/dist/providers/mock.js +0 -234
- package/dist/search/idf.js +0 -31
- package/dist/search/search-api.js +0 -119
- package/dist/search/search-index.js +0 -35
- package/dist/search/search-utils.js +0 -122
- package/dist/search/session-cache.js +0 -92
- package/dist/search/types.js +0 -1
- package/dist/search/vector-reranker.js +0 -135
- package/dist/server/chat-handler.js +0 -590
- package/dist/server/errors.js +0 -41
- package/dist/server/metadata-init.js +0 -47
- package/dist/server/notify.js +0 -74
- package/dist/server/stream-helpers.js +0 -197
- package/dist/server/types.js +0 -13
- package/dist/stream/mock-stream.js +0 -27
- package/dist/stream/response.js +0 -22
- package/dist/utils/i18n.js +0 -164
- package/src/components/AIChatContainer.tsx +0 -31
- package/src/components/ChatPanel.tsx +0 -866
- package/src/providers/mock.ts +0 -240
- package/src/server/types.ts +0 -89
- package/src/utils/i18n.ts +0 -238
|
@@ -1,294 +1,271 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* pnpm exec astro-ai-dev # Start dev server
|
|
9
|
-
* pnpm exec astro-ai-dev --init # Initialize datas/ directory
|
|
10
|
-
*
|
|
11
|
-
* Environment variables:
|
|
12
|
-
* AI_DEV_PORT - Port to listen on (default: 8787)
|
|
13
|
-
* AI_BASE_URL - AI provider base URL
|
|
14
|
-
* AI_API_KEY - AI provider API key
|
|
15
|
-
* AI_MODEL - AI model name
|
|
16
|
-
*/
|
|
17
|
-
import { createServer } from 'node:http';
|
|
18
|
-
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
19
|
-
import { resolve, dirname } from 'node:path';
|
|
20
|
-
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { resolve, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
21
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
22
7
|
const __dirname = dirname(__filename);
|
|
23
|
-
const DEFAULT_SUMMARIES = { meta: { lastUpdated: new Date().toISOString(), model:
|
|
8
|
+
const DEFAULT_SUMMARIES = { meta: { lastUpdated: (/* @__PURE__ */ new Date()).toISOString(), model: "none", totalProcessed: 0 }, articles: {} };
|
|
24
9
|
const DEFAULT_AUTHOR_CONTEXT = { author: {}, posts: [] };
|
|
25
10
|
const DEFAULT_VOICE_PROFILE = { style: {}, examples: [] };
|
|
26
11
|
function extractPostTitle(url) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
const match = url.match(/\/posts\/([^/]+)/);
|
|
13
|
+
if (match) {
|
|
14
|
+
return decodeURIComponent(match[1].replace(/-/g, " "));
|
|
15
|
+
}
|
|
16
|
+
return "\u535A\u5BA2\u6587\u7AE0";
|
|
32
17
|
}
|
|
33
18
|
function findBlogRoot() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
if (existsSync(resolve(dir, 'src', 'data'))) {
|
|
43
|
-
return { root: dir, datasDir: resolve(dir, 'src', 'data'), hasDatas: true };
|
|
44
|
-
}
|
|
45
|
-
const parent = resolve(dir, '..');
|
|
46
|
-
if (parent === dir)
|
|
47
|
-
break;
|
|
48
|
-
dir = parent;
|
|
19
|
+
let dir = process.cwd();
|
|
20
|
+
for (let i = 0; i < 10; i++) {
|
|
21
|
+
if (existsSync(resolve(dir, "datas"))) {
|
|
22
|
+
return { root: dir, datasDir: resolve(dir, "datas"), hasDatas: true };
|
|
23
|
+
}
|
|
24
|
+
if (existsSync(resolve(dir, "apps", "blog", "datas"))) {
|
|
25
|
+
return { root: resolve(dir, "apps", "blog"), datasDir: resolve(dir, "apps", "blog", "datas"), hasDatas: true };
|
|
49
26
|
}
|
|
50
|
-
|
|
27
|
+
if (existsSync(resolve(dir, "src", "data"))) {
|
|
28
|
+
return { root: dir, datasDir: resolve(dir, "src", "data"), hasDatas: true };
|
|
29
|
+
}
|
|
30
|
+
const parent = resolve(dir, "..");
|
|
31
|
+
if (parent === dir) break;
|
|
32
|
+
dir = parent;
|
|
33
|
+
}
|
|
34
|
+
return { root: process.cwd(), datasDir: resolve(process.cwd(), "datas"), hasDatas: false };
|
|
51
35
|
}
|
|
52
36
|
function loadEnv(envPath) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
let value = trimmed.slice(eqIndex + 1).trim();
|
|
65
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
66
|
-
value = value.slice(1, -1);
|
|
67
|
-
}
|
|
68
|
-
if (!process.env[key]) {
|
|
69
|
-
process.env[key] = value;
|
|
70
|
-
}
|
|
37
|
+
if (!existsSync(envPath)) return;
|
|
38
|
+
const content = readFileSync(envPath, "utf-8");
|
|
39
|
+
for (const line of content.split("\n")) {
|
|
40
|
+
const trimmed = line.trim();
|
|
41
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
42
|
+
const eqIndex = trimmed.indexOf("=");
|
|
43
|
+
if (eqIndex === -1) continue;
|
|
44
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
45
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
46
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
47
|
+
value = value.slice(1, -1);
|
|
71
48
|
}
|
|
49
|
+
if (!process.env[key]) {
|
|
50
|
+
process.env[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
72
53
|
}
|
|
73
54
|
function initDatasDirectory(datasDir) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
console.log(` Skipped ${file.name} (already exists)`);
|
|
90
|
-
}
|
|
55
|
+
if (!existsSync(datasDir)) {
|
|
56
|
+
mkdirSync(datasDir, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
const files = [
|
|
59
|
+
{ name: "ai-summaries.json", content: DEFAULT_SUMMARIES },
|
|
60
|
+
{ name: "author-context.json", content: DEFAULT_AUTHOR_CONTEXT },
|
|
61
|
+
{ name: "voice-profile.json", content: DEFAULT_VOICE_PROFILE }
|
|
62
|
+
];
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
const filePath = resolve(datasDir, file.name);
|
|
65
|
+
if (!existsSync(filePath)) {
|
|
66
|
+
writeFileSync(filePath, JSON.stringify(file.content, null, 2) + "\n");
|
|
67
|
+
console.log(` Created ${file.name}`);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(` Skipped ${file.name} (already exists)`);
|
|
91
70
|
}
|
|
71
|
+
}
|
|
92
72
|
}
|
|
93
73
|
function loadJson(datasDir, file, defaultValue) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return defaultValue;
|
|
102
|
-
}
|
|
74
|
+
const path = resolve(datasDir, file);
|
|
75
|
+
if (existsSync(path)) {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.warn(` Warning: Failed to parse ${file}, using defaults`);
|
|
80
|
+
return defaultValue;
|
|
103
81
|
}
|
|
104
|
-
|
|
82
|
+
}
|
|
83
|
+
return defaultValue;
|
|
105
84
|
}
|
|
106
85
|
async function setupHandler(datasDir, hasDatas) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (!hasSummaries || !hasAuthor) {
|
|
117
|
-
console.log('\n ⚠️ Some metadata files are missing. AI chat may have limited context.\n');
|
|
118
|
-
}
|
|
86
|
+
const { handleChatRequest, initializeMetadata } = await import("./index.js");
|
|
87
|
+
if (!hasDatas) {
|
|
88
|
+
console.log("\n \u26A0\uFE0F No datas/ directory found. AI chat will work with empty context.");
|
|
89
|
+
console.log(' Run "pnpm exec astro-ai-dev --init" to create placeholder files.\n');
|
|
90
|
+
} else {
|
|
91
|
+
const hasSummaries = existsSync(resolve(datasDir, "ai-summaries.json"));
|
|
92
|
+
const hasAuthor = existsSync(resolve(datasDir, "author-context.json"));
|
|
93
|
+
if (!hasSummaries || !hasAuthor) {
|
|
94
|
+
console.log("\n \u26A0\uFE0F Some metadata files are missing. AI chat may have limited context.\n");
|
|
119
95
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
96
|
+
}
|
|
97
|
+
const summaries = loadJson(datasDir, "ai-summaries.json", DEFAULT_SUMMARIES);
|
|
98
|
+
const authorContext = loadJson(datasDir, "author-context.json", DEFAULT_AUTHOR_CONTEXT);
|
|
99
|
+
const voiceProfile = loadJson(datasDir, "voice-profile.json", DEFAULT_VOICE_PROFILE);
|
|
100
|
+
const env = { ...process.env };
|
|
101
|
+
initializeMetadata({
|
|
102
|
+
summaries,
|
|
103
|
+
authorContext,
|
|
104
|
+
voiceProfile,
|
|
105
|
+
siteUrl: process.env.SITE_URL || "http://localhost:4321"
|
|
106
|
+
}, env);
|
|
107
|
+
return { handleChatRequest, env };
|
|
131
108
|
}
|
|
132
109
|
function toWebRequest(req) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
});
|
|
145
|
-
req.on('error', reject);
|
|
110
|
+
return new Promise((resolve2, reject) => {
|
|
111
|
+
const chunks = [];
|
|
112
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
113
|
+
req.on("end", () => {
|
|
114
|
+
const body = Buffer.concat(chunks);
|
|
115
|
+
const url = `http://localhost${req.url || "/"}`;
|
|
116
|
+
resolve2(new Request(url, {
|
|
117
|
+
method: req.method || "GET",
|
|
118
|
+
headers: req.headers,
|
|
119
|
+
body: req.method !== "GET" && req.method !== "HEAD" ? body : void 0
|
|
120
|
+
}));
|
|
146
121
|
});
|
|
122
|
+
req.on("error", reject);
|
|
123
|
+
});
|
|
147
124
|
}
|
|
148
125
|
async function sendWebResponse(webRes, res) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
res.write(value);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
finally {
|
|
164
|
-
res.end();
|
|
126
|
+
res.writeHead(webRes.status, Object.fromEntries(webRes.headers));
|
|
127
|
+
if (!webRes.body) {
|
|
128
|
+
res.end();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const reader = webRes.body.getReader();
|
|
132
|
+
try {
|
|
133
|
+
while (true) {
|
|
134
|
+
const { done, value } = await reader.read();
|
|
135
|
+
if (done) break;
|
|
136
|
+
res.write(value);
|
|
165
137
|
}
|
|
138
|
+
} finally {
|
|
139
|
+
res.end();
|
|
140
|
+
}
|
|
166
141
|
}
|
|
167
142
|
async function main() {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
143
|
+
const args = process.argv.slice(2);
|
|
144
|
+
if (args.includes("--init") || args.includes("-i")) {
|
|
145
|
+
const { datasDir: datasDir2 } = findBlogRoot();
|
|
146
|
+
console.log("\n\u{1F4E6} Initializing datas/ directory...\n");
|
|
147
|
+
console.log(` Location: ${datasDir2}
|
|
148
|
+
`);
|
|
149
|
+
initDatasDirectory(datasDir2);
|
|
150
|
+
console.log("\n\u2705 Done! You can now configure your AI metadata files.\n");
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
const port = parseInt(process.env.AI_DEV_PORT || "8787", 10);
|
|
154
|
+
const { root: blogRoot, datasDir, hasDatas } = findBlogRoot();
|
|
155
|
+
loadEnv(resolve(blogRoot, ".env"));
|
|
156
|
+
console.log("\n\u{1F916} AI Dev Server starting...\n");
|
|
157
|
+
console.log(` Working directory: ${blogRoot}`);
|
|
158
|
+
console.log(` Datas directory: ${datasDir} ${hasDatas ? "\u2713" : "(not found)"}`);
|
|
159
|
+
console.log(` Port: ${port}`);
|
|
160
|
+
console.log(` AI_BASE_URL: ${process.env.AI_BASE_URL ? "\u2713 configured" : "\u2717 not set"}`);
|
|
161
|
+
console.log(` AI_API_KEY: ${process.env.AI_API_KEY ? "\u2713 configured" : "\u2717 not set"}`);
|
|
162
|
+
console.log(` AI_MODEL: ${process.env.AI_MODEL || "(default)"}`);
|
|
163
|
+
const { handleChatRequest, env } = await setupHandler(datasDir, hasDatas);
|
|
164
|
+
const server = createServer(async (req, res) => {
|
|
165
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
166
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
167
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, x-session-id");
|
|
168
|
+
if (req.method === "OPTIONS") {
|
|
169
|
+
res.writeHead(204);
|
|
170
|
+
res.end();
|
|
171
|
+
return;
|
|
176
172
|
}
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
res.writeHead(204);
|
|
194
|
-
res.end();
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
const url = req.url || '/';
|
|
198
|
-
try {
|
|
199
|
-
if (url.startsWith('/api/chat')) {
|
|
200
|
-
const webReq = await toWebRequest(req);
|
|
201
|
-
const webRes = await handleChatRequest({ env: env, request: webReq });
|
|
202
|
-
await sendWebResponse(webRes, res);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (url.startsWith('/api/notify/comment')) {
|
|
206
|
-
const { createNotifier } = await import('@astro-minimax/notify');
|
|
207
|
-
const webReq = await toWebRequest(req);
|
|
208
|
-
const body = await webReq.json();
|
|
209
|
-
if (body.type !== 'new_comment' && body.type !== 'new_reply') {
|
|
210
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
211
|
-
res.end(JSON.stringify({ error: 'Unsupported event type' }));
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
const data = body.data || {};
|
|
215
|
-
const siteUrl = process.env.SITE_URL || 'http://localhost:4321';
|
|
216
|
-
const postUrl = data.url?.startsWith('http') ? data.url : `${siteUrl}${data.url || '/'}`;
|
|
217
|
-
const notifier = createNotifier({
|
|
218
|
-
telegram: process.env.NOTIFY_TELEGRAM_BOT_TOKEN && process.env.NOTIFY_TELEGRAM_CHAT_ID ? {
|
|
219
|
-
botToken: process.env.NOTIFY_TELEGRAM_BOT_TOKEN,
|
|
220
|
-
chatId: process.env.NOTIFY_TELEGRAM_CHAT_ID,
|
|
221
|
-
} : undefined,
|
|
222
|
-
webhook: process.env.NOTIFY_WEBHOOK_URL ? {
|
|
223
|
-
url: process.env.NOTIFY_WEBHOOK_URL,
|
|
224
|
-
} : undefined,
|
|
225
|
-
email: process.env.NOTIFY_RESEND_API_KEY && process.env.NOTIFY_RESEND_FROM && process.env.NOTIFY_RESEND_TO ? {
|
|
226
|
-
provider: 'resend',
|
|
227
|
-
apiKey: process.env.NOTIFY_RESEND_API_KEY,
|
|
228
|
-
from: process.env.NOTIFY_RESEND_FROM,
|
|
229
|
-
to: process.env.NOTIFY_RESEND_TO,
|
|
230
|
-
} : undefined,
|
|
231
|
-
});
|
|
232
|
-
const result = await notifier.comment({
|
|
233
|
-
author: data.nick || '匿名用户',
|
|
234
|
-
content: data.comment || '',
|
|
235
|
-
postTitle: extractPostTitle(data.url || '/'),
|
|
236
|
-
postUrl,
|
|
237
|
-
});
|
|
238
|
-
console.log('[notify] Comment notification sent:', result.success ? '✓' : '✗', result.results);
|
|
239
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
240
|
-
res.end(JSON.stringify({
|
|
241
|
-
success: result.success,
|
|
242
|
-
event: 'comment',
|
|
243
|
-
channels: result.results.map(r => ({ channel: r.channel, success: r.success, error: r.error })),
|
|
244
|
-
}));
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (url.startsWith('/api/ai-info')) {
|
|
248
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
249
|
-
res.end(JSON.stringify({
|
|
250
|
-
status: 'ok',
|
|
251
|
-
mode: 'dev-server',
|
|
252
|
-
datas: { found: hasDatas, path: datasDir },
|
|
253
|
-
ai: {
|
|
254
|
-
configured: !!(process.env.AI_BASE_URL && process.env.AI_API_KEY),
|
|
255
|
-
model: process.env.AI_MODEL || 'unknown',
|
|
256
|
-
},
|
|
257
|
-
}, null, 2));
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
261
|
-
res.end(JSON.stringify({ error: 'Not found' }));
|
|
262
|
-
}
|
|
263
|
-
catch (err) {
|
|
264
|
-
console.error('[AI Dev Server] Error:', err);
|
|
265
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
266
|
-
res.end(JSON.stringify({ error: 'Internal server error', detail: err instanceof Error ? err.message : String(err) }));
|
|
173
|
+
const url = req.url || "/";
|
|
174
|
+
try {
|
|
175
|
+
if (url.startsWith("/api/chat")) {
|
|
176
|
+
const webReq = await toWebRequest(req);
|
|
177
|
+
const webRes = await handleChatRequest({ env, request: webReq });
|
|
178
|
+
await sendWebResponse(webRes, res);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (url.startsWith("/api/notify/comment")) {
|
|
182
|
+
const { createNotifier } = await import("@astro-minimax/notify");
|
|
183
|
+
const webReq = await toWebRequest(req);
|
|
184
|
+
const body = await webReq.json();
|
|
185
|
+
if (body.type !== "new_comment" && body.type !== "new_reply") {
|
|
186
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
187
|
+
res.end(JSON.stringify({ error: "Unsupported event type" }));
|
|
188
|
+
return;
|
|
267
189
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
190
|
+
const data = body.data || {};
|
|
191
|
+
const siteUrl = process.env.SITE_URL || "http://localhost:4321";
|
|
192
|
+
const postUrl = data.url?.startsWith("http") ? data.url : `${siteUrl}${data.url || "/"}`;
|
|
193
|
+
const notifier = createNotifier({
|
|
194
|
+
telegram: process.env.NOTIFY_TELEGRAM_BOT_TOKEN && process.env.NOTIFY_TELEGRAM_CHAT_ID ? {
|
|
195
|
+
botToken: process.env.NOTIFY_TELEGRAM_BOT_TOKEN,
|
|
196
|
+
chatId: process.env.NOTIFY_TELEGRAM_CHAT_ID
|
|
197
|
+
} : void 0,
|
|
198
|
+
webhook: process.env.NOTIFY_WEBHOOK_URL ? {
|
|
199
|
+
url: process.env.NOTIFY_WEBHOOK_URL
|
|
200
|
+
} : void 0,
|
|
201
|
+
email: process.env.NOTIFY_RESEND_API_KEY && process.env.NOTIFY_RESEND_FROM && process.env.NOTIFY_RESEND_TO ? {
|
|
202
|
+
provider: "resend",
|
|
203
|
+
apiKey: process.env.NOTIFY_RESEND_API_KEY,
|
|
204
|
+
from: process.env.NOTIFY_RESEND_FROM,
|
|
205
|
+
to: process.env.NOTIFY_RESEND_TO
|
|
206
|
+
} : void 0
|
|
207
|
+
});
|
|
208
|
+
const result = await notifier.comment({
|
|
209
|
+
author: data.nick || "\u533F\u540D\u7528\u6237",
|
|
210
|
+
content: data.comment || "",
|
|
211
|
+
postTitle: extractPostTitle(data.url || "/"),
|
|
212
|
+
postUrl
|
|
281
213
|
});
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
214
|
+
console.log("[notify] Comment notification sent:", result.success ? "\u2713" : "\u2717", result.results);
|
|
215
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
216
|
+
res.end(JSON.stringify({
|
|
217
|
+
success: result.success,
|
|
218
|
+
event: "comment",
|
|
219
|
+
channels: result.results.map((r) => ({ channel: r.channel, success: r.success, error: r.error }))
|
|
220
|
+
}));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (url.startsWith("/api/ai-info")) {
|
|
224
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
225
|
+
res.end(JSON.stringify({
|
|
226
|
+
status: "ok",
|
|
227
|
+
mode: "dev-server",
|
|
228
|
+
datas: { found: hasDatas, path: datasDir },
|
|
229
|
+
ai: {
|
|
230
|
+
configured: !!(process.env.AI_BASE_URL && process.env.AI_API_KEY),
|
|
231
|
+
model: process.env.AI_MODEL || "unknown"
|
|
232
|
+
}
|
|
233
|
+
}, null, 2));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
237
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error("[AI Dev Server] Error:", err);
|
|
240
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
241
|
+
res.end(JSON.stringify({ error: "Internal server error", detail: err instanceof Error ? err.message : String(err) }));
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
server.listen(port, () => {
|
|
245
|
+
console.log(`
|
|
246
|
+
\u2705 AI Dev Server running at http://localhost:${port}`);
|
|
247
|
+
console.log(` POST http://localhost:${port}/api/chat`);
|
|
248
|
+
console.log(` GET http://localhost:${port}/api/ai-info`);
|
|
249
|
+
console.log(`
|
|
250
|
+
Tip: Run "pnpm exec astro-ai-dev --init" to create datas/ directory.
|
|
251
|
+
`);
|
|
252
|
+
});
|
|
253
|
+
const shutdown = (signal) => {
|
|
254
|
+
console.log(`
|
|
255
|
+
\u{1F6D1} AI Dev Server received ${signal}, shutting down...`);
|
|
256
|
+
server.close(() => {
|
|
257
|
+
console.log("\u2705 AI Dev Server closed");
|
|
258
|
+
process.exit(0);
|
|
259
|
+
});
|
|
260
|
+
setTimeout(() => {
|
|
261
|
+
console.log("\u26A0\uFE0F Force closing after timeout");
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}, 3e3);
|
|
264
|
+
};
|
|
265
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
266
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
290
267
|
}
|
|
291
268
|
main().catch((err) => {
|
|
292
|
-
|
|
293
|
-
|
|
269
|
+
console.error("\n\u274C Failed to start AI Dev Server:", err);
|
|
270
|
+
process.exit(1);
|
|
294
271
|
});
|
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { handleChatRequest } from "./chat-handler.js";
|
|
2
|
+
import { initializeMetadata, resetMetadataInit } from "./metadata-init.js";
|
|
3
|
+
import { errors, corsPreflightResponse, chatError } from "./errors.js";
|
|
4
|
+
import { notifyAiChat } from "./notify.js";
|
|
5
|
+
import {
|
|
6
|
+
writeSearchStatus,
|
|
7
|
+
writeGeneratingStatus,
|
|
8
|
+
writeDoneStatus,
|
|
9
|
+
writeSourceArticles,
|
|
10
|
+
writeTextChunk,
|
|
11
|
+
writeFinish,
|
|
12
|
+
streamLLMResponse,
|
|
13
|
+
streamMockFallback,
|
|
14
|
+
streamCachedResponse
|
|
15
|
+
} from "./stream-helpers.js";
|
|
16
|
+
import {
|
|
17
|
+
createChatStatusData,
|
|
18
|
+
isChatStatusData
|
|
19
|
+
} from "./types.js";
|
|
20
|
+
export {
|
|
21
|
+
chatError,
|
|
22
|
+
corsPreflightResponse,
|
|
23
|
+
createChatStatusData,
|
|
24
|
+
errors,
|
|
25
|
+
handleChatRequest,
|
|
26
|
+
initializeMetadata,
|
|
27
|
+
isChatStatusData,
|
|
28
|
+
notifyAiChat,
|
|
29
|
+
resetMetadataInit,
|
|
30
|
+
streamCachedResponse,
|
|
31
|
+
streamLLMResponse,
|
|
32
|
+
streamMockFallback,
|
|
33
|
+
writeDoneStatus,
|
|
34
|
+
writeFinish,
|
|
35
|
+
writeGeneratingStatus,
|
|
36
|
+
writeSearchStatus,
|
|
37
|
+
writeSourceArticles,
|
|
38
|
+
writeTextChunk
|
|
39
|
+
};
|
package/dist/stream/index.js
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { streamMockResponse } from "./mock-stream.js";
|
|
2
|
+
import { STREAM_HEADERS, JSON_HEADERS, errorResponse } from "./response.js";
|
|
3
|
+
export {
|
|
4
|
+
JSON_HEADERS,
|
|
5
|
+
STREAM_HEADERS,
|
|
6
|
+
errorResponse,
|
|
7
|
+
streamMockResponse
|
|
8
|
+
};
|