Merge remote-tracking branch 'origin/main'

Fixed conflicts in keyword cloud page and API route
This commit is contained in:
2026-02-01 17:02:17 +08:00
2 changed files with 88 additions and 47 deletions
+83 -1
View File
@@ -135,6 +135,22 @@ When updating an existing project, the webhook:
- **tsconfig.json**: ES2017 target, strict mode enabled, noUncheckedIndexedAccess enabled
- **Testing**: Vitest for unit tests, Playwright for E2E tests (configured but not extensively used yet)
#### Next.js 15 Breaking Change: Async Params
**Critical**: Next.js 15 changed `params` and `searchParams` to be **async** (Promise type).
- In page components: `params: Promise<{ locale: string }>` and `searchParams: Promise<{ key?: string }>`
- In API routes: `{ params }: { params: Promise<{ id: string }> }`
- **Always await these params before using them**:
```typescript
// ✅ Correct
const { locale } = await params;
const resolvedSearchParams = await searchParams;
const quarter = resolvedSearchParams.quarter || 'default';
// ❌ Wrong (causes runtime errors in Next.js 15)
const { locale } = params; // params is a Promise, not an object
const quarter = searchParams.quarter;
```
### Project Discovery System
项目发现系统是自动化探索和收录 AI 项目的核心功能,采用**双 Agent 协作架构**实现上下文隔离:
@@ -233,10 +249,23 @@ API Submitter Agent (提交到生产环境 API)
- `/[locale]/keyword-cloud`: 词云展示页面
#### 前端组件
- **Location**: `src/app/[locale]/keyword-cloud/components/`
- `CloudWord`: 单个词汇组件(支持颜色、大小、边框、旋转、悬停弹出框)
- `QuarterNavigator`: 季度导航组件(前后切换)
- `ProgressIndicator`: 进度条组件(显示季度进度)
- `KeywordCloud`: 主容器组件(集成所有子组件)
- `KeywordCloud`: 主容器组件(集成所有子组件,客户端组件
#### 国际化支持
- **消息键**: `src/messages/{locale}.json` 中的 `keywordCloud` 命名空间
- **支持的字段**:
- `metaTitle`/`metaDescription`: SEO 元数据
- `badge`: 页面徽章文本
- `title`/`titleHighlight`: 主标题(支持高亮)
- `subtitle`: 副标题描述
- `loading`/`loadFailed`/`retry`: 加载状态文本
- `hotKeyword`: 热门词汇提示(支持 `{word}` 参数替换)
- **导航菜单**: `navigation.keywordCloud` 键控制导航栏显示
- **添加新翻译**: 更新 `src/messages/zh.json` 和 `src/messages/en.json` 中的 `keywordCloud` 部分
#### 数据访问层
- **Location**: `src/hooks/useKeywordCloud.ts` (服务器端函数)
@@ -332,6 +361,47 @@ Git 提交信息遵循约定式提交格式(详见上方 Code Quality & Standa
- For project data, prefer Chinese as primary language with English as optional
- When creating content, avoid mechanical translation - write naturally for each locale
### Creating New Pages (Next.js 15)
When adding new pages in the App Router, remember that **params and searchParams are Promises**:
```typescript
// Page component structure
interface PageProps {
params: Promise<{ locale: string; id?: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}
export default async function MyPage({ params, searchParams }: PageProps) {
// MUST await params and searchParams
const { locale, id } = await params;
const { filter, sort } = await searchParams;
// Now you can use the values
// ...
}
// For generateMetadata
export async function generateMetadata({
params
}: {
params: Promise<{ locale: string }>
}) {
const { locale } = await params;
// ...
}
```
For API routes with dynamic parameters:
```typescript
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
// ...
}
```
## Code Quality & Standards
### TypeScript Configuration
@@ -339,6 +409,12 @@ Git 提交信息遵循约定式提交格式(详见上方 Code Quality & Standa
- Path alias: `@/*` maps to `./src/*`
- Target: ES2017 for modern browser support
### ESLint Configuration
- **Config**: `.eslintrc.json` extends `next/core-web-vitals` and `prettier`
- **Console rules**: `no-console` warns on `console.log` but allows `console.warn` and `console.error`
- **Prettier integration**: `eslint-config-prettier` disables conflicting ESLint rules
- **Prettier config**: `.prettierrc.json` for code formatting
### Validation & Security
- **API Authentication**: Webhook uses timing-safe comparison (`crypto.timingSafeEqual`) to prevent timing attacks
- **Input Validation**: All API inputs use Zod schemas with detailed error messages
@@ -411,6 +487,12 @@ Git 提交信息遵循约定式提交格式(详见上方 Code Quality & Standa
- Restart dev server after schema changes
- Check for TypeScript errors in generated types
**Next.js 15 Async Params Errors**
- If you see errors like `params.locale is undefined` or `Cannot read properties of undefined`, you likely forgot to `await` the params
- Check that all page components properly await: `const { locale } = await params;`
- Check that all API routes properly await: `const { id } = await params;`
- See "Next.js Configuration → Next.js 15 Breaking Change" above for examples
**Discovery Task Failures**
- Check `src/app/api/discovery/lib/discovery-service.ts:16-80` for deduplication logic
- Verify `WEBHOOK_API_KEY` is set for api-submitter-agent
+5 -46
View File
@@ -2,12 +2,8 @@ import { KeywordCloud } from "./components/KeywordCloud";
import { getTranslations } from "next-intl/server";
interface PageProps {
params?: Promise<{
locale?: string | string[];
}>;
searchParams?: Promise<{
quarter?: string | string[];
}>;
params: Promise<{ locale: string }>;
searchParams: Promise<{ quarter?: string }>;
}
export async function generateMetadata({ params }: PageProps) {
@@ -23,47 +19,10 @@ export async function generateMetadata({ params }: PageProps) {
}
export default async function KeywordCloudPage({ searchParams, params }: PageProps) {
const resolvedParams = (await params) ?? {};
const rawLocale = resolvedParams.locale;
const locale = (Array.isArray(rawLocale) ? rawLocale[0] : rawLocale) ?? "zh";
const resolvedSearchParams = (await searchParams) ?? {};
const { locale } = await params;
const resolvedSearchParams = await searchParams;
const t = await getTranslations("keywordCloud");
// 如果 URL 中有 quarter 参数,使用它;否则使用默认季度
const rawQuarter = resolvedSearchParams.quarter;
const quarter = (Array.isArray(rawQuarter) ? rawQuarter[0] : rawQuarter) || "2024-Q1";
const texts = {
loading: t("loading"),
loadFailed: t("loadFailed"),
retry: t("retry"),
hotKeyword: t("hotKeyword"),
};
return (
<main className="relative z-10 max-w-6xl mx-auto px-4 pb-32 overflow-visible">
{/* 页面标题 */}
<header className="relative z-10 pt-16 pb-8 text-center max-w-4xl mx-auto px-4">
<div className="inline-block bg-accent dark:bg-purple-700 border-2 border-black px-3 py-1 font-display font-bold text-xs mb-4 shadow-hard-sm rotate-[-2deg]">
{t("badge")}
</div>
<h1 className="font-display text-5xl md:text-7xl font-bold mb-6 tracking-tighter leading-tight">
{t("title")}{" "}
<span
className="text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-purple-500"
style={{ WebkitTextStroke: "1.5px black" }}
>
{t("titleHighlight")}
</span>
</h1>
<p className="text-lg md:text-xl max-w-2xl mx-auto font-medium text-gray-700 dark:text-gray-300 mb-8">
{t("subtitle")}
</p>
</header>
{/* 词云组件 */}
<KeywordCloud initialQuarter={quarter} locale={locale} texts={texts} />
</main>
);
const quarter = resolvedSearchParams.quarter || "2024-Q1";
}