Merge remote-tracking branch 'origin/main'
Fixed conflicts in keyword cloud page and API route
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user