@astro-minimax/ai 0.2.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/README.md +223 -0
- package/dist/cache/global-cache.d.ts +31 -0
- package/dist/cache/global-cache.d.ts.map +1 -0
- package/dist/cache/global-cache.js +141 -0
- package/dist/cache/index.d.ts +8 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +62 -0
- package/dist/cache/kv-adapter.d.ts +21 -0
- package/dist/cache/kv-adapter.d.ts.map +1 -0
- package/dist/cache/kv-adapter.js +102 -0
- package/dist/cache/memory-adapter.d.ts +24 -0
- package/dist/cache/memory-adapter.d.ts.map +1 -0
- package/dist/cache/memory-adapter.js +95 -0
- package/dist/cache/response-cache.d.ts +45 -0
- package/dist/cache/response-cache.d.ts.map +1 -0
- package/dist/cache/response-cache.js +85 -0
- package/dist/cache/types.d.ts +118 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +16 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +1 -0
- package/dist/data/metadata-loader.d.ts +37 -0
- package/dist/data/metadata-loader.d.ts.map +1 -0
- package/dist/data/metadata-loader.js +54 -0
- package/dist/data/types.d.ts +51 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/data/types.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/intelligence/citation-guard.d.ts +24 -0
- package/dist/intelligence/citation-guard.d.ts.map +1 -0
- package/dist/intelligence/citation-guard.js +82 -0
- package/dist/intelligence/evidence-analysis.d.ts +29 -0
- package/dist/intelligence/evidence-analysis.d.ts.map +1 -0
- package/dist/intelligence/evidence-analysis.js +88 -0
- package/dist/intelligence/index.d.ts +6 -0
- package/dist/intelligence/index.d.ts.map +1 -0
- package/dist/intelligence/index.js +4 -0
- package/dist/intelligence/intent-detect.d.ts +29 -0
- package/dist/intelligence/intent-detect.d.ts.map +1 -0
- package/dist/intelligence/intent-detect.js +64 -0
- package/dist/intelligence/keyword-extract.d.ts +31 -0
- package/dist/intelligence/keyword-extract.d.ts.map +1 -0
- package/dist/intelligence/keyword-extract.js +114 -0
- package/dist/intelligence/types.d.ts +27 -0
- package/dist/intelligence/types.d.ts.map +1 -0
- package/dist/intelligence/types.js +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/rate-limiter.d.ts +26 -0
- package/dist/middleware/rate-limiter.d.ts.map +1 -0
- package/dist/middleware/rate-limiter.js +129 -0
- package/dist/prompt/dynamic-layer.d.ts +7 -0
- package/dist/prompt/dynamic-layer.d.ts.map +1 -0
- package/dist/prompt/dynamic-layer.js +40 -0
- package/dist/prompt/index.d.ts +6 -0
- package/dist/prompt/index.d.ts.map +1 -0
- package/dist/prompt/index.js +4 -0
- package/dist/prompt/prompt-builder.d.ts +11 -0
- package/dist/prompt/prompt-builder.d.ts.map +1 -0
- package/dist/prompt/prompt-builder.js +19 -0
- package/dist/prompt/semi-static-layer.d.ts +7 -0
- package/dist/prompt/semi-static-layer.d.ts.map +1 -0
- package/dist/prompt/semi-static-layer.js +32 -0
- package/dist/prompt/static-layer.d.ts +3 -0
- package/dist/prompt/static-layer.d.ts.map +1 -0
- package/dist/prompt/static-layer.js +78 -0
- package/dist/prompt/types.d.ts +25 -0
- package/dist/prompt/types.d.ts.map +1 -0
- package/dist/prompt/types.js +1 -0
- package/dist/provider-manager/base.d.ts +26 -0
- package/dist/provider-manager/base.d.ts.map +1 -0
- package/dist/provider-manager/base.js +47 -0
- package/dist/provider-manager/config.d.ts +7 -0
- package/dist/provider-manager/config.d.ts.map +1 -0
- package/dist/provider-manager/config.js +134 -0
- package/dist/provider-manager/index.d.ts +8 -0
- package/dist/provider-manager/index.d.ts.map +1 -0
- package/dist/provider-manager/index.js +6 -0
- package/dist/provider-manager/manager.d.ts +18 -0
- package/dist/provider-manager/manager.d.ts.map +1 -0
- package/dist/provider-manager/manager.js +121 -0
- package/dist/provider-manager/mock.d.ts +18 -0
- package/dist/provider-manager/mock.d.ts.map +1 -0
- package/dist/provider-manager/mock.js +56 -0
- package/dist/provider-manager/openai.d.ts +20 -0
- package/dist/provider-manager/openai.d.ts.map +1 -0
- package/dist/provider-manager/openai.js +83 -0
- package/dist/provider-manager/types.d.ts +217 -0
- package/dist/provider-manager/types.d.ts.map +1 -0
- package/dist/provider-manager/types.js +6 -0
- package/dist/provider-manager/workers.d.ts +20 -0
- package/dist/provider-manager/workers.d.ts.map +1 -0
- package/dist/provider-manager/workers.js +74 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/mock.d.ts +14 -0
- package/dist/providers/mock.d.ts.map +1 -0
- package/dist/providers/mock.js +234 -0
- package/dist/search/index.d.ts +5 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +3 -0
- package/dist/search/search-api.d.ts +28 -0
- package/dist/search/search-api.d.ts.map +1 -0
- package/dist/search/search-api.js +110 -0
- package/dist/search/search-index.d.ts +6 -0
- package/dist/search/search-index.d.ts.map +1 -0
- package/dist/search/search-index.js +22 -0
- package/dist/search/search-utils.d.ts +43 -0
- package/dist/search/search-utils.d.ts.map +1 -0
- package/dist/search/search-utils.js +114 -0
- package/dist/search/session-cache.d.ts +19 -0
- package/dist/search/session-cache.d.ts.map +1 -0
- package/dist/search/session-cache.js +92 -0
- package/dist/search/types.d.ts +41 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/search/types.js +1 -0
- package/dist/server/chat-handler.d.ts +3 -0
- package/dist/server/chat-handler.d.ts.map +1 -0
- package/dist/server/chat-handler.js +750 -0
- package/dist/server/dev-server.d.ts +18 -0
- package/dist/server/dev-server.d.ts.map +1 -0
- package/dist/server/dev-server.js +294 -0
- package/dist/server/errors.d.ts +17 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +41 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +5 -0
- package/dist/server/metadata-init.d.ts +11 -0
- package/dist/server/metadata-init.d.ts.map +1 -0
- package/dist/server/metadata-init.js +45 -0
- package/dist/server/notify.d.ts +25 -0
- package/dist/server/notify.d.ts.map +1 -0
- package/dist/server/notify.js +62 -0
- package/dist/server/types.d.ts +56 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +13 -0
- package/dist/stream/index.d.ts +3 -0
- package/dist/stream/index.d.ts.map +1 -0
- package/dist/stream/index.js +2 -0
- package/dist/stream/mock-stream.d.ts +12 -0
- package/dist/stream/mock-stream.d.ts.map +1 -0
- package/dist/stream/mock-stream.js +27 -0
- package/dist/stream/response.d.ts +10 -0
- package/dist/stream/response.d.ts.map +1 -0
- package/dist/stream/response.js +22 -0
- package/dist/utils/i18n.d.ts +18 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +148 -0
- package/package.json +93 -0
- package/src/components/AIChatContainer.tsx +30 -0
- package/src/components/AIChatWidget.astro +30 -0
- package/src/components/ChatPanel.tsx +865 -0
- package/src/styles/source.css +2 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone local AI dev server using Node.js built-in HTTP.
|
|
4
|
+
* Runs alongside `astro dev` to provide /api/chat and /api/ai-info endpoints
|
|
5
|
+
* when wrangler pages dev is unavailable.
|
|
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
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=dev-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../src/server/dev-server.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone local AI dev server using Node.js built-in HTTP.
|
|
4
|
+
* Runs alongside `astro dev` to provide /api/chat and /api/ai-info endpoints
|
|
5
|
+
* when wrangler pages dev is unavailable.
|
|
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';
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
const DEFAULT_SUMMARIES = { meta: { lastUpdated: new Date().toISOString(), model: 'none', totalProcessed: 0 }, articles: {} };
|
|
24
|
+
const DEFAULT_AUTHOR_CONTEXT = { author: {}, posts: [] };
|
|
25
|
+
const DEFAULT_VOICE_PROFILE = { style: {}, examples: [] };
|
|
26
|
+
function extractPostTitle(url) {
|
|
27
|
+
const match = url.match(/\/posts\/([^/]+)/);
|
|
28
|
+
if (match) {
|
|
29
|
+
return decodeURIComponent(match[1].replace(/-/g, ' '));
|
|
30
|
+
}
|
|
31
|
+
return '博客文章';
|
|
32
|
+
}
|
|
33
|
+
function findBlogRoot() {
|
|
34
|
+
let dir = process.cwd();
|
|
35
|
+
for (let i = 0; i < 10; i++) {
|
|
36
|
+
if (existsSync(resolve(dir, 'datas'))) {
|
|
37
|
+
return { root: dir, datasDir: resolve(dir, 'datas'), hasDatas: true };
|
|
38
|
+
}
|
|
39
|
+
if (existsSync(resolve(dir, 'apps', 'blog', 'datas'))) {
|
|
40
|
+
return { root: resolve(dir, 'apps', 'blog'), datasDir: resolve(dir, 'apps', 'blog', 'datas'), hasDatas: true };
|
|
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;
|
|
49
|
+
}
|
|
50
|
+
return { root: process.cwd(), datasDir: resolve(process.cwd(), 'datas'), hasDatas: false };
|
|
51
|
+
}
|
|
52
|
+
function loadEnv(envPath) {
|
|
53
|
+
if (!existsSync(envPath))
|
|
54
|
+
return;
|
|
55
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
56
|
+
for (const line of content.split('\n')) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
59
|
+
continue;
|
|
60
|
+
const eqIndex = trimmed.indexOf('=');
|
|
61
|
+
if (eqIndex === -1)
|
|
62
|
+
continue;
|
|
63
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
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
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function initDatasDirectory(datasDir) {
|
|
74
|
+
if (!existsSync(datasDir)) {
|
|
75
|
+
mkdirSync(datasDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
const files = [
|
|
78
|
+
{ name: 'ai-summaries.json', content: DEFAULT_SUMMARIES },
|
|
79
|
+
{ name: 'author-context.json', content: DEFAULT_AUTHOR_CONTEXT },
|
|
80
|
+
{ name: 'voice-profile.json', content: DEFAULT_VOICE_PROFILE },
|
|
81
|
+
];
|
|
82
|
+
for (const file of files) {
|
|
83
|
+
const filePath = resolve(datasDir, file.name);
|
|
84
|
+
if (!existsSync(filePath)) {
|
|
85
|
+
writeFileSync(filePath, JSON.stringify(file.content, null, 2) + '\n');
|
|
86
|
+
console.log(` Created ${file.name}`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(` Skipped ${file.name} (already exists)`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function loadJson(datasDir, file, defaultValue) {
|
|
94
|
+
const path = resolve(datasDir, file);
|
|
95
|
+
if (existsSync(path)) {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
console.warn(` Warning: Failed to parse ${file}, using defaults`);
|
|
101
|
+
return defaultValue;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return defaultValue;
|
|
105
|
+
}
|
|
106
|
+
async function setupHandler(datasDir, hasDatas) {
|
|
107
|
+
const { handleChatRequest, initializeMetadata } = await import('./index.js');
|
|
108
|
+
// Show warnings for missing metadata
|
|
109
|
+
if (!hasDatas) {
|
|
110
|
+
console.log('\n ⚠️ No datas/ directory found. AI chat will work with empty context.');
|
|
111
|
+
console.log(' Run "pnpm exec astro-ai-dev --init" to create placeholder files.\n');
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const hasSummaries = existsSync(resolve(datasDir, 'ai-summaries.json'));
|
|
115
|
+
const hasAuthor = existsSync(resolve(datasDir, 'author-context.json'));
|
|
116
|
+
if (!hasSummaries || !hasAuthor) {
|
|
117
|
+
console.log('\n ⚠️ Some metadata files are missing. AI chat may have limited context.\n');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const summaries = loadJson(datasDir, 'ai-summaries.json', DEFAULT_SUMMARIES);
|
|
121
|
+
const authorContext = loadJson(datasDir, 'author-context.json', DEFAULT_AUTHOR_CONTEXT);
|
|
122
|
+
const voiceProfile = loadJson(datasDir, 'voice-profile.json', DEFAULT_VOICE_PROFILE);
|
|
123
|
+
const env = { ...process.env };
|
|
124
|
+
initializeMetadata({
|
|
125
|
+
summaries: summaries,
|
|
126
|
+
authorContext: authorContext,
|
|
127
|
+
voiceProfile: voiceProfile,
|
|
128
|
+
siteUrl: process.env.SITE_URL || 'http://localhost:4321',
|
|
129
|
+
}, env);
|
|
130
|
+
return { handleChatRequest, env };
|
|
131
|
+
}
|
|
132
|
+
function toWebRequest(req) {
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const chunks = [];
|
|
135
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
136
|
+
req.on('end', () => {
|
|
137
|
+
const body = Buffer.concat(chunks);
|
|
138
|
+
const url = `http://localhost${req.url || '/'}`;
|
|
139
|
+
resolve(new Request(url, {
|
|
140
|
+
method: req.method || 'GET',
|
|
141
|
+
headers: req.headers,
|
|
142
|
+
body: req.method !== 'GET' && req.method !== 'HEAD' ? body : undefined,
|
|
143
|
+
}));
|
|
144
|
+
});
|
|
145
|
+
req.on('error', reject);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async function sendWebResponse(webRes, res) {
|
|
149
|
+
res.writeHead(webRes.status, Object.fromEntries(webRes.headers));
|
|
150
|
+
if (!webRes.body) {
|
|
151
|
+
res.end();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const reader = webRes.body.getReader();
|
|
155
|
+
try {
|
|
156
|
+
while (true) {
|
|
157
|
+
const { done, value } = await reader.read();
|
|
158
|
+
if (done)
|
|
159
|
+
break;
|
|
160
|
+
res.write(value);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
res.end();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function main() {
|
|
168
|
+
const args = process.argv.slice(2);
|
|
169
|
+
if (args.includes('--init') || args.includes('-i')) {
|
|
170
|
+
const { datasDir } = findBlogRoot();
|
|
171
|
+
console.log('\n📦 Initializing datas/ directory...\n');
|
|
172
|
+
console.log(` Location: ${datasDir}\n`);
|
|
173
|
+
initDatasDirectory(datasDir);
|
|
174
|
+
console.log('\n✅ Done! You can now configure your AI metadata files.\n');
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
const port = parseInt(process.env.AI_DEV_PORT || '8787', 10);
|
|
178
|
+
const { root: blogRoot, datasDir, hasDatas } = findBlogRoot();
|
|
179
|
+
loadEnv(resolve(blogRoot, '.env'));
|
|
180
|
+
console.log('\n🤖 AI Dev Server starting...\n');
|
|
181
|
+
console.log(` Working directory: ${blogRoot}`);
|
|
182
|
+
console.log(` Datas directory: ${datasDir} ${hasDatas ? '✓' : '(not found)'}`);
|
|
183
|
+
console.log(` Port: ${port}`);
|
|
184
|
+
console.log(` AI_BASE_URL: ${process.env.AI_BASE_URL ? '✓ configured' : '✗ not set'}`);
|
|
185
|
+
console.log(` AI_API_KEY: ${process.env.AI_API_KEY ? '✓ configured' : '✗ not set'}`);
|
|
186
|
+
console.log(` AI_MODEL: ${process.env.AI_MODEL || '(default)'}`);
|
|
187
|
+
const { handleChatRequest, env } = await setupHandler(datasDir, hasDatas);
|
|
188
|
+
const server = createServer(async (req, res) => {
|
|
189
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
190
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
191
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, x-session-id');
|
|
192
|
+
if (req.method === 'OPTIONS') {
|
|
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) }));
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
server.listen(port, () => {
|
|
270
|
+
console.log(`\n✅ AI Dev Server running at http://localhost:${port}`);
|
|
271
|
+
console.log(` POST http://localhost:${port}/api/chat`);
|
|
272
|
+
console.log(` GET http://localhost:${port}/api/ai-info`);
|
|
273
|
+
console.log(`\n Tip: Run "pnpm exec astro-ai-dev --init" to create datas/ directory.\n`);
|
|
274
|
+
});
|
|
275
|
+
// Graceful shutdown on SIGINT (Ctrl+C) and SIGTERM
|
|
276
|
+
const shutdown = (signal) => {
|
|
277
|
+
console.log(`\n🛑 AI Dev Server received ${signal}, shutting down...`);
|
|
278
|
+
server.close(() => {
|
|
279
|
+
console.log('✅ AI Dev Server closed');
|
|
280
|
+
process.exit(0);
|
|
281
|
+
});
|
|
282
|
+
// Force exit after 3 seconds if connections are pending
|
|
283
|
+
setTimeout(() => {
|
|
284
|
+
console.log('⚠️ Force closing after timeout');
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}, 3000);
|
|
287
|
+
};
|
|
288
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
289
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
290
|
+
}
|
|
291
|
+
main().catch((err) => {
|
|
292
|
+
console.error('\n❌ Failed to start AI Dev Server:', err);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function chatError(code: string, error: string, status: number, options?: {
|
|
2
|
+
retryable?: boolean;
|
|
3
|
+
retryAfter?: number;
|
|
4
|
+
}): Response;
|
|
5
|
+
export declare const errors: {
|
|
6
|
+
methodNotAllowed: (lang?: string) => Response;
|
|
7
|
+
invalidRequest: (detail?: string, lang?: string) => Response;
|
|
8
|
+
emptyMessage: (lang?: string) => Response;
|
|
9
|
+
emptyContent: (lang?: string) => Response;
|
|
10
|
+
inputTooLong: (max: number, lang?: string) => Response;
|
|
11
|
+
rateLimited: (retryAfter?: number, lang?: string) => Response;
|
|
12
|
+
timeout: (lang?: string) => Response;
|
|
13
|
+
providerUnavailable: (lang?: string) => Response;
|
|
14
|
+
internal: (detail?: string, lang?: string) => Response;
|
|
15
|
+
};
|
|
16
|
+
export declare function corsPreflightResponse(): Response;
|
|
17
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/server/errors.ts"],"names":[],"mappings":"AAQA,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD,QAAQ,CAYV;AAMD,eAAO,MAAM,MAAM;8BACS,MAAM;8BAEN,MAAM,SAAS,MAAM;0BAEzB,MAAM;0BAEN,MAAM;wBAER,MAAM,SAAS,MAAM;+BAEd,MAAM,SAAS,MAAM;qBAE/B,MAAM;iCAEM,MAAM;wBAEf,MAAM,SAAS,MAAM;CAE1C,CAAC;AAEF,wBAAgB,qBAAqB,IAAI,QAAQ,CAQhD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { t } from '../utils/i18n.js';
|
|
2
|
+
const CORS_HEADERS = {
|
|
3
|
+
'Content-Type': 'application/json',
|
|
4
|
+
'Access-Control-Allow-Origin': '*',
|
|
5
|
+
};
|
|
6
|
+
export function chatError(code, error, status, options) {
|
|
7
|
+
const body = {
|
|
8
|
+
error,
|
|
9
|
+
code,
|
|
10
|
+
retryable: options?.retryable ?? false,
|
|
11
|
+
retryAfter: options?.retryAfter,
|
|
12
|
+
};
|
|
13
|
+
const headers = { ...CORS_HEADERS };
|
|
14
|
+
if (options?.retryAfter) {
|
|
15
|
+
headers['Retry-After'] = String(options.retryAfter);
|
|
16
|
+
}
|
|
17
|
+
return new Response(JSON.stringify(body), { status, headers });
|
|
18
|
+
}
|
|
19
|
+
function te(key, lang, vars) {
|
|
20
|
+
return t(key, lang ?? 'zh', vars);
|
|
21
|
+
}
|
|
22
|
+
export const errors = {
|
|
23
|
+
methodNotAllowed: (lang) => chatError('METHOD_NOT_ALLOWED', lang === 'en' ? 'Method not allowed' : '方法不允许', 405),
|
|
24
|
+
invalidRequest: (detail, lang) => chatError('INVALID_REQUEST', detail ?? te('ai.error.format', lang), 400),
|
|
25
|
+
emptyMessage: (lang) => chatError('INVALID_REQUEST', te('ai.error.emptyMessage', lang), 400),
|
|
26
|
+
emptyContent: (lang) => chatError('INVALID_REQUEST', te('ai.error.emptyContent', lang), 400),
|
|
27
|
+
inputTooLong: (max, lang) => chatError('INPUT_TOO_LONG', te('ai.error.inputTooLong', lang, { max }), 400),
|
|
28
|
+
rateLimited: (retryAfter, lang) => chatError('RATE_LIMITED', te('ai.error.rateLimit', lang), 429, { retryable: true, retryAfter: retryAfter ?? 10 }),
|
|
29
|
+
timeout: (lang) => chatError('TIMEOUT', te('ai.error.timeout', lang), 504, { retryable: true }),
|
|
30
|
+
providerUnavailable: (lang) => chatError('PROVIDER_UNAVAILABLE', te('ai.error.unavailable', lang), 503, { retryable: true, retryAfter: 30 }),
|
|
31
|
+
internal: (detail, lang) => chatError('INTERNAL_ERROR', detail ?? te('ai.error.generic', lang), 500, { retryable: true, retryAfter: 5 }),
|
|
32
|
+
};
|
|
33
|
+
export function corsPreflightResponse() {
|
|
34
|
+
return new Response(null, {
|
|
35
|
+
headers: {
|
|
36
|
+
'Access-Control-Allow-Origin': '*',
|
|
37
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
38
|
+
'Access-Control-Allow-Headers': 'Content-Type, x-session-id',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { handleChatRequest } from './chat-handler.js';
|
|
2
|
+
export { initializeMetadata, resetMetadataInit } from './metadata-init.js';
|
|
3
|
+
export { errors, corsPreflightResponse, chatError } from './errors.js';
|
|
4
|
+
export { notifyAiChat } from './notify.js';
|
|
5
|
+
export type { ChatNotifyOptions } from './notify.js';
|
|
6
|
+
export { createChatStatusData, isChatStatusData, } from './types.js';
|
|
7
|
+
export type { ChatContext, ArticleChatContext, ChatRequestBody, ChatHandlerEnv, ChatHandlerOptions, ChatStatusData, ChatStatusStage, ChatErrorResponse, MetadataConfig, } from './types.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EACL,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { handleChatRequest } from './chat-handler.js';
|
|
2
|
+
export { initializeMetadata, resetMetadataInit } from './metadata-init.js';
|
|
3
|
+
export { errors, corsPreflightResponse, chatError } from './errors.js';
|
|
4
|
+
export { notifyAiChat } from './notify.js';
|
|
5
|
+
export { createChatStatusData, isChatStatusData, } from './types.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MetadataConfig, ChatHandlerEnv } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Initializes AI metadata: loads summaries, author context, voice profile,
|
|
4
|
+
* and builds search indices. Safe to call multiple times (idempotent).
|
|
5
|
+
*/
|
|
6
|
+
export declare function initializeMetadata(config: MetadataConfig, env?: ChatHandlerEnv): void;
|
|
7
|
+
/**
|
|
8
|
+
* Resets initialization state (for testing).
|
|
9
|
+
*/
|
|
10
|
+
export declare function resetMetadataInit(): void;
|
|
11
|
+
//# sourceMappingURL=metadata-init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-init.d.ts","sourceRoot":"","sources":["../../src/server/metadata-init.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIjE;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,cAAc,GAAG,IAAI,CAoCrF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { preloadMetadata, getAuthorContext, getAllSummaries, initArticleIndex, initProjectIndex, } from '../index.js';
|
|
2
|
+
let initialized = false;
|
|
3
|
+
/**
|
|
4
|
+
* Initializes AI metadata: loads summaries, author context, voice profile,
|
|
5
|
+
* and builds search indices. Safe to call multiple times (idempotent).
|
|
6
|
+
*/
|
|
7
|
+
export function initializeMetadata(config, env) {
|
|
8
|
+
if (initialized)
|
|
9
|
+
return;
|
|
10
|
+
initialized = true;
|
|
11
|
+
preloadMetadata({
|
|
12
|
+
summaries: config.summaries,
|
|
13
|
+
authorContext: config.authorContext,
|
|
14
|
+
voiceProfile: config.voiceProfile,
|
|
15
|
+
});
|
|
16
|
+
const authorCtx = getAuthorContext();
|
|
17
|
+
const allSummaries = getAllSummaries();
|
|
18
|
+
const summaryMap = new Map(allSummaries.map(s => [s.slug, s]));
|
|
19
|
+
const siteUrl = config.siteUrl ?? env?.SITE_URL ?? '';
|
|
20
|
+
const articleDocs = (authorCtx?.posts ?? []).map((post) => {
|
|
21
|
+
const summary = summaryMap.get(post.id);
|
|
22
|
+
const baseUrl = post.url?.startsWith('http') ? '' : siteUrl;
|
|
23
|
+
return {
|
|
24
|
+
id: post.id,
|
|
25
|
+
title: post.title,
|
|
26
|
+
url: post.url ? `${baseUrl}${post.url}` : `${siteUrl}/${post.id}`,
|
|
27
|
+
excerpt: post.summary || summary?.summary || '',
|
|
28
|
+
content: [...(post.keyPoints ?? []), ...(summary?.keyPoints ?? [])].join(' '),
|
|
29
|
+
categories: [post.category].filter(Boolean),
|
|
30
|
+
tags: post.tags ?? [],
|
|
31
|
+
keyPoints: [...(post.keyPoints ?? []), ...(summary?.keyPoints ?? [])],
|
|
32
|
+
dateTime: post.date ? new Date(post.date).getTime() : 0,
|
|
33
|
+
lang: post.lang,
|
|
34
|
+
summary: summary?.summary,
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
initArticleIndex(articleDocs);
|
|
38
|
+
initProjectIndex([]);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resets initialization state (for testing).
|
|
42
|
+
*/
|
|
43
|
+
export function resetMetadataInit() {
|
|
44
|
+
initialized = false;
|
|
45
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type NotifyResult, type ArticleRef, type ModelInfo, type TokenUsage, type PhaseTiming } from '@astro-minimax/notify';
|
|
2
|
+
import type { UIMessage } from 'ai';
|
|
3
|
+
interface NotifyEnv {
|
|
4
|
+
NOTIFY_TELEGRAM_BOT_TOKEN?: string;
|
|
5
|
+
NOTIFY_TELEGRAM_CHAT_ID?: string;
|
|
6
|
+
NOTIFY_WEBHOOK_URL?: string;
|
|
7
|
+
NOTIFY_RESEND_API_KEY?: string;
|
|
8
|
+
NOTIFY_RESEND_FROM?: string;
|
|
9
|
+
NOTIFY_RESEND_TO?: string;
|
|
10
|
+
SITE_URL?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface ChatNotifyOptions {
|
|
14
|
+
env: NotifyEnv;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
messages: UIMessage[];
|
|
17
|
+
aiResponse?: string;
|
|
18
|
+
referencedArticles?: ArticleRef[];
|
|
19
|
+
model?: ModelInfo;
|
|
20
|
+
usage?: TokenUsage;
|
|
21
|
+
timing?: PhaseTiming;
|
|
22
|
+
}
|
|
23
|
+
export declare function notifyAiChat(options: ChatNotifyOptions): Promise<NotifyResult | null>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/server/notify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC7J,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC,UAAU,SAAS;IACjB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAuCD,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,SAAS,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,UAAU,EAAE,CAAC;IAClC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAgCrF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createNotifier } from '@astro-minimax/notify';
|
|
2
|
+
let notifierInstance = null;
|
|
3
|
+
function getNotifier(env) {
|
|
4
|
+
if (notifierInstance)
|
|
5
|
+
return notifierInstance;
|
|
6
|
+
const hasConfig = env.NOTIFY_TELEGRAM_BOT_TOKEN || env.NOTIFY_WEBHOOK_URL || env.NOTIFY_RESEND_API_KEY;
|
|
7
|
+
if (!hasConfig)
|
|
8
|
+
return null;
|
|
9
|
+
notifierInstance = createNotifier({
|
|
10
|
+
telegram: env.NOTIFY_TELEGRAM_BOT_TOKEN && env.NOTIFY_TELEGRAM_CHAT_ID ? {
|
|
11
|
+
botToken: env.NOTIFY_TELEGRAM_BOT_TOKEN,
|
|
12
|
+
chatId: env.NOTIFY_TELEGRAM_CHAT_ID,
|
|
13
|
+
} : undefined,
|
|
14
|
+
webhook: env.NOTIFY_WEBHOOK_URL ? {
|
|
15
|
+
url: env.NOTIFY_WEBHOOK_URL,
|
|
16
|
+
} : undefined,
|
|
17
|
+
email: env.NOTIFY_RESEND_API_KEY && env.NOTIFY_RESEND_FROM && env.NOTIFY_RESEND_TO ? {
|
|
18
|
+
provider: 'resend',
|
|
19
|
+
apiKey: env.NOTIFY_RESEND_API_KEY,
|
|
20
|
+
from: env.NOTIFY_RESEND_FROM,
|
|
21
|
+
to: env.NOTIFY_RESEND_TO,
|
|
22
|
+
} : undefined,
|
|
23
|
+
});
|
|
24
|
+
return notifierInstance;
|
|
25
|
+
}
|
|
26
|
+
function getMessageText(message) {
|
|
27
|
+
if (Array.isArray(message.parts)) {
|
|
28
|
+
return message.parts
|
|
29
|
+
.filter((p) => p.type === 'text')
|
|
30
|
+
.map(p => p.text)
|
|
31
|
+
.join('');
|
|
32
|
+
}
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
export function notifyAiChat(options) {
|
|
36
|
+
const { env, sessionId, messages, aiResponse, referencedArticles, model, usage, timing } = options;
|
|
37
|
+
const notifier = getNotifier(env);
|
|
38
|
+
if (!notifier) {
|
|
39
|
+
return Promise.resolve(null);
|
|
40
|
+
}
|
|
41
|
+
const userMessages = messages.filter(m => m.role === 'user');
|
|
42
|
+
const lastUserMessage = userMessages[userMessages.length - 1];
|
|
43
|
+
if (!lastUserMessage) {
|
|
44
|
+
return Promise.resolve(null);
|
|
45
|
+
}
|
|
46
|
+
const userMessage = getMessageText(lastUserMessage);
|
|
47
|
+
const roundNumber = userMessages.length;
|
|
48
|
+
return notifier.aiChat({
|
|
49
|
+
sessionId,
|
|
50
|
+
roundNumber,
|
|
51
|
+
userMessage,
|
|
52
|
+
aiResponse: aiResponse?.slice(0, 500),
|
|
53
|
+
referencedArticles,
|
|
54
|
+
model,
|
|
55
|
+
usage,
|
|
56
|
+
timing,
|
|
57
|
+
siteUrl: env.SITE_URL,
|
|
58
|
+
}).catch((error) => {
|
|
59
|
+
console.error('[notify] AI chat notification failed:', error);
|
|
60
|
+
return null;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { UIMessage } from 'ai';
|
|
2
|
+
import type { ProviderManagerEnv } from '../provider-manager/types.js';
|
|
3
|
+
import type { CacheEnv } from '../cache/types.js';
|
|
4
|
+
export interface ChatContext {
|
|
5
|
+
scope: 'global' | 'article';
|
|
6
|
+
article?: ArticleChatContext;
|
|
7
|
+
}
|
|
8
|
+
export interface ArticleChatContext {
|
|
9
|
+
slug: string;
|
|
10
|
+
title: string;
|
|
11
|
+
categories?: string[];
|
|
12
|
+
summary?: string;
|
|
13
|
+
abstract?: string;
|
|
14
|
+
keyPoints?: string[];
|
|
15
|
+
relatedSlugs?: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface ChatRequestBody {
|
|
18
|
+
context?: ChatContext;
|
|
19
|
+
id?: string;
|
|
20
|
+
messages: UIMessage[];
|
|
21
|
+
lang?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ChatHandlerEnv extends ProviderManagerEnv, CacheEnv {
|
|
24
|
+
SITE_AUTHOR?: string;
|
|
25
|
+
SITE_URL?: string;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface ChatHandlerOptions {
|
|
29
|
+
env: ChatHandlerEnv;
|
|
30
|
+
request: Request;
|
|
31
|
+
}
|
|
32
|
+
export type ChatStatusStage = 'search' | 'answer' | 'complete';
|
|
33
|
+
export interface ChatStatusData {
|
|
34
|
+
stage: ChatStatusStage;
|
|
35
|
+
message: string;
|
|
36
|
+
progress: number;
|
|
37
|
+
done: boolean;
|
|
38
|
+
at: number;
|
|
39
|
+
}
|
|
40
|
+
export declare function createChatStatusData(partial: Omit<ChatStatusData, 'done' | 'at'> & {
|
|
41
|
+
done?: boolean;
|
|
42
|
+
}): ChatStatusData;
|
|
43
|
+
export declare function isChatStatusData(value: unknown): value is ChatStatusData;
|
|
44
|
+
export interface ChatErrorResponse {
|
|
45
|
+
error: string;
|
|
46
|
+
code: string;
|
|
47
|
+
retryable: boolean;
|
|
48
|
+
retryAfter?: number;
|
|
49
|
+
}
|
|
50
|
+
export interface MetadataConfig {
|
|
51
|
+
summaries: unknown;
|
|
52
|
+
authorContext: unknown;
|
|
53
|
+
voiceProfile: unknown;
|
|
54
|
+
siteUrl?: string;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAIlD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAID,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAe,SAAQ,kBAAkB,EAAE,QAAQ;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,cAAc,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;CAClB;AAID,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAChE,cAAc,CAMhB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAIxE;AAID,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function createChatStatusData(partial) {
|
|
2
|
+
return {
|
|
3
|
+
...partial,
|
|
4
|
+
done: partial.done ?? partial.stage === 'complete',
|
|
5
|
+
at: Date.now(),
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function isChatStatusData(value) {
|
|
9
|
+
if (!value || typeof value !== 'object')
|
|
10
|
+
return false;
|
|
11
|
+
const v = value;
|
|
12
|
+
return typeof v.stage === 'string' && typeof v.message === 'string' && typeof v.progress === 'number';
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/stream/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface MockStreamOptions {
|
|
2
|
+
question: string;
|
|
3
|
+
lang?: string;
|
|
4
|
+
/** Delay range per character chunk in ms [min, max] */
|
|
5
|
+
delayRange?: [number, number];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Streams a mock response character-by-character as a ReadableStream<string>.
|
|
9
|
+
* Simulates natural typing speed with variable delays.
|
|
10
|
+
*/
|
|
11
|
+
export declare function streamMockResponse(options: MockStreamOptions): ReadableStream<string>;
|
|
12
|
+
//# sourceMappingURL=mock-stream.d.ts.map
|