Phase 1.3: 시드 크롤 — by-project 엔드포인트 + CrawlPage 폼/폴링/취소
세션 영구화: UI_REBUILD_PLAN.md 신설
- 사용자 비전 5가지 흐름 + Phase 보드 + 작업 재개 가이드
- 세션이 끊겨도 이 파일 + git log로 어디까지 했는지 즉시 복원
백엔드 (crawler_platform/app/api/):
- routes.py: POST /crawl-site/by-project 추가 — config_path 의존 제거
- SiteCrawlByProjectRequest: project_name 기반, DB project.config 재구성
- run_site_crawl_job이 __config_dict 메타키 인식하도록 최소 변경
- config/loader.py: project_config_from_dict() 헬퍼 추출 (DB dict ↔ ProjectConfig)
프론트엔드 API (src/lib/api/):
- crawl.ts: crawlApi.startByProject/getJob/cancel + Zod 스키마
- 종료 상태 판정 헬퍼 isCrawlTerminal()
TanStack Query 훅 (src/hooks/):
- useCrawl.ts: useStartSiteCrawl, useCrawlJob (2초 폴링, 종료 시 자동 중단), useCancelCrawl
- queryKeys에 crawl.job 키
UI 프리미티브 (src/components/ui/):
- select.tsx, progress.tsx, badge.tsx (success/warning/destructive variants 포함)
CrawlPage 완전 교체 (src/pages/):
- 2열 레이아웃: 좌측 크롤 설정 폼 + 우측 진행 상태
- 폼: 소스 선택 (드롭다운, 비어있으면 소스 추가 안내) + 시드 URL + max_depth/max_pages + same_domain_only
- react-hook-form + zod 검증
- 진행 상태: 상태 배지, 진행률 바, visited/queued/analyzed 카운터, 최근 페이지, 에러 details
- 크롤 종료시 폴링 자동 중단, 완료시 "결과 검토" 버튼
- 취소 버튼 → /crawl-site/jobs/{id}/cancel
- sonner 토스트
i18n: crawl.* 키 (한/영)
다음 단계: Phase 1.4 — 자율 연구 (POST /research/run by-project 마이그레이션 + ResearchPage)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -19,4 +19,9 @@ export const queryKeys = {
|
||||
byProject: (projectName: string) =>
|
||||
[...queryKeys.sources.all, "byProject", projectName] as const,
|
||||
},
|
||||
crawl: {
|
||||
all: ["crawl"] as const,
|
||||
job: (jobId: string) =>
|
||||
[...queryKeys.crawl.all, "job", jobId] as const,
|
||||
},
|
||||
};
|
||||
|
||||
44
crawler_platform/app/web/frontend/src/hooks/useCrawl.ts
Normal file
44
crawler_platform/app/web/frontend/src/hooks/useCrawl.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
CrawlJob,
|
||||
crawlApi,
|
||||
isCrawlTerminal,
|
||||
StartSiteCrawlRequest,
|
||||
} from "@/lib/api/crawl";
|
||||
import { queryKeys } from "./queryKeys";
|
||||
|
||||
const POLL_INTERVAL_MS = 2000;
|
||||
|
||||
export function useStartSiteCrawl() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (body: StartSiteCrawlRequest) => crawlApi.startByProject(body),
|
||||
onSuccess: (job) => {
|
||||
queryClient.setQueryData(queryKeys.crawl.job(job.job_id), job);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCrawlJob(jobId: string | null | undefined) {
|
||||
return useQuery<CrawlJob>({
|
||||
queryKey: queryKeys.crawl.job(jobId ?? ""),
|
||||
queryFn: () => crawlApi.getJob(jobId!),
|
||||
enabled: Boolean(jobId),
|
||||
refetchInterval: (query) => {
|
||||
const data = query.state.data as CrawlJob | undefined;
|
||||
if (!data) return POLL_INTERVAL_MS;
|
||||
return isCrawlTerminal(data.status) ? false : POLL_INTERVAL_MS;
|
||||
},
|
||||
refetchIntervalInBackground: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCancelCrawl() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (jobId: string) => crawlApi.cancel(jobId),
|
||||
onSuccess: (job) => {
|
||||
queryClient.setQueryData(queryKeys.crawl.job(job.job_id), job);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user