Skip to content

Commit 7f59533

Browse files
committed
feat: 将 docs-tree API 设为静态生成,优化代理中间件的匹配规则,增加缓存
1 parent 4db1672 commit 7f59533

File tree

4 files changed

+72
-9
lines changed

4 files changed

+72
-9
lines changed

app/api/docs-tree/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import * as fs from "node:fs";
88
import * as path from "node:path";
99

1010
export const runtime = "nodejs";
11-
export const dynamic = "force-dynamic";
12-
export const revalidate = 0;
11+
export const dynamic = "force-static";
12+
// 采用 force-static,使该接口在 Vercel 构建 (Build) 时直接生成静态结果,后续所有访问永远不再消耗 CPU,直到下次由于发版重新构建。
1313

1414
type DirNode = {
1515
name: string;

app/api/suggestions/route.ts

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { generateText } from "ai";
2+
import { unstable_cache } from "next/cache";
23
import { getModel, requiresApiKey, type AIProvider } from "@/lib/ai/models";
34
import { createGlmFlashModel } from "@/lib/ai/providers/glm";
45

@@ -85,10 +86,62 @@ export async function POST(req: Request) {
8586
: `User asked: "${lastText}". Suggest 3 short follow-up questions (max 10 words each). Return a JSON array only, e.g. ["Q1","Q2","Q3"]`;
8687
}
8788

88-
const { text } = await generateText({
89-
model,
90-
prompt,
91-
});
89+
// 将核心请求提取为一个辅助函数以便于使用 Vercel Cache
90+
// 注意:unstable_cache 序列化函数只能接收可序列化的对象,因此仅传递必需的参数
91+
const fetchSuggestionsWithCache = unstable_cache(
92+
async (cPrompt: string, cModelId: string) => {
93+
// 由于 model 不能序列化传入,在缓存函数内侧由于无法动态构建,所以此处传递标识再创建
94+
const m =
95+
cModelId === "intern"
96+
? getModel("intern")
97+
: cModelId === "glm"
98+
? createGlmFlashModel()
99+
: // 无法传递用户动态 key,对于配置了 key 的情况不走此函数
100+
getModel("intern");
101+
102+
const { text } = await generateText({
103+
model: m,
104+
prompt: cPrompt,
105+
});
106+
return text;
107+
},
108+
// 使用明确具有区分度的 cache key
109+
[`suggestions-cache-${isWelcomeRequest ? "welcome" : "followup"}`],
110+
{
111+
revalidate: 60 * 60 * 24, // 对同一个页面的建议缓存 24 小时
112+
tags: ["suggestions"],
113+
},
114+
);
115+
116+
let text = "";
117+
// 判断是否可以使用缓存(如果使用了自定义 apiKey,为了防止数据交叉,不采用公用缓存)
118+
if (provider !== "intern" && !process.env.ZHIPU_API_KEY) {
119+
const { text: directText } = await generateText({
120+
model,
121+
prompt,
122+
});
123+
text = directText;
124+
} else {
125+
// 确定内部用于缓存匹配的模型标识
126+
const internalModelId =
127+
provider === "intern"
128+
? process.env.ZHIPU_API_KEY
129+
? "glm"
130+
: "intern"
131+
: "intern";
132+
// 将缓存粒度关联到请求本身的内容上
133+
const suggestionKey = isWelcomeRequest
134+
? `welcome-${pageContext?.slug || "default"}`
135+
: `followup-${prompt}`;
136+
137+
const cachedFn = unstable_cache(
138+
async (pPrompt: string, pModelId: string) =>
139+
fetchSuggestionsWithCache(pPrompt, pModelId),
140+
[`suggestion-key-${suggestionKey}`],
141+
{ revalidate: 3600 * 24 }, // 24小时
142+
);
143+
text = await cachedFn(prompt, internalModelId);
144+
}
92145

93146
let questions: unknown[] = [];
94147
try {

proxy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ export default auth;
44

55
export const config = {
66
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
7-
matcher: ["/((?!api|_next/static|_next/image|.*\\.png$).*)"],
7+
matcher: [
8+
"/((?!api|_next/static|_next/image|images|favicon|logo|fonts|.*\\.(?:png|jpg|jpeg|gif|webp|svg|ico)$).*)",
9+
],
810
};

scripts/generate-leaderboard.mjs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,19 @@ async function ensureParentDir(filePath) {
3434
}
3535

3636
async function main() {
37+
const outputAbs = path.resolve(REPO_ROOT, OUTPUT);
38+
3739
if (!process.env.DATABASE_URL) {
3840
console.error(
39-
"[generate-leaderboard] 未找到 DATABASE_URL,跳过生成排行榜。 | No DATABASE_URL found. Skipping.",
41+
"[generate-leaderboard] 未找到 DATABASE_URL,跳过生成排行榜。正在为您生成空榜单以放行构建... | No DATABASE_URL found. Skipping. Generating an empty leaderboard for build pass...",
4042
);
43+
await ensureParentDir(outputAbs);
44+
try {
45+
// 检查是否已经存在,存在则不覆盖(或者为了容错直接写入空的 array 也行)
46+
await fs.writeFile(outputAbs, "[]", "utf-8");
47+
} catch (e) {
48+
// Ignore
49+
}
4150
process.exit(0);
4251
}
4352

@@ -191,7 +200,6 @@ async function main() {
191200
}
192201
}
193202

194-
const outputAbs = path.resolve(REPO_ROOT, OUTPUT);
195203
await ensureParentDir(outputAbs);
196204

197205
await fs.writeFile(outputAbs, JSON.stringify(leaderboard, null, 2), "utf8");

0 commit comments

Comments
 (0)