diff --git a/Makefile b/Makefile index dc81e55..71e19c6 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ PORT ?= 3000 PM2 := $(shell command -v pm2 2>/dev/null) .PHONY: help init db-init install build up start dev stop restart status logs down \ - playwright-setup save + playwright-setup playwright-deps save help: @echo "巡樓 Haixun — 常用指令" @@ -32,6 +32,7 @@ help: @echo " 其他" @echo " make build next build" @echo " make playwright-setup 安裝 Chromium 與 Playwright 依賴" + @echo " make playwright-deps 安裝 Playwright 系統函式庫(apt)" @echo "" @echo " 環境變數:PORT=$(PORT)(預設 3000)" @@ -91,5 +92,12 @@ save: check-pm2 @pm2 save @echo "PM2 程序列表已儲存(搭配 pm2 startup 可開機自啟)" +playwright-deps: + @echo "安裝 Playwright 系統函式庫..." + @sudo apt-get install -y libatk1.0-0 libatk-bridge2.0-0t64 libcups2t64 \ + libdrm2 libdbus-1-3 libxkbcommon-x11-0 libxcomposite1 libxdamage1 \ + libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2t64 + @echo "Playwright 系統函式庫安裝完成" + playwright-setup: @cd "$(ROOT)" && npm run playwright:setup \ No newline at end of file diff --git a/app/(dashboard)/scans/[topicId]/page.tsx b/app/(dashboard)/scans/[topicId]/page.tsx index ea4bc39..f98d501 100644 --- a/app/(dashboard)/scans/[topicId]/page.tsx +++ b/app/(dashboard)/scans/[topicId]/page.tsx @@ -760,6 +760,7 @@ export default function TopicDetailPage() { onToggle={toggleTag} onSelectAllAccounts={selectAllAccountTags} hideAccounts={isPlacementGoal(topic.topicGoal)} + researchMap={researchMap} /> diff --git a/app/api/outreach/generate/route.ts b/app/api/outreach/generate/route.ts index 0e3c56d..ba9d3d0 100644 --- a/app/api/outreach/generate/route.ts +++ b/app/api/outreach/generate/route.ts @@ -59,7 +59,7 @@ export async function POST(request: Request) { include: { topic: true, items: { - where: { qualityTier: { not: "EXCLUDE" } }, + where: { OR: [{ qualityTier: null }, { qualityTier: { not: "EXCLUDE" } }] }, orderBy: [{ combinedScore: "desc" }, { score: "desc" }], take: body.limit ?? 8, include: { replies: { orderBy: { likeCount: "desc" }, take: 5 } }, diff --git a/components/research-map-view.tsx b/components/research-map-view.tsx index 393d7de..76c2fff 100644 --- a/components/research-map-view.tsx +++ b/components/research-map-view.tsx @@ -11,6 +11,7 @@ import { } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { + SIMILAR_ACCOUNT_CONFIDENCE_LABELS, SIMILAR_ACCOUNT_SOURCE_LABELS, threadsProfileUrl, type ResearchMap, @@ -177,6 +178,16 @@ export function ResearchMapView({ map, showSimilarAccounts = true }: ResearchMap {accounts.map((a) => { const profileUrl = a.profileUrl ?? threadsProfileUrl(a.username); const sourceLabel = a.source ? SIMILAR_ACCOUNT_SOURCE_LABELS[a.source] : null; + const confidenceLabel = a.confidence ? SIMILAR_ACCOUNT_CONFIDENCE_LABELS[a.confidence] : null; + const confidenceColor = + a.confidence === "high" + ? "bg-emerald-500/10 text-emerald-600 border-emerald-200" + : a.confidence === "medium" + ? "bg-amber-500/10 text-amber-600 border-amber-200" + : "bg-muted text-muted-foreground border-border"; + const daysSinceActive = a.lastActiveAt + ? Math.floor((Date.now() - new Date(a.lastActiveAt).getTime()) / 86400000) + : null; return (
+ {daysSinceActive <= 1 ? "最近活躍" : `${daysSinceActive} 天前活躍`} +
+ )} {a.postUrl ? ( ) : (- 尚未找到相似帳號,可重試分析或在微調面板手動加入 @帳號 + 尚未找到相似帳號(沒有高品質也無中低品質候選),可重試分析或在微調面板手動加入 @帳號
)} diff --git a/components/suggested-tags-picker.tsx b/components/suggested-tags-picker.tsx index 77b6516..a5f2726 100644 --- a/components/suggested-tags-picker.tsx +++ b/components/suggested-tags-picker.tsx @@ -4,7 +4,7 @@ import { Check } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; -import type { SuggestedTag } from "@/lib/types/research"; +import { SIMILAR_ACCOUNT_CONFIDENCE_LABELS, type ResearchMap, type SuggestedTag } from "@/lib/types/research"; interface SuggestedTagsPickerProps { tags: SuggestedTag[]; @@ -13,17 +13,28 @@ interface SuggestedTagsPickerProps { onSelectAllAccounts?: () => void; /** 置入模式不顯示相似帳號標籤 */ hideAccounts?: boolean; + researchMap?: ResearchMap | null; } function TagRow({ item, isSelected, onToggle, + confidence, }: { item: SuggestedTag; isSelected: boolean; onToggle: () => void; + confidence?: "high" | "medium" | "low" | null; }) { + const confidenceLabel = confidence ? SIMILAR_ACCOUNT_CONFIDENCE_LABELS[confidence] : null; + const confidenceColor = + confidence === "high" + ? "bg-emerald-500/10 text-emerald-600 border-emerald-200" + : confidence === "medium" + ? "bg-amber-500/10 text-amber-600 border-amber-200" + : "bg-muted text-muted-foreground border-border"; + return (