diff --git a/apps/web/src/app/traffic/page.tsx b/apps/web/src/app/traffic/page.tsx index 12f2875..9730b40 100644 --- a/apps/web/src/app/traffic/page.tsx +++ b/apps/web/src/app/traffic/page.tsx @@ -13,118 +13,17 @@ const EVENT_LABELS: Record = { EXTERNAL_LIST_OPEN_TASKS_MCP_SURGE: "外部 MCP 流量突增告警", EXTERNAL_CLAIM_TASK_SUCCESS: "外部 AI 成功接單", EXTERNAL_SUBMIT_SOLUTION_SUCCESS: "外部 AI 提交解法", - EXTERNAL_CHECK_PAYOUT_STATUS_SUCCESS: "外部 AI 查詢付款狀態", EXTERNAL_CLAIM_TASK_ERROR: "外部接單失敗", EXTERNAL_SUBMIT_SOLUTION_ERROR: "外部提交失敗", EXTERNAL_LIST_OPEN_TASKS_ERROR: "外部公開流量端點錯誤", EXTERNAL_LIST_OPEN_TASKS_MCP_ERROR: "外部 MCP 流量端點錯誤", - EXTERNAL_MCP_AUTH_MISSING: "MCP 未提供 Bearer Token", - EXTERNAL_MCP_AUTH_FORBIDDEN: "MCP Bearer Token 無效", - EXTERNAL_MCP_TOOL_UNKNOWN: "MCP 不支援 Tool", - EXTERNAL_CLAIM_TASK_FORBIDDEN: "外部 AI 接案被拒", EXTERNAL_FUNNEL_CLAIM_STALL: "外部曝光後未接案", EXTERNAL_FUNNEL_SUBMIT_STALL: "外部接案後未提交", EXTERNAL_FUNNEL_PASS_STALL: "外部提交後未 PASS", EXTERNAL_FUNNEL_PAYOUT_STALL: "PASS 後未出金", - EXTERNAL_FUNNEL_OPEN_COLD_STANDBY: "外部大量曝光但未接案(冷啟動斷崖)", JUDGE_COMPLETE: "AI 交件判定完成", }; -function normalizeSurface(value: unknown) { - if (typeof value !== "string") return "-"; - const trimmed = value.trim(); - return trimmed.length > 0 ? trimmed : "-"; -} - -function normalizeSourceIp(value: unknown) { - if (typeof value !== "string") return "unknown"; - const trimmed = value.trim(); - return trimmed.length > 0 ? trimmed : "unknown"; -} - -function normalizeUserAgent(value: unknown) { - if (typeof value !== "string") return "unknown"; - const trimmed = value.trim(); - if (!trimmed) return "unknown"; - - const lower = trimmed.toLowerCase(); - const tokens = trimmed.split(/[;\s]/).filter((item) => item.length > 0); - const topToken = tokens[0] || trimmed; - - if (lower.includes("python-requests")) return "python-requests"; - if (lower.includes("curl")) return "curl"; - if (lower.includes("node")) return "node"; - if (lower.includes("openai")) return "openai"; - if (lower.includes("perplexity")) return "perplexity"; - if (lower.includes("anthropic")) return "anthropic"; - if (lower.includes("bot")) return "bot"; - - return topToken.length > 48 ? `${topToken.slice(0, 45)}...` : topToken; -} - -const AI_USER_AGENT_HINTS = [ - "gpt", - "chatgpt", - "openai", - "anthropic", - "claude", - "perplexity", - "llm", - "mcp", - "autogpt", - "agent", - "assistant", - "gemini", - "cursor", - "copilot", -]; - -function isLikelyAIAgentActor( - actorType: string | null | undefined, - actorId: string | null | undefined, - metadata: Record | undefined -) { - if (actorType === "AGENT") { - return true; - } - - const normalizedActor = (actorId || "").toLowerCase(); - if (normalizedActor.startsWith("agent:")) { - return true; - } - - const userAgent = String(metadata?.user_agent || "").toLowerCase(); - if (AI_USER_AGENT_HINTS.some((token) => userAgent.includes(token))) { - return true; - } - - const requestHeaders = asRecordJson(metadata?.request_actor_headers); - if (!requestHeaders) { - return false; - } - - const headerText = Object.values(requestHeaders) - .filter((item): item is string => typeof item === "string") - .join(" ") - .toLowerCase(); - - return AI_USER_AGENT_HINTS.some((token) => headerText.includes(token)); -} - -function classifyActorSource( - actorType: string | null | undefined, - actorId: string | null | undefined, - metadata: Record | undefined -) { - if (actorType === "AGENT" || (actorId || "").toLowerCase().startsWith("agent:")) { - return "AGENT"; - } - if (isLikelyAIAgentActor(actorType, actorId, metadata)) { - return "LIKELY_AI"; - } - return "OTHER"; -} - function asRecordJson(value: unknown): Record | undefined { if (typeof value === "object" && value !== null && !Array.isArray(value)) { return value as Record; @@ -139,33 +38,6 @@ function normalizedJudgeResult(value: unknown) { return value.trim().toLowerCase(); } -function summarizeActorHeaders(value: unknown) { - const headers = asRecordJson(value) || {}; - const pairs = Object.entries(headers) - .filter(([, rawValue]) => typeof rawValue === "string" && rawValue.trim().length > 0) - .map(([key, rawValue]) => `${key}=${String(rawValue)}`); - return pairs.length ? pairs.join(" | ") : undefined; -} - -function summarizeResponseOutcome( - action: string, - responseStatus: number | null, - errorName: string, - errorMessage: string, - responseSummary: string -) { - if (errorName || errorMessage) { - return `錯誤回應 (${responseStatus || "?"}): ${errorName || "Error"} ${errorMessage}`; - } - if (responseSummary) { - return `${action} → ${responseSummary} (HTTP ${responseStatus || "-"})`; - } - if (responseStatus !== null) { - return `${action} → HTTP ${responseStatus}`; - } - return `response_status=${responseStatus ?? "-"}`; -} - function percent(numerator: number, denominator: number) { if (!denominator) return 0; return Math.round((numerator / denominator) * 1000) / 10; @@ -175,27 +47,6 @@ function fmtPercent(value: number) { return `${value.toFixed(1)}%`; } -function actorFunnelStage(flow: { - opens: number; - claims: number; - submits: number; - pays: number; -}) { - if (flow.pays > 0) return "查詢付款"; - if (flow.submits > 0) return "已提交"; - if (flow.claims > 0) return "已接案"; - if (flow.opens > 0) return "僅曝光"; - return "異常"; -} - -function actorFunnelBadge(stage: string) { - if (stage === "已提交") return "text-blue-300"; - if (stage === "已接案") return "text-cyan-300"; - if (stage === "查詢付款") return "text-amber-300"; - if (stage === "僅曝光") return "text-yellow-300"; - return "text-gray-400"; -} - function isInternalActorId(value: string | null | undefined) { if (!value) return true; const actorId = value.toLowerCase(); @@ -247,7 +98,6 @@ async function getTrafficSummary(minutes: number) { summaryRows, actorSummaryRows, externalActorRows, - externalAgentActionRows, totalRows, latestEvents, judgeCompleteRows, @@ -278,17 +128,6 @@ async function getTrafficSummary(minutes: number) { }, _count: { _all: true }, }), - prisma.auditEvent.groupBy({ - by: ["actorId", "action"], - where: { - createdAt: { gte: since }, - actorType: "AGENT", - action: { - startsWith: "EXTERNAL_", - }, - }, - _count: { _all: true }, - }), prisma.auditEvent.count({ where: { createdAt: { gte: since } }, }), @@ -337,35 +176,6 @@ async function getTrafficSummary(minutes: number) { }), ]); - type ExternalActorFlow = { - actorId: string; - actorType: string; - events: number; - opens: number; - claims: number; - submits: number; - pays: number; - passes: number; - fails: number; - errors: number; - stage: string; - firstAction: string; - firstAt: number; - firstSurface: string; - firstTaskId: string; - firstSourceIp: string; - firstUserAgent: string; - latestAction: string; - latestAt: number; - latestSourceIp: string; - latestTaskId: string; - latestSurface: string; - responseSummary: string; - responseStatus: number | null; - }; - - const externalAgentFlowMap = new Map(); - const actionSummary = Object.fromEntries(summaryRows.map((row) => [row.action, row._count._all])); const actorSummary = Object.fromEntries(actorSummaryRows.map((row) => [row.actorType, row._count._all])); const externalActorSummary = externalActorRows @@ -377,62 +187,6 @@ async function getTrafficSummary(minutes: number) { .sort((a, b) => b.events - a.events) .slice(0, 20); - externalAgentActionRows.forEach((row) => { - const actorId = row.actorId || "agent:unknown"; - if (isInternalActor({ actorType: "AGENT", actorId })) { - return; - } - - const flow = externalAgentFlowMap.get(actorId) || { - actorId, - actorType: "AGENT", - events: 0, - opens: 0, - claims: 0, - submits: 0, - pays: 0, - passes: 0, - fails: 0, - errors: 0, - stage: "異常", - firstAction: "-", - firstAt: Number.MAX_SAFE_INTEGER, - firstSurface: "-", - firstTaskId: "-", - firstSourceIp: "-", - firstUserAgent: "-", - latestAction: "-", - latestAt: 0, - latestSourceIp: "-", - latestTaskId: "-", - latestSurface: "-", - responseSummary: "n/a", - responseStatus: null, - }; - - flow.events += row._count._all; - if (row.action === "EXTERNAL_LIST_OPEN_TASKS" || row.action === "EXTERNAL_LIST_OPEN_TASKS_MCP") { - flow.opens += row._count._all; - } else if (row.action === "EXTERNAL_CLAIM_TASK_SUCCESS") { - flow.claims += row._count._all; - } else if (row.action === "EXTERNAL_SUBMIT_SOLUTION_SUCCESS") { - flow.submits += row._count._all; - } else if (row.action === "EXTERNAL_CHECK_PAYOUT_STATUS_SUCCESS") { - flow.pays += row._count._all; - } else if (row.action.includes("ERROR") || row.action.includes("FORBIDDEN") || row.action.includes("MISSING")) { - flow.errors += row._count._all; - } - - flow.stage = actorFunnelStage({ - opens: flow.opens, - claims: flow.claims, - submits: flow.submits, - pays: flow.pays, - }); - - externalAgentFlowMap.set(actorId, flow); - }); - const channelSummary = Object.entries(actionSummary).reduce( (acc, [action, count]) => { if (action.startsWith("EXTERNAL_")) { @@ -455,233 +209,14 @@ async function getTrafficSummary(minutes: number) { const recentEvents = latestEvents.map((event) => { const metadata = asRecordJson(event.metadata); - const actorSource = classifyActorSource(event.actorType, event.actorId, metadata); return { ...event, surface: metadata?.surface, level: metadata?.level, - actorSource, metadata, }; }); - const externalSourceSurfaceMap = new Map; latestAt: number }>(); - const externalIpSurfaceMap = new Map; latestAt: number }>(); - const externalUserAgentMap = new Map; latestAt: number }>(); - const externalResponseStatusMap = new Map; latestAt: number }>(); - const externalActorCategorySummary = { - agentActors: new Set(), - likelyAiActors: new Set(), - otherActors: new Set(), - }; - const externalErrorRows: Array<{ - actorId: string; - actorType: string; - action: string; - taskId: string; - surface: string; - sourceIp: string; - userAgent: string; - responseStatus: number | null; - errorName: string; - errorMessage: string; - createdAtMs: number; - }> = []; - - type ActorSourceBucket = { - events: number; - actors: Set; - latestAt: number; - surface?: string; - sourceIp?: string; - userAgent?: string; - }; - - const updateActorBucket = ( - store: Map, - key: string, - actorId: string, - eventAt: number, - keyField: Record - ) => { - const bucket = store.get(key); - if (!bucket) { - store.set(key, { - ...keyField, - events: 1, - actors: new Set([actorId]), - latestAt: eventAt, - }); - return; - } - - bucket.events += 1; - bucket.actors.add(actorId); - if (eventAt > bucket.latestAt) { - bucket.latestAt = eventAt; - } - } - }; - - const updateResponseStatusBucket = ( - responseStatus: string, - actorId: string, - eventAt: number - ) => { - const bucket = externalResponseStatusMap.get(responseStatus); - if (!bucket) { - externalResponseStatusMap.set(responseStatus, { - events: 1, - actors: new Set([actorId]), - latestAt: eventAt, - }); - return; - } - - bucket.events += 1; - bucket.actors.add(actorId); - if (eventAt > bucket.latestAt) { - bucket.latestAt = eventAt; - } - }; - - recentEvents.forEach((event) => { - const metadata = asRecordJson(event.metadata); - const normalizedSurface = normalizeSurface(event.surface); - const normalizedIp = normalizeSourceIp(metadata?.source_ip); - const normalizedUa = normalizeUserAgent(metadata?.user_agent); - const actorId = event.actorId || "agent:unknown"; - const isExternalAgent = event.action.startsWith("EXTERNAL_") && - event.actorType === "AGENT" && - !isInternalActor({ actorType: event.actorType, actorId: event.actorId }); - - if (!isExternalAgent) { - return; - } - - const eventAt = event.createdAt.getTime(); - const errorName = typeof metadata?.error_name === "string" ? metadata.error_name : ""; - const errorMessage = typeof metadata?.error_message === "string" ? metadata.error_message : ""; - const responseStatus = - typeof metadata?.response_status === "number" ? metadata.response_status : null; - const responseSummary = typeof metadata?.response_summary === "string" ? metadata.response_summary : ""; - const outcome = errorName || errorMessage ? "ERROR" : responseSummary || `HTTP_${responseStatus || "-"}`; - const eventTaskId = typeof metadata?.task_id === "string" ? metadata.task_id : (event.entityId || "-"); - - updateActorBucket(externalSourceSurfaceMap, normalizedSurface, actorId, eventAt, { - surface: normalizedSurface, - }); - updateActorBucket(externalIpSurfaceMap, normalizedIp, actorId, eventAt, { - sourceIp: normalizedIp, - }); - updateActorBucket(externalUserAgentMap, normalizedUa, actorId, eventAt, { - userAgent: normalizedUa, - }); - updateResponseStatusBucket(String(responseStatus ?? "n/a"), actorId, eventAt); - - if (event.actorSource === "AGENT") { - externalActorCategorySummary.agentActors.add(actorId); - } else if (event.actorSource === "LIKELY_AI") { - externalActorCategorySummary.likelyAiActors.add(actorId); - } else { - externalActorCategorySummary.otherActors.add(actorId); - } - - if ( - event.action.includes("ERROR") || - event.action.includes("FORBIDDEN") || - event.action.includes("MISSING") - ) { - externalErrorRows.push({ - actorId, - actorType: event.actorType || "USER", - action: event.action, - taskId: eventTaskId, - surface: normalizedSurface, - sourceIp: normalizedIp, - userAgent: normalizedUa, - responseStatus, - errorName: errorName || "unknown", - errorMessage: errorMessage || "unknown", - createdAtMs: eventAt, - }); - } - - const flow = externalAgentFlowMap.get(actorId) || { - actorId, - actorType: event.actorType || "AGENT", - events: 0, - opens: 0, - claims: 0, - submits: 0, - pays: 0, - passes: 0, - fails: 0, - errors: 0, - stage: "異常", - firstAction: event.action, - firstAt: eventAt, - firstSurface: normalizedSurface, - firstTaskId: eventTaskId, - firstSourceIp: normalizedIp, - firstUserAgent: normalizedUa, - latestAction: event.action, - latestAt: eventAt, - latestSourceIp: normalizedIp, - latestTaskId: eventTaskId, - latestSurface: normalizedSurface, - responseSummary: outcome, - responseStatus, - }; - - flow.events += 1; - if (event.action === "EXTERNAL_LIST_OPEN_TASKS" || event.action === "EXTERNAL_LIST_OPEN_TASKS_MCP") { - flow.opens += 1; - } else if (event.action === "EXTERNAL_CLAIM_TASK_SUCCESS") { - flow.claims += 1; - } else if (event.action === "EXTERNAL_SUBMIT_SOLUTION_SUCCESS") { - flow.submits += 1; - } else if (event.action === "EXTERNAL_CHECK_PAYOUT_STATUS_SUCCESS") { - flow.pays += 1; - } else if (event.action === "JUDGE_COMPLETE") { - const judgeResult = normalizedJudgeResult(metadata?.overall_result); - if (judgeResult === "pass") flow.passes += 1; - if (judgeResult === "fail") flow.fails += 1; - } - - if (event.action.includes("ERROR") || event.action.includes("FORBIDDEN") || event.action.includes("MISSING")) { - flow.errors += 1; - } - - if (eventAt < flow.firstAt) { - flow.firstAction = event.action; - flow.firstAt = eventAt; - flow.firstSurface = normalizedSurface; - flow.firstTaskId = eventTaskId; - flow.firstSourceIp = normalizedIp; - flow.firstUserAgent = normalizedUa; - } - - if (eventAt >= flow.latestAt) { - flow.latestAction = event.action; - flow.latestAt = eventAt; - flow.latestSourceIp = normalizedIp; - flow.latestTaskId = eventTaskId; - flow.latestSurface = normalizedSurface; - flow.responseSummary = outcome; - flow.responseStatus = responseStatus; - } - - flow.stage = actorFunnelStage({ - opens: flow.opens, - claims: flow.claims, - submits: flow.submits, - pays: flow.pays, - }); - - externalAgentFlowMap.set(actorId, flow); - }); - const discoveryEvents = (actionSummary["EXTERNAL_LIST_OPEN_TASKS"] || 0) + (actionSummary["EXTERNAL_LIST_OPEN_TASKS_MCP"] || 0); @@ -717,126 +252,6 @@ async function getTrafficSummary(minutes: number) { .filter((event) => event.action.includes("ERROR")) .map((event) => event.action); - const externalSurfaceSummary = Array.from(externalSourceSurfaceMap.entries()) - .map(([surface, bucket]) => ({ - surface, - events: bucket.events, - actors: bucket.actors.size, - latestAt: bucket.latestAt, - })) - .sort((a, b) => b.events - a.events); - - const externalSourceIpSummary = Array.from(externalIpSurfaceMap.entries()) - .map(([sourceIp, bucket]) => ({ - sourceIp, - events: bucket.events, - actors: bucket.actors.size, - latestAt: bucket.latestAt, - })) - .sort((a, b) => b.events - a.events); - - const externalUserAgentSummary = Array.from(externalUserAgentMap.entries()) - .map(([userAgent, bucket]) => ({ - userAgent, - events: bucket.events, - actors: bucket.actors.size, - latestAt: bucket.latestAt, - })) - .sort((a, b) => b.events - a.events); - - const externalResponseStatusSummary = Array.from(externalResponseStatusMap.entries()) - .map(([responseStatus, bucket]) => ({ - responseStatus, - events: bucket.events, - actors: bucket.actors.size, - latestAt: bucket.latestAt, - })) - .sort((a, b) => b.events - a.events); - - const externalErrorRowsSorted = externalErrorRows - .sort((left, right) => right.createdAtMs - left.createdAtMs) - .slice(0, 30); - - const actorFlowValues = Array.from(externalAgentFlowMap.values()); - const actorFunnelSummary = { - total_external_agents: actorFlowValues.length, - actors_open_only: actorFlowValues.filter((item) => item.opens > 0 && item.claims === 0).length, - actors_with_claim: actorFlowValues.filter((item) => item.claims > 0).length, - actors_with_submit: actorFlowValues.filter((item) => item.submits > 0).length, - actors_with_payment_query: actorFlowValues.filter((item) => item.pays > 0).length, - open_only_top: actorFlowValues - .filter((item) => item.opens > 0 && item.claims === 0) - .sort((left, right) => right.opens - left.opens) - .slice(0, 5) - .map((item) => ({ actorId: item.actorId, opens: item.opens })), - }; - - const taskFlowMap = new Map< - string, - { - taskId: string; - opens: number; - claims: number; - submits: number; - passes: number; - fails: number; - lastAction: string; - lastAt: number; - } - >(); - - latestEvents.forEach((event) => { - if (!event.entityId || event.entityId === "open-tasks") { - return; - } - - if (event.action !== "EXTERNAL_LIST_OPEN_TASKS" && event.action !== "JUDGE_COMPLETE" && !event.action.startsWith("EXTERNAL_")) { - return; - } - - const metadata = asRecordJson(event.metadata); - const taskId = typeof metadata?.task_id === "string" ? metadata.task_id : event.entityId; - if (!taskId) return; - - const item = taskFlowMap.get(taskId) || { - taskId, - opens: 0, - claims: 0, - submits: 0, - passes: 0, - fails: 0, - lastAction: event.action, - lastAt: event.createdAt.getTime(), - }; - - if (event.action === "EXTERNAL_LIST_OPEN_TASKS" || event.action === "EXTERNAL_LIST_OPEN_TASKS_MCP") { - item.opens += 1; - } else if (event.action === "EXTERNAL_CLAIM_TASK_SUCCESS") { - item.claims += 1; - } else if (event.action === "EXTERNAL_SUBMIT_SOLUTION_SUCCESS") { - item.submits += 1; - } else if (event.action === "JUDGE_COMPLETE") { - const judgeResult = normalizedJudgeResult(metadata?.overall_result); - if (judgeResult === "pass") item.passes += 1; - if (judgeResult === "fail") item.fails += 1; - } - - if (event.createdAt.getTime() > item.lastAt) { - item.lastAt = event.createdAt.getTime(); - item.lastAction = event.action; - } - - taskFlowMap.set(taskId, item); - }); - - const externalTaskFlow = Array.from(taskFlowMap.values()) - .sort((left, right) => right.lastAt - left.lastAt) - .slice(0, 30); - - const externalAgentFlows = Array.from(externalAgentFlowMap.values()) - .sort((left, right) => right.events - left.events || right.latestAt - left.latestAt) - .slice(0, 30); - return { periodMinutes: minutes, totalEvents: totalRows, @@ -854,21 +269,8 @@ async function getTrafficSummary(minutes: number) { }) ), recentInternalEvents: recentEvents.filter((event) => !event.action.startsWith("EXTERNAL_")), - externalSurfaceSummary, - externalSourceIpSummary, - externalUserAgentSummary, - externalResponseStatusSummary, - externalActorCategorySummary: { - agentActors: externalActorCategorySummary.agentActors.size, - likelyAiActors: externalActorCategorySummary.likelyAiActors.size, - otherActors: externalActorCategorySummary.otherActors.size, - }, - externalErrorRows: externalErrorRowsSorted, conversionSummary, conversionRates, - actorFunnelSummary, - externalTaskFlow, - externalAgentFlows, externalErrors, }; } @@ -890,8 +292,6 @@ function buildConversionTips(summary: { judge_fail_events: number; payout_captured: number; payout_released: number; -}, actorFunnelSummary: { - actors_open_only: number; }) { const steps: string[] = []; @@ -919,10 +319,6 @@ function buildConversionTips(summary: { steps.push("PASS 率偏低:提高指令兼容度,改用可判讀的輸出欄位與固定檔名。"); } - if (actorFunnelSummary.actors_open_only > 0) { - steps.push("有大量 Actor 停留在曝光階段,建議補齊 npx command、回傳欄位格式、最小可交付定義,降低第一層認知門檻。"); - } - if (steps.length === 0) { steps.push("目前流程尚未形成明顯瓶頸。可持續觀察 30 分鐘內轉化率走勢,再做對照測試。 "); } @@ -930,80 +326,6 @@ function buildConversionTips(summary: { return steps; } -function buildEventSignal(event: { - actorType: string | null; - actorId: string | null; - action: string; - createdAt: Date; - metadata?: Record | null; - surface?: string; - actorSource?: string; - reason?: string | null; -}) { - const metadata = asRecordJson(event.metadata) || {}; - const sourceIp = typeof metadata.source_ip === "string" ? metadata.source_ip : "-"; - const userAgent = typeof metadata.user_agent === "string" ? metadata.user_agent : "-"; - const requestId = typeof metadata.request_id === "string" ? metadata.request_id : "-"; - const errorName = typeof metadata.error_name === "string" ? metadata.error_name : ""; - const errorMessage = - typeof metadata.error_message === "string" ? metadata.error_message : ""; - const responseSummary = typeof metadata.response_summary === "string" ? metadata.response_summary : ""; - const taskCount = typeof metadata.task_count === "number" ? metadata.task_count : null; - const responseStatus = typeof metadata.response_status === "number" ? metadata.response_status : null; - const actorHeaders = summarizeActorHeaders(metadata.request_actor_headers); - const taskId = - typeof metadata.task_id === "string" - ? metadata.task_id - : event.entityId || "-"; - const payloadSummary = asRecordJson(metadata.payload_summary); - const payloadHint = - payloadSummary && typeof payloadSummary.tool === "string" - ? `payload=${payloadSummary.tool}:` + - Object.entries(payloadSummary) - .filter(([key]) => key !== "tool") - .map(([key, value]) => `${key}=${typeof value === "object" ? JSON.stringify(value) : value}`) - .slice(0, 2) - .join(",") - : ""; - const summary = summarizeResponseOutcome( - event.action, - responseStatus, - errorName, - errorMessage, - responseSummary - ); - const actorSource = event.actorSource || classifyActorSource(event.actorType, event.actorId, metadata); - - return { - actor: `${event.actorType || "USER"}:${event.actorId || "unknown"}`, - actorSource, - sourceIp, - userAgent, - surface: typeof event.surface === "string" ? event.surface : "-", - summary, - actorHeaders, - taskCount, - requestId, - taskId, - responseStatus, - payloadHint, - createdAtText: toLocalTime(event.createdAt), - createdAtMs: event.createdAt.getTime(), - full: { - action: event.action, - task_count: taskCount, - source_ip: sourceIp, - user_agent: userAgent, - error_name: errorName || undefined, - error_message: errorMessage || undefined, - actor_headers: actorHeaders, - request_id: requestId, - response_status: responseStatus || undefined, - surface: typeof event.surface === "string" ? event.surface : "-", - }, - }; -} - export default async function TrafficDashboard({ searchParams, }: { @@ -1027,39 +349,7 @@ export default async function TrafficDashboard({ const summary = await getTrafficSummary(minutes); const { conversionSummary, conversionRates } = summary; - const conversionHints = buildConversionTips(conversionRates, conversionSummary, summary.actorFunnelSummary); - - const actorEventMap = new Map>>(); - summary.recentExternalEvents.forEach((event) => { - const actor = event.actorId || "unknown"; - const signal = buildEventSignal({ - actorType: event.actorType, - actorId: event.actorId, - action: event.action, - createdAt: event.createdAt, - metadata: event.metadata, - surface: String(event.surface || "-"), - actorSource: (event as { actorSource?: string }).actorSource, - reason: event.reason, - }); - - const list = actorEventMap.get(actor) || []; - list.push(signal); - actorEventMap.set(actor, list); - }); - - const actorEventRows = Array.from(actorEventMap.entries()) - .map(([actor, signals]) => { - const sorted = signals.sort((a, b) => b.createdAtMs - a.createdAtMs); - return { - actor, - events: sorted, - eventsCount: sorted.length, - latestAction: sorted[0]?.action || "-", - latestTask: sorted[0]?.taskId, - }; - }) - .sort((left, right) => right.eventsCount - left.eventsCount); + const conversionHints = buildConversionTips(conversionRates, conversionSummary); return (
@@ -1098,27 +388,7 @@ export default async function TrafficDashboard({
-
-
-
外部 AI Actor
-
{summary.actorFunnelSummary.total_external_agents}
-
在觀測窗有事件的獨立外部 AI Actor
-
-
-
僅曝光未接案
-
{summary.actorFunnelSummary.actors_open_only}
-
- 已接案: {summary.actorFunnelSummary.actors_with_claim}|已提交: {summary.actorFunnelSummary.actors_with_submit} -
-
-
-
已查詢付款
-
{summary.actorFunnelSummary.actors_with_payment_query}
-
查詢付款狀態的外部 Actor 數
-
-
- -
+

外部流量轉化漏斗

@@ -1171,60 +441,6 @@ export default async function TrafficDashboard({
-
-
-

外部來源 Surface 拆解

-
- {summary.externalSurfaceSummary.length === 0 ? ( -

目前區間內未有 surface 分佈資料。

- ) : ( - summary.externalSurfaceSummary.slice(0, 10).map((item) => ( -
- {item.surface} - - {item.events} events / {item.actors} actors - -
- )) - )} -
-
-
-

外部來源 IP Top20

-
- {summary.externalSourceIpSummary.length === 0 ? ( -

目前區間內未有 IP 分佈資料。

- ) : ( - summary.externalSourceIpSummary.slice(0, 10).map((item) => ( -
- {item.sourceIp} - - {item.events} events / {item.actors} actors - -
- )) - )} -
-
-
-

外部 UA 標籤 Top20

-
- {summary.externalUserAgentSummary.length === 0 ? ( -

目前區間內未有 UA 分佈資料。

- ) : ( - summary.externalUserAgentSummary.slice(0, 10).map((item) => ( -
- {item.userAgent} - - {item.events} events / {item.actors} actors - -
- )) - )} -
-
-
-

轉化流程與使用者後續處理(你現在該做什麼)

    @@ -1253,48 +469,8 @@ export default async function TrafficDashboard({ )) )}
- {summary.actorFunnelSummary.open_only_top.length > 0 ? ( -
- 僅曝光高頻前 5:{summary.actorFunnelSummary.open_only_top.map((item) => `${item.actorId}(${item.opens})`).join(";")} -
- ) : null}
-
-

外部 AI 主動行為(Actor 會話級)

-
- {summary.externalAgentFlows?.length === 0 ? ( -

目前區間內無可判讀的外部 AI 主動行為。

- ) : ( - summary.externalAgentFlows.map((actor) => ( -
-
- {actor.actorId} - {actor.events} events -
-
- 階段:{actor.stage} - | Open {actor.opens}|Claim {actor.claims}|Submit {actor.submits}|Pay {actor.pays} -
-
- 首次:{actor.firstAction} @ {actor.firstAt > 0 && actor.firstAt < 4102444800000 ? toLocalTime(new Date(actor.firstAt)) : "-"} | surface={actor.firstSurface} | ip={actor.firstSourceIp} | ua={actor.firstUserAgent} | task={actor.firstTaskId} -
-
- 最近:{actor.latestAction} @ {actor.latestAt ? toLocalTime(new Date(actor.latestAt)) : "-"} | surface={actor.latestSurface} | ip={actor.latestSourceIp} | task={actor.latestTaskId} -
-
- response={actor.responseStatus ?? "-"} ({actor.responseSummary}) -
-
- )) - )} -
-
-
-

Actor 類型分布

@@ -1311,7 +487,7 @@ export default async function TrafficDashboard({
-
+

外部事件說明(可讀)

{Object.entries(summary.actionSummary) @@ -1338,69 +514,6 @@ export default async function TrafficDashboard({
-
-

外部回應結果(HTTP)

-
- {summary.externalResponseStatusSummary.length === 0 ? ( -

目前區間內無外部回應摘要。

- ) : ( - summary.externalResponseStatusSummary.slice(0, 10).map((item) => ( -
- HTTP {item.responseStatus} - - {item.events} events / {item.actors} actors - -
- )) - )} -
-
- -
-

外部 AI 身份判斷

-
-
- 明確 AGENT actor - {summary.externalActorCategorySummary.agentActors} -
-
- 疑似 AI actor(header/UA) - {summary.externalActorCategorySummary.likelyAiActors} -
-
- 其他 - {summary.externalActorCategorySummary.otherActors} -
-
-
- -
-

外部錯誤事件(Top 30)

-
- {summary.externalErrorRows.length === 0 ? ( -

目前區間內無外部錯誤事件。

- ) : ( - summary.externalErrorRows.map((errorRow) => ( -
-
- {errorRow.actorType}:{errorRow.actorId} - {errorRow.action} -
-
- task={errorRow.taskId} | surface={errorRow.surface} | ip={errorRow.sourceIp} | ua={errorRow.userAgent} | response={errorRow.responseStatus ?? "-"} -
-
- {errorRow.errorName}: {errorRow.errorMessage} -
-
- )) - )} -
-
-

可執行轉化建議

    @@ -1423,36 +536,16 @@ export default async function TrafficDashboard({ ) : ( summary.recentExternalEvents.map((event) => { const ts = toLocalTime(event.createdAt); - const signal = buildEventSignal({ - actorType: event.actorType, - actorId: event.actorId, - action: event.action, - createdAt: event.createdAt, - metadata: event.metadata, - surface: String(event.surface || "-"), - actorSource: (event as { actorSource?: string }).actorSource, - reason: event.reason, - }); return ( -
    -
    {event.action}
    -
    - actor={signal.actor} ({signal.actorSource}) | entity={event.entityType}/{event.entityId} | surface={signal.surface} | response={signal.responseStatus ?? "-"} | {ts} -
    - {signal.actorHeaders ? ( -
    actor_headers={signal.actorHeaders}
    - ) : null} -
    - source_ip={signal.sourceIp} | user-agent={signal.userAgent} | request_id={signal.requestId} | response={signal.responseStatus ?? "-"} -
    - {signal.payloadHint ? ( -
    {signal.payloadHint}
    - ) : null} -
    {signal.summary}
    - {event.reason ?
    {event.reason}
    : null} - {event.metadata ? ( -
    -                          {JSON.stringify(event.metadata, null, 2)}
    +                  
    +
    {event.action}
    +
    + actor={event.actorType}:{event.actorId || "unknown"} | entity={event.entityType}/{event.entityId} | surface={String(event.surface || "-")} | {ts} +
    + {event.reason ?
    {event.reason}
    : null} + {event.metadata ? ( +
    +                        {JSON.stringify(event.metadata, null, 2)}
                           
    ) : null}
    @@ -1461,67 +554,6 @@ export default async function TrafficDashboard({ )}
- -
-

外部 AI Actor 行為軌跡(可操作)

-
- {actorEventRows.length === 0 ? ( -

目前區間內無外部 AI 行為軌跡可分析。

- ) : ( - actorEventRows.map((item) => ( -
-
- Actor: {item.actor} - {item.eventsCount} 次事件 -
-
- 最新行為:{item.latestAction}|最新任務:{item.latestTask} -
-
- {item.events.slice(0, 12).map((signal) => ( -
- {signal.full.action} - — {signal.createdAtText} - - surface={signal.surface} | source_ip={signal.sourceIp} | request_id={signal.requestId} - - {signal.actorHeaders ? ( - actor_headers={signal.actorHeaders} - ) : null} - {signal.summary} - {signal.taskId ? task_id={signal.taskId} : null} - {signal.payloadHint ? {signal.payloadHint} : null} -
- ))} -
-
- )) - )} -
-
- -
-

外部任務追蹤(Task 流程)

-
- {summary.externalTaskFlow.length === 0 ? ( -

目前區間內無任務級別事件可彙總。

- ) : ( - summary.externalTaskFlow.map((item) => ( -
-
Task {item.taskId}
-
Open {item.opens}
-
Claim {item.claims}
-
Submit {item.submits}
-
Pass {item.passes}
-
Fail {item.fails}
-
- )) - )} -
-
);