From 07457f09b1ab47879015b4564d06c1db3dc73677 Mon Sep 17 00:00:00 2001 From: Caihaohan Date: Tue, 27 Jan 2026 19:09:16 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=BB=9F=E4=B8=80=20AI=20=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E4=B8=8E=E4=BC=A0=E7=BB=9F=E6=90=9C=E7=B4=A2=E5=B8=83?= =?UTF-8?q?=E5=B1=80=EF=BC=8C=E5=B9=B6=E7=AE=80=E5=8C=96=20n8n=20=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E6=A0=BC=E5=BC=8F=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 只支持 n8n 返回的 {results: [{id, similarity}]} 格式 - AI 搜索结果改用网格布局,与传统搜索保持一致 - 用右上角相似度徽章替代左侧彩色条和黄色高亮框 - 移除顶部的匹配度说明,徽章颜色直观显示匹配质量 Co-Authored-By: Claude (glm-4.7) --- src/app/api/search/ai/route.ts | 36 +++++-------- src/components/search/AISearchResults.tsx | 65 +++++++++-------------- 2 files changed, 38 insertions(+), 63 deletions(-) diff --git a/src/app/api/search/ai/route.ts b/src/app/api/search/ai/route.ts index 5097063..c0391ce 100644 --- a/src/app/api/search/ai/route.ts +++ b/src/app/api/search/ai/route.ts @@ -9,13 +9,15 @@ if (!N8N_WEBHOOK_URL) { throw new Error('N8N_AI_SEARCH_WEBHOOK environment variable is not set') } -// n8n 返回的简化搜索结果 Schema -const N8NSearchResultSchema = z.array( - z.object({ - id: z.string(), - similarity: z.number(), - }) -) +// n8n 返回的搜索结果 Schema(统一格式) +const N8NSearchResponseSchema = z.object({ + results: z.array( + z.object({ + id: z.string(), + similarity: z.number(), + }) + ), +}) export async function POST(request: Request) { try { @@ -50,20 +52,9 @@ export async function POST(request: Request) { // n8n 返回数据 const n8nData = await n8nResponse.json() - // 兼容多种返回格式 - let n8nResults: Array<{ id: string; similarity: number }> - if (Array.isArray(n8nData)) { - // 数组格式: [{ id: "...", similarity: 0.59 }] - n8nResults = N8NSearchResultSchema.parse(n8nData) - } else if (n8nData.id && typeof n8nData.similarity === 'number') { - // 单个对象格式: { id: "...", similarity: 0.59 } - n8nResults = [n8nData as { id: string; similarity: number }] - } else if (n8nData.data && Array.isArray(n8nData.data)) { - // 包装在 data 字段: { data: [{ id: "...", similarity: 0.59 }] } - n8nResults = N8NSearchResultSchema.parse(n8nData.data) - } else { - throw new Error('Invalid n8n response format') - } + // 解析 n8n 响应(只支持 {results: [{id, similarity}, ...]} 格式) + const parsed = N8NSearchResponseSchema.parse(n8nData) + const n8nResults = parsed.results if (n8nResults.length === 0) { return NextResponse.json({ projects: [], pagination: { total: 0, page: 1, limit: validatedQuery.limit || 20 } }) @@ -75,9 +66,6 @@ export async function POST(request: Request) { // 从数据库批量获取完整项目数据 const projects = await getProjectsByIds(projectIds) - // 创建 similarity 映射表(ID -> similarity) - const similarityMap = new Map(n8nResults.map((r) => [r.id, r.similarity])) - // 将 similarity 合并到项目数据中,并按 n8n 返回的顺序排序 const results: AISearchResultItem[] = n8nResults .map((n8nItem) => { diff --git a/src/components/search/AISearchResults.tsx b/src/components/search/AISearchResults.tsx index d8c39a3..fbf7640 100644 --- a/src/components/search/AISearchResults.tsx +++ b/src/components/search/AISearchResults.tsx @@ -33,44 +33,24 @@ export function AISearchResults({ results, locale, translations }: AISearchResul } return ( -
- {/* 相似度说明 */} -
- 匹配度: -
- - - -
-
- - {/* 结果列表 */} - {results.map(({ project, similarity, matchReason }) => ( +
+ {results.map(({ project, similarity }) => (
- {/* 相似度指示条 */} -
- - {/* 项目卡片 */} -
- - - {/* AI 匹配信息 */} -
-
- - 匹配度: {(similarity * 100).toFixed(0)}% - - {matchReason && ( - {matchReason} - )} -
+ {/* 相似度徽章 */} +
+
+ {(similarity * 100).toFixed(0)}%
+ + {/* 项目卡片 */} +
))}
@@ -78,8 +58,15 @@ export function AISearchResults({ results, locale, translations }: AISearchResul } function getSimilarityColor(score: number): string { - if (score > 0.8) return '#22c55e' // green-500 - if (score > 0.6) return '#eab308' // yellow-500 - if (score > 0.4) return '#f97316' // orange-500 - return '#ef4444' // red-500 + if (score > 0.8) return '#dcfce7' // green-100 + if (score > 0.6) return '#fef9c3' // yellow-100 + if (score > 0.4) return '#fed7aa' // orange-100 + return '#fecaca' // red-100 +} + +function getSimilarityTextColor(score: number): string { + if (score > 0.8) return '#166534' // green-800 + if (score > 0.6) return '#854d0e' // yellow-800 + if (score > 0.4) return '#9a3412' // orange-800 + return '#991b1b' // red-800 }