diff --git a/crawler_platform/app/api/routes.py b/crawler_platform/app/api/routes.py index 5a8fbb3..5387007 100644 --- a/crawler_platform/app/api/routes.py +++ b/crawler_platform/app/api/routes.py @@ -7,7 +7,7 @@ from fastapi import BackgroundTasks, HTTPException from pydantic import BaseModel, Field from sqlalchemy import select -from crawler_platform.app.config.loader import load_project_config +from crawler_platform.app.config.loader import ProjectConfig, SourceConfig, load_project_config from crawler_platform.app.core.crawler.discovery import discover_links from crawler_platform.app.core.crawler.fetchers import RobotsPolicy, make_fetcher from crawler_platform.app.core.crawler.pipeline import CrawlPipeline @@ -17,7 +17,7 @@ from crawler_platform.app.core.database.repository import KnowledgeRepository from crawler_platform.app.core.database.session import session_scope from crawler_platform.app.core.extractor.ai_provider import list_openai_compatible_models from crawler_platform.app.core.extractor.factory import extractor_for_domain -from crawler_platform.app.core.ontology.definitions import ontology_for_domain +from crawler_platform.app.core.ontology.definitions import DOMAIN_ONTOLOGIES, ontology_for_domain from crawler_platform.app.core.ontology.gap_detector import KnowledgeGapDetector from crawler_platform.app.core.ontology.mapper import ontology_to_dict from crawler_platform.app.core.ontology.registry import OntologyRegistry @@ -65,6 +65,43 @@ class CreateProjectRequest(BaseModel): config_path: str +class InlineSourceConfig(BaseModel): + name: str + type: str = "unknown" + trust_level: float = 0.5 + base_url: str | None = None + allowed_paths: list[str] = Field(default_factory=list) + parser: str = "generic" + fetcher: str = "requests" + rate_limit_per_minute: int = 30 + respect_robots_txt: bool = False + + +class CreateProjectInlineRequest(BaseModel): + """Create a project from inline JSON config (no filesystem dependency).""" + + project_name: str + domain: str + target_entities: list[str] = Field(default_factory=list) + fields: list[str] = Field(default_factory=list) + sources: list[InlineSourceConfig] = Field(default_factory=list) + ontology: dict[str, Any] = Field(default_factory=dict) + recommendation: dict[str, Any] = Field(default_factory=dict) + update_policy: dict[str, Any] = Field(default_factory=dict) + + def to_project_config(self) -> ProjectConfig: + return ProjectConfig( + project_name=self.project_name, + domain=self.domain, + target_entities=list(self.target_entities), + fields=list(self.fields), + sources=[SourceConfig(**s.model_dump()) for s in self.sources], + ontology=dict(self.ontology), + recommendation=dict(self.recommendation), + update_policy=dict(self.update_policy), + ) + + class ResetProjectRequest(BaseModel): config_path: str project_name: str | None = None @@ -298,6 +335,26 @@ def register_routes(app, database_url: str) -> None: project = KnowledgeRepository(session).upsert_project(config) return {"id": project.id, "name": project.name, "domain": project.domain} + @app.post("/projects/inline") + def create_project_inline(request: CreateProjectInlineRequest): + config = request.to_project_config() + with session_scope(database_url) as session: + project = KnowledgeRepository(session).upsert_project(config) + return {"id": project.id, "name": project.name, "domain": project.domain} + + @app.get("/domains") + def list_domains(): + """Available pre-defined ontology domains for project creation.""" + return [ + { + "domain": ont.domain, + "entity_types": list(ont.entity_types), + "predicates": list(ont.predicates), + "attribute_count": len(ont.attributes), + } + for ont in DOMAIN_ONTOLOGIES.values() + ] + @app.post("/projects/reset") def reset_project(request: ResetProjectRequest): config = load_project_config(request.config_path) diff --git a/crawler_platform/app/web/frontend/public/locales/en/common.json b/crawler_platform/app/web/frontend/public/locales/en/common.json index 91a7d4e..ee86693 100644 --- a/crawler_platform/app/web/frontend/public/locales/en/common.json +++ b/crawler_platform/app/web/frontend/public/locales/en/common.json @@ -5,7 +5,7 @@ }, "nav": { "dashboard": "Dashboard", - "onboard": "Upload Ontology", + "onboard": "New Project", "sources": "Sources", "crawl": "Crawl", "review": "Review", @@ -23,6 +23,18 @@ "hint": "Create your first project to start building an ontology" } }, + "onboarding": { + "title": "Create New Project", + "formTitle": "Choose Ontology Domain", + "formDesc": "Pick the domain of ontology you want to build and give your project a name.", + "projectName": "Project Name", + "projectNameHint": "Letters, digits, _ and - only (2~64 chars)", + "domain": "Domain", + "domainSummary": "{{entities}} entity types · {{predicates}} predicates", + "submit": "Create Project", + "created": "Project created: {{name}}", + "createFailed": "Create failed: {{msg}}" + }, "common": { "retry": "Retry", "cancel": "Cancel", diff --git a/crawler_platform/app/web/frontend/public/locales/ko/common.json b/crawler_platform/app/web/frontend/public/locales/ko/common.json index 274c0a1..d7009bb 100644 --- a/crawler_platform/app/web/frontend/public/locales/ko/common.json +++ b/crawler_platform/app/web/frontend/public/locales/ko/common.json @@ -5,7 +5,7 @@ }, "nav": { "dashboard": "대시보드", - "onboard": "온톨로지 업로드", + "onboard": "프로젝트 생성", "sources": "참고 소스", "crawl": "크롤 진행", "review": "결과 검토", @@ -23,6 +23,18 @@ "hint": "첫 프로젝트를 만들어 온톨로지 구축을 시작하세요" } }, + "onboarding": { + "title": "새 프로젝트 만들기", + "formTitle": "온톨로지 도메인 선택", + "formDesc": "어떤 종류의 온톨로지를 구축할지 도메인을 선택하고 프로젝트 이름을 정해주세요.", + "projectName": "프로젝트 이름", + "projectNameHint": "영문, 숫자, _ , - 만 사용 (2~64자)", + "domain": "도메인", + "domainSummary": "엔티티 {{entities}}종 · 관계 {{predicates}}개", + "submit": "프로젝트 만들기", + "created": "프로젝트가 생성되었습니다: {{name}}", + "createFailed": "생성 실패: {{msg}}" + }, "common": { "retry": "다시 시도", "cancel": "취소", diff --git a/crawler_platform/app/web/frontend/src/components/ui/input.tsx b/crawler_platform/app/web/frontend/src/components/ui/input.tsx new file mode 100644 index 0000000..98efa75 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +export type InputProps = React.InputHTMLAttributes; + +export const Input = React.forwardRef( + ({ className, type = "text", ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; diff --git a/crawler_platform/app/web/frontend/src/components/ui/label.tsx b/crawler_platform/app/web/frontend/src/components/ui/label.tsx new file mode 100644 index 0000000..73f6885 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/components/ui/label.tsx @@ -0,0 +1,17 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +export const Label = React.forwardRef< + HTMLLabelElement, + React.LabelHTMLAttributes +>(({ className, ...props }, ref) => ( +