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
|
- **tsconfig.json**: ES2017 target, strict mode enabled, noUncheckedIndexedAccess enabled
|
||||||
- **Testing**: Vitest for unit tests, Playwright for E2E tests (configured but not extensively used yet)
|
- **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
|
### Project Discovery System
|
||||||
项目发现系统是自动化探索和收录 AI 项目的核心功能,采用**双 Agent 协作架构**实现上下文隔离:
|
项目发现系统是自动化探索和收录 AI 项目的核心功能,采用**双 Agent 协作架构**实现上下文隔离:
|
||||||
|
|
||||||
@@ -233,10 +249,23 @@ API Submitter Agent (提交到生产环境 API)
|
|||||||
- `/[locale]/keyword-cloud`: 词云展示页面
|
- `/[locale]/keyword-cloud`: 词云展示页面
|
||||||
|
|
||||||
#### 前端组件
|
#### 前端组件
|
||||||
|
- **Location**: `src/app/[locale]/keyword-cloud/components/`
|
||||||
- `CloudWord`: 单个词汇组件(支持颜色、大小、边框、旋转、悬停弹出框)
|
- `CloudWord`: 单个词汇组件(支持颜色、大小、边框、旋转、悬停弹出框)
|
||||||
- `QuarterNavigator`: 季度导航组件(前后切换)
|
- `QuarterNavigator`: 季度导航组件(前后切换)
|
||||||
- `ProgressIndicator`: 进度条组件(显示季度进度)
|
- `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` (服务器端函数)
|
- **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
|
- For project data, prefer Chinese as primary language with English as optional
|
||||||
- When creating content, avoid mechanical translation - write naturally for each locale
|
- 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
|
## Code Quality & Standards
|
||||||
|
|
||||||
### TypeScript Configuration
|
### TypeScript Configuration
|
||||||
@@ -339,6 +409,12 @@ Git 提交信息遵循约定式提交格式(详见上方 Code Quality & Standa
|
|||||||
- Path alias: `@/*` maps to `./src/*`
|
- Path alias: `@/*` maps to `./src/*`
|
||||||
- Target: ES2017 for modern browser support
|
- 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
|
### Validation & Security
|
||||||
- **API Authentication**: Webhook uses timing-safe comparison (`crypto.timingSafeEqual`) to prevent timing attacks
|
- **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
|
- **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
|
- Restart dev server after schema changes
|
||||||
- Check for TypeScript errors in generated types
|
- 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**
|
**Discovery Task Failures**
|
||||||
- Check `src/app/api/discovery/lib/discovery-service.ts:16-80` for deduplication logic
|
- Check `src/app/api/discovery/lib/discovery-service.ts:16-80` for deduplication logic
|
||||||
- Verify `WEBHOOK_API_KEY` is set for api-submitter-agent
|
- 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";
|
import { getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params?: Promise<{
|
params: Promise<{ locale: string }>;
|
||||||
locale?: string | string[];
|
searchParams: Promise<{ quarter?: string }>;
|
||||||
}>;
|
|
||||||
searchParams?: Promise<{
|
|
||||||
quarter?: string | string[];
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }: PageProps) {
|
export async function generateMetadata({ params }: PageProps) {
|
||||||
@@ -23,47 +19,10 @@ export async function generateMetadata({ params }: PageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function KeywordCloudPage({ searchParams, params }: PageProps) {
|
export default async function KeywordCloudPage({ searchParams, params }: PageProps) {
|
||||||
const resolvedParams = (await params) ?? {};
|
const { locale } = await params;
|
||||||
const rawLocale = resolvedParams.locale;
|
const resolvedSearchParams = await searchParams;
|
||||||
const locale = (Array.isArray(rawLocale) ? rawLocale[0] : rawLocale) ?? "zh";
|
|
||||||
|
|
||||||
const resolvedSearchParams = (await searchParams) ?? {};
|
|
||||||
const t = await getTranslations("keywordCloud");
|
const t = await getTranslations("keywordCloud");
|
||||||
|
|
||||||
// 如果 URL 中有 quarter 参数,使用它;否则使用默认季度
|
// 如果 URL 中有 quarter 参数,使用它;否则使用默认季度
|
||||||
const rawQuarter = resolvedSearchParams.quarter;
|
const quarter = resolvedSearchParams.quarter || "2024-Q1";
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user