Phase 1.4: 자율 연구 — by-project 엔드포인트 + ResearchPage + 세션 이력
백엔드 (crawler_platform/app/api/routes.py):
- POST /research/run/by-project 추가 — config_path 의존 제거
- ResearchRunByProjectRequest: project_name 기반, DB project.config 재구성
- GraphResearchLoop 동기 실행 (장시간 가능), asdict(result) 반환
- 기존 /projects/{n}/research/sessions, /research/sessions/{job_id}는 변경 없음
프론트엔드 API (src/lib/api/):
- research.ts: researchApi.startByProject/listSessions/getSession + Zod 스키마
- 결과 페이로드는 passthrough() (백엔드 dataclass 그대로 노출)
TanStack Query 훅 (src/hooks/):
- useResearch.ts: useStartResearch, useResearchSessions, useResearchSession
- queryKeys에 research.sessions, research.session 키
ResearchPage 신규 (src/pages/ResearchPage.tsx):
- 2열 레이아웃: 좌측 연구 설정 폼 + 우측 결과/이력
- 폼: 소스 선택, goal 텍스트(필수, 3~500자), 시드 URL(선택),
max_steps/max_branch/max_depth/min_relevance, same_domain_only
react-hook-form + zod 검증
- 결과 카드: 단계/페이지/엔티티/클레임 4종 stats + raw JSON details
- 세션 이력 카드: GET /projects/{n}/research/sessions 결과 리스트
- 실행 중 안내 (장시간 가능), sonner 토스트
라우팅 & Sidebar:
- App.tsx: /research/:projectId 라우트 추가
- AppShell: 사이드바에 "자율 연구" 메뉴 (Brain 아이콘)
i18n: research.*, nav.research 키 (한/영)
다음 단계: Phase 1.5 — 엔티티/클레임 직접 입력
(백엔드 POST /projects/{n}/entities, /claims 신규 필요)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 19:01:52 +09:00
|
|
|
import { z } from "zod";
|
|
|
|
|
import { apiClient } from "./client";
|
|
|
|
|
|
|
|
|
|
const idLike = z.union([z.string(), z.number()]).transform(String);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Research result and session payload are loose by design — the backend
|
|
|
|
|
* returns `asdict(result)` of a Python dataclass we don't want to mirror
|
|
|
|
|
* field-for-field. Keep core fields strict and let extras pass through.
|
|
|
|
|
*/
|
|
|
|
|
export const researchRunResultSchema = z
|
|
|
|
|
.object({
|
|
|
|
|
job_id: idLike.optional(),
|
|
|
|
|
project_id: idLike.optional(),
|
|
|
|
|
project_name: z.string().optional(),
|
|
|
|
|
goal: z.string().optional(),
|
|
|
|
|
status: z.string().optional(),
|
|
|
|
|
started_at: z.string().nullable().optional(),
|
|
|
|
|
finished_at: z.string().nullable().optional(),
|
|
|
|
|
steps_taken: z.number().optional(),
|
|
|
|
|
pages_visited: z.number().optional(),
|
|
|
|
|
entities_found: z.number().optional(),
|
|
|
|
|
claims_added: z.number().optional(),
|
|
|
|
|
error: z.string().nullable().optional(),
|
|
|
|
|
})
|
|
|
|
|
.passthrough();
|
|
|
|
|
|
|
|
|
|
export type ResearchRunResult = z.infer<typeof researchRunResultSchema>;
|
|
|
|
|
|
|
|
|
|
export const researchSessionItemSchema = z
|
|
|
|
|
.object({
|
|
|
|
|
job_id: idLike,
|
|
|
|
|
status: z.string().optional(),
|
|
|
|
|
goal: z.string().nullable().optional(),
|
|
|
|
|
seed_url: z.string().nullable().optional(),
|
|
|
|
|
started_at: z.string().nullable().optional(),
|
|
|
|
|
finished_at: z.string().nullable().optional(),
|
|
|
|
|
steps_taken: z.number().nullable().optional(),
|
|
|
|
|
pages_visited: z.number().nullable().optional(),
|
|
|
|
|
entities_found: z.number().nullable().optional(),
|
|
|
|
|
})
|
|
|
|
|
.passthrough();
|
|
|
|
|
|
|
|
|
|
export const researchSessionListSchema = z.array(researchSessionItemSchema);
|
|
|
|
|
export type ResearchSessionItem = z.infer<typeof researchSessionItemSchema>;
|
|
|
|
|
|
|
|
|
|
export const researchSessionDetailSchema = researchSessionItemSchema.extend({
|
|
|
|
|
trace: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type ResearchSessionDetail = z.infer<typeof researchSessionDetailSchema>;
|
|
|
|
|
|
|
|
|
|
export interface StartResearchRequest {
|
|
|
|
|
project_name: string;
|
|
|
|
|
source_name: string;
|
|
|
|
|
url?: string;
|
|
|
|
|
seed_entity_id?: number | null;
|
|
|
|
|
goal?: string;
|
|
|
|
|
max_depth?: number;
|
|
|
|
|
max_steps?: number;
|
|
|
|
|
max_branch?: number;
|
|
|
|
|
min_relevance?: number;
|
|
|
|
|
same_domain_only?: boolean;
|
|
|
|
|
analyze_page_types?: string[];
|
2026-05-22 00:22:03 +09:00
|
|
|
extraction_mode?: "rule_only" | "llm_only" | "hybrid" | "compare";
|
Phase 1.4: 자율 연구 — by-project 엔드포인트 + ResearchPage + 세션 이력
백엔드 (crawler_platform/app/api/routes.py):
- POST /research/run/by-project 추가 — config_path 의존 제거
- ResearchRunByProjectRequest: project_name 기반, DB project.config 재구성
- GraphResearchLoop 동기 실행 (장시간 가능), asdict(result) 반환
- 기존 /projects/{n}/research/sessions, /research/sessions/{job_id}는 변경 없음
프론트엔드 API (src/lib/api/):
- research.ts: researchApi.startByProject/listSessions/getSession + Zod 스키마
- 결과 페이로드는 passthrough() (백엔드 dataclass 그대로 노출)
TanStack Query 훅 (src/hooks/):
- useResearch.ts: useStartResearch, useResearchSessions, useResearchSession
- queryKeys에 research.sessions, research.session 키
ResearchPage 신규 (src/pages/ResearchPage.tsx):
- 2열 레이아웃: 좌측 연구 설정 폼 + 우측 결과/이력
- 폼: 소스 선택, goal 텍스트(필수, 3~500자), 시드 URL(선택),
max_steps/max_branch/max_depth/min_relevance, same_domain_only
react-hook-form + zod 검증
- 결과 카드: 단계/페이지/엔티티/클레임 4종 stats + raw JSON details
- 세션 이력 카드: GET /projects/{n}/research/sessions 결과 리스트
- 실행 중 안내 (장시간 가능), sonner 토스트
라우팅 & Sidebar:
- App.tsx: /research/:projectId 라우트 추가
- AppShell: 사이드바에 "자율 연구" 메뉴 (Brain 아이콘)
i18n: research.*, nav.research 키 (한/영)
다음 단계: Phase 1.5 — 엔티티/클레임 직접 입력
(백엔드 POST /projects/{n}/entities, /claims 신규 필요)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 19:01:52 +09:00
|
|
|
extractor_provider?: string;
|
|
|
|
|
extractor_model?: string | null;
|
|
|
|
|
extractor_base_url?: string | null;
|
2026-05-22 00:22:03 +09:00
|
|
|
fallback_to_rules?: boolean;
|
Phase 1.4: 자율 연구 — by-project 엔드포인트 + ResearchPage + 세션 이력
백엔드 (crawler_platform/app/api/routes.py):
- POST /research/run/by-project 추가 — config_path 의존 제거
- ResearchRunByProjectRequest: project_name 기반, DB project.config 재구성
- GraphResearchLoop 동기 실행 (장시간 가능), asdict(result) 반환
- 기존 /projects/{n}/research/sessions, /research/sessions/{job_id}는 변경 없음
프론트엔드 API (src/lib/api/):
- research.ts: researchApi.startByProject/listSessions/getSession + Zod 스키마
- 결과 페이로드는 passthrough() (백엔드 dataclass 그대로 노출)
TanStack Query 훅 (src/hooks/):
- useResearch.ts: useStartResearch, useResearchSessions, useResearchSession
- queryKeys에 research.sessions, research.session 키
ResearchPage 신규 (src/pages/ResearchPage.tsx):
- 2열 레이아웃: 좌측 연구 설정 폼 + 우측 결과/이력
- 폼: 소스 선택, goal 텍스트(필수, 3~500자), 시드 URL(선택),
max_steps/max_branch/max_depth/min_relevance, same_domain_only
react-hook-form + zod 검증
- 결과 카드: 단계/페이지/엔티티/클레임 4종 stats + raw JSON details
- 세션 이력 카드: GET /projects/{n}/research/sessions 결과 리스트
- 실행 중 안내 (장시간 가능), sonner 토스트
라우팅 & Sidebar:
- App.tsx: /research/:projectId 라우트 추가
- AppShell: 사이드바에 "자율 연구" 메뉴 (Brain 아이콘)
i18n: research.*, nav.research 키 (한/영)
다음 단계: Phase 1.5 — 엔티티/클레임 직접 입력
(백엔드 POST /projects/{n}/entities, /claims 신규 필요)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 19:01:52 +09:00
|
|
|
check_robots_txt?: boolean;
|
|
|
|
|
respect_robots_txt?: boolean | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const researchApi = {
|
|
|
|
|
startByProject: (body: StartResearchRequest) =>
|
|
|
|
|
apiClient.post("/research/run/by-project", researchRunResultSchema, body),
|
|
|
|
|
listSessions: (projectName: string, limit = 25) =>
|
|
|
|
|
apiClient.get(
|
|
|
|
|
`/projects/${encodeURIComponent(projectName)}/research/sessions`,
|
|
|
|
|
researchSessionListSchema,
|
|
|
|
|
{ query: { limit } },
|
|
|
|
|
),
|
|
|
|
|
getSession: (jobId: string) =>
|
|
|
|
|
apiClient.get(
|
|
|
|
|
`/research/sessions/${encodeURIComponent(jobId)}`,
|
|
|
|
|
researchSessionDetailSchema,
|
|
|
|
|
),
|
|
|
|
|
};
|