From 1be2c7d3a3c5e9b6c9aab8e2fc1f7c8778825afd Mon Sep 17 00:00:00 2001 From: lasta Date: Thu, 14 May 2026 13:52:45 +0900 Subject: [PATCH] Phase 0: React environment setup + project initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 구현 사항: 1. React + TypeScript 마이그레이션 시작 - package.json: React, Redux Toolkit, TanStack Query, shadcn/ui dependencies 추가 - vite.config.js: React 플러그인 + Phase 5/7 API 프록시 추가 - tsconfig.json, tsconfig.node.json 추가 2. Redux 스토어 설정 - stores/ontologySlice.ts: 온톨로지 상태 관리 - stores/crawlSlice.ts: 크롤링 진행 상태 - stores/uiSlice.ts: UI 상태 3. 리액트 진입점 및 기본 컴포넌트 - src/main.tsx: React 앱 초기화 - src/App.tsx: 라우팅 설정 (대시보드, 온보딩, 소스, 크롤, 검증) - 5개 페이지 컴포넌트 (placeholder) 4. 스타일 및 설정 - tailwind.config.js: Tailwind 설정 - src/styles/globals.css: 글로벌 스타일 - postcss.config.js: PostCSS 설정 - src/i18n.ts: i18next 다국어 설정 - src/lib/queryClient.ts: React Query 설정 5. TypeScript 타입 정의 - src/stores/slices: 각 Redux slice의 TypeScript 타입 프로젝트 구조: crawler_platform/app/web/frontend/ ├── src/ │ ├── main.tsx (React 진입점) │ ├── App.tsx (라우팅) │ ├── pages/ (4개 페이지 컴포넌트) │ ├── stores/ (Redux 스토어 + slices) │ ├── lib/ (유틸리티) │ ├── styles/ (CSS) │ └── i18n.ts (다국어 설정) ├── index.html (업데이트: React root) ├── package.json (의존성 추가) ├── vite.config.js (React 플러그인) ├── tsconfig.json (TypeScript 설정) ├── tailwind.config.js (Tailwind 설정) └── postcss.config.js (PostCSS 설정) 다음 단계: - npm install로 의존성 설치 - Phase 1: 온톨로지 업로더 구현 - Phase 1: 참고 사이트 매니저 구현 Co-Authored-By: Claude Haiku 4.5 --- .claude/settings.local.json | 4 +- crawler_platform/app/web/frontend/index.html | 7 +- .../app/web/frontend/package.json | 33 ++++- .../app/web/frontend/postcss.config.js | 6 + crawler_platform/app/web/frontend/src/App.tsx | 25 ++++ crawler_platform/app/web/frontend/src/i18n.ts | 25 ++++ .../app/web/frontend/src/lib/queryClient.ts | 14 ++ .../app/web/frontend/src/main.tsx | 24 ++++ .../src/pages/ConfigureSourcesPage.tsx | 40 ++++++ .../app/web/frontend/src/pages/CrawlPage.tsx | 40 ++++++ .../web/frontend/src/pages/DashboardPage.tsx | 58 ++++++++ .../web/frontend/src/pages/OnboardingPage.tsx | 41 ++++++ .../app/web/frontend/src/pages/ReviewPage.tsx | 51 +++++++ .../app/web/frontend/src/stores/index.ts | 17 +++ .../frontend/src/stores/slices/crawlSlice.ts | 128 ++++++++++++++++++ .../src/stores/slices/ontologySlice.ts | 87 ++++++++++++ .../web/frontend/src/stores/slices/uiSlice.ts | 65 +++++++++ .../app/web/frontend/src/styles/globals.css | 60 ++++++++ .../app/web/frontend/tailwind.config.js | 44 ++++++ .../app/web/frontend/tsconfig.json | 40 ++++++ .../app/web/frontend/tsconfig.node.json | 10 ++ .../app/web/frontend/vite.config.js | 5 + 22 files changed, 817 insertions(+), 7 deletions(-) create mode 100644 crawler_platform/app/web/frontend/postcss.config.js create mode 100644 crawler_platform/app/web/frontend/src/App.tsx create mode 100644 crawler_platform/app/web/frontend/src/i18n.ts create mode 100644 crawler_platform/app/web/frontend/src/lib/queryClient.ts create mode 100644 crawler_platform/app/web/frontend/src/main.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/ConfigureSourcesPage.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/CrawlPage.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/DashboardPage.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/OnboardingPage.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/ReviewPage.tsx create mode 100644 crawler_platform/app/web/frontend/src/stores/index.ts create mode 100644 crawler_platform/app/web/frontend/src/stores/slices/crawlSlice.ts create mode 100644 crawler_platform/app/web/frontend/src/stores/slices/ontologySlice.ts create mode 100644 crawler_platform/app/web/frontend/src/stores/slices/uiSlice.ts create mode 100644 crawler_platform/app/web/frontend/src/styles/globals.css create mode 100644 crawler_platform/app/web/frontend/tailwind.config.js create mode 100644 crawler_platform/app/web/frontend/tsconfig.json create mode 100644 crawler_platform/app/web/frontend/tsconfig.node.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 14644f2..5d78499 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -43,7 +43,9 @@ "Bash(python -m pytest tests/core/graph/test_entity_resolver.py -v --tb=line)", "Bash(python -m pytest tests/api/test_phase5_app.py -v --tb=short)", "Bash(python -m pytest tests/core/graph/test_subgraph_retriever.py -v --tb=short)", - "Bash(python -m pytest tests/core/graph/test_entity_resolver.py tests/core/graph/test_subgraph_retriever.py tests/core/graph/test_rdf_converter.py tests/api/test_phase5_app.py -v --tb=line)" + "Bash(python -m pytest tests/core/graph/test_entity_resolver.py tests/core/graph/test_subgraph_retriever.py tests/core/graph/test_rdf_converter.py tests/api/test_phase5_app.py -v --tb=line)", + "Bash(python -m pytest tests/integration/test_phase5_phase7_integration.py -v --tb=short)", + "Bash(python -m pytest tests/core/graph/test_entity_resolver.py tests/core/graph/test_subgraph_retriever.py tests/core/graph/test_rdf_converter.py tests/api/test_phase5_app.py tests/integration/test_phase5_phase7_integration.py -v --tb=no)" ] } } diff --git a/crawler_platform/app/web/frontend/index.html b/crawler_platform/app/web/frontend/index.html index eeeaf69..414f057 100644 --- a/crawler_platform/app/web/frontend/index.html +++ b/crawler_platform/app/web/frontend/index.html @@ -3,10 +3,11 @@ - Ontology Crawler Platform + + Ontology Builder - AI-Powered Ontology Construction -
- +
+ diff --git a/crawler_platform/app/web/frontend/package.json b/crawler_platform/app/web/frontend/package.json index 3f3ca98..da4853c 100644 --- a/crawler_platform/app/web/frontend/package.json +++ b/crawler_platform/app/web/frontend/package.json @@ -1,17 +1,44 @@ { "name": "crawler-platform-ui", - "version": "0.1.0", + "version": "0.2.0", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "type-check": "tsc --noEmit" }, "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.0", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0", + "typescript": "^5.3.0", "vite": "^7.0.4" }, "dependencies": { - "cytoscape": "^3.33.3" + "@hookform/resolvers": "^3.3.4", + "@reduxjs/toolkit": "^1.9.7", + "@tanstack/react-query": "^5.28.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", + "cytoscape": "^3.33.3", + "i18next": "^23.7.6", + "i18next-browser-languagedetector": "^7.2.0", + "i18next-http-backend": "^2.4.2", + "lucide-react": "^0.292.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.48.0", + "react-i18next": "^13.4.0", + "react-redux": "^1.13.0", + "react-router-dom": "^6.20.0", + "reactflow": "^11.10.1", + "sonner": "^1.2.3", + "tailwind-merge": "^2.2.0", + "zod": "^3.22.4" } } diff --git a/crawler_platform/app/web/frontend/postcss.config.js b/crawler_platform/app/web/frontend/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/crawler_platform/app/web/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/crawler_platform/app/web/frontend/src/App.tsx b/crawler_platform/app/web/frontend/src/App.tsx new file mode 100644 index 0000000..baad009 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/App.tsx @@ -0,0 +1,25 @@ +import { Routes, Route } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import OnboardingPage from "./pages/OnboardingPage"; +import ConfigureSourcesPage from "./pages/ConfigureSourcesPage"; +import CrawlPage from "./pages/CrawlPage"; +import ReviewPage from "./pages/ReviewPage"; +import DashboardPage from "./pages/DashboardPage"; + +function App() { + const { i18n } = useTranslation(); + + return ( +
+ + } /> + } /> + } /> + } /> + } /> + +
+ ); +} + +export default App; diff --git a/crawler_platform/app/web/frontend/src/i18n.ts b/crawler_platform/app/web/frontend/src/i18n.ts new file mode 100644 index 0000000..d61a941 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/i18n.ts @@ -0,0 +1,25 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import HttpBackend from "i18next-http-backend"; + +i18n + .use(HttpBackend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + fallbackLng: "en", + ns: ["common", "pages"], + defaultNS: "common", + backend: { + loadPath: "/static/locales/{{lng}}/{{ns}}.json", + }, + interpolation: { + escapeValue: false, + }, + react: { + useSuspense: false, + }, + }); + +export default i18n; diff --git a/crawler_platform/app/web/frontend/src/lib/queryClient.ts b/crawler_platform/app/web/frontend/src/lib/queryClient.ts new file mode 100644 index 0000000..bd3c4b1 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/lib/queryClient.ts @@ -0,0 +1,14 @@ +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, // 5 minutes + gcTime: 1000 * 60 * 10, // 10 minutes (formerly cacheTime) + retry: 1, + }, + mutations: { + retry: 1, + }, + }, +}); diff --git a/crawler_platform/app/web/frontend/src/main.tsx b/crawler_platform/app/web/frontend/src/main.tsx new file mode 100644 index 0000000..5c461f8 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/main.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { Provider } from "react-redux"; +import { BrowserRouter } from "react-router-dom"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { Toaster } from "sonner"; +import "./i18n"; +import App from "./App"; +import store from "./stores"; +import { queryClient } from "./lib/queryClient"; +import "./styles/globals.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + + + + + + +); diff --git a/crawler_platform/app/web/frontend/src/pages/ConfigureSourcesPage.tsx b/crawler_platform/app/web/frontend/src/pages/ConfigureSourcesPage.tsx new file mode 100644 index 0000000..648e929 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/pages/ConfigureSourcesPage.tsx @@ -0,0 +1,40 @@ +import { useNavigate, useParams } from "react-router-dom"; +import { useTranslation } from "react-i18next"; + +export default function ConfigureSourcesPage() { + const navigate = useNavigate(); + const { projectId } = useParams(); + const { t } = useTranslation(); + + return ( +
+
+

+ {t("Configure Reference Sites")} +

+

+ {t("Step 2 of 4: Add 5-20 reference sites for knowledge extraction")} +

+ +
+

{t("No sites added yet")}

+
+ +
+ + +
+
+
+ ); +} diff --git a/crawler_platform/app/web/frontend/src/pages/CrawlPage.tsx b/crawler_platform/app/web/frontend/src/pages/CrawlPage.tsx new file mode 100644 index 0000000..4daeb34 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/pages/CrawlPage.tsx @@ -0,0 +1,40 @@ +import { useNavigate, useParams } from "react-router-dom"; +import { useTranslation } from "react-i18next"; + +export default function CrawlPage() { + const navigate = useNavigate(); + const { projectId } = useParams(); + const { t } = useTranslation(); + + return ( +
+
+

+ {t("Build Ontology")} +

+

+ {t("Step 3 of 4: Auto-crawl and build ontology with Phase 5 + 7")} +

+ +
+

{t("Crawl pipeline will appear here")}

+
+ +
+ + +
+
+
+ ); +} diff --git a/crawler_platform/app/web/frontend/src/pages/DashboardPage.tsx b/crawler_platform/app/web/frontend/src/pages/DashboardPage.tsx new file mode 100644 index 0000000..cc0139d --- /dev/null +++ b/crawler_platform/app/web/frontend/src/pages/DashboardPage.tsx @@ -0,0 +1,58 @@ +import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { Plus, BarChart3, Settings } from "lucide-react"; + +export default function DashboardPage() { + const navigate = useNavigate(); + const { t } = useTranslation(); + + return ( +
+
+
+
+

+ {t("Ontology Builder")} +

+

+ {t("Build and manage domain ontologies with AI-powered extraction")} +

+
+ +
+ +
+
+ +

{t("Quick Start")}

+

+ {t("Get started by uploading your domain ontology")} +

+
+ +
+ +

{t("Phase 5 GraphRAG")}

+

+ {t("Intelligent entity resolution and deduplication")} +

+
+ +
+ +

{t("Phase 7 LLM")}

+

+ {t("AI-powered extraction and validation")} +

+
+
+
+
+ ); +} diff --git a/crawler_platform/app/web/frontend/src/pages/OnboardingPage.tsx b/crawler_platform/app/web/frontend/src/pages/OnboardingPage.tsx new file mode 100644 index 0000000..9284bd2 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/pages/OnboardingPage.tsx @@ -0,0 +1,41 @@ +import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; + +export default function OnboardingPage() { + const navigate = useNavigate(); + const { t } = useTranslation(); + + return ( +
+
+

+ {t("Upload Ontology")} +

+

+ {t("Step 1 of 4: Upload your domain ontology (YAML, JSON, or OWL)")} +

+ +
+

+ {t("Drag and drop your ontology file here, or click to browse")} +

+
+ +
+ + +
+
+
+ ); +} diff --git a/crawler_platform/app/web/frontend/src/pages/ReviewPage.tsx b/crawler_platform/app/web/frontend/src/pages/ReviewPage.tsx new file mode 100644 index 0000000..00be0e7 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/pages/ReviewPage.tsx @@ -0,0 +1,51 @@ +import { useNavigate, useParams } from "react-router-dom"; +import { useTranslation } from "react-i18next"; + +export default function ReviewPage() { + const navigate = useNavigate(); + const { projectId } = useParams(); + const { t } = useTranslation(); + + return ( +
+
+

+ {t("Review & Validate")} +

+

+ {t("Step 4 of 4: Review extracted entities and relations")} +

+ +
+
+

+ {t("Phase 5 Entity Merges")} +

+

{t("Review merge suggestions")}

+
+
+

+ {t("Phase 7 Extractions")} +

+

{t("Validate extracted claims")}

+
+
+ +
+ + +
+
+
+ ); +} diff --git a/crawler_platform/app/web/frontend/src/stores/index.ts b/crawler_platform/app/web/frontend/src/stores/index.ts new file mode 100644 index 0000000..499376c --- /dev/null +++ b/crawler_platform/app/web/frontend/src/stores/index.ts @@ -0,0 +1,17 @@ +import { configureStore } from "@reduxjs/toolkit"; +import ontologyReducer from "./slices/ontologySlice"; +import crawlReducer from "./slices/crawlSlice"; +import uiReducer from "./slices/uiSlice"; + +const store = configureStore({ + reducer: { + ontology: ontologyReducer, + crawl: crawlReducer, + ui: uiReducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; + +export default store; diff --git a/crawler_platform/app/web/frontend/src/stores/slices/crawlSlice.ts b/crawler_platform/app/web/frontend/src/stores/slices/crawlSlice.ts new file mode 100644 index 0000000..02e894c --- /dev/null +++ b/crawler_platform/app/web/frontend/src/stores/slices/crawlSlice.ts @@ -0,0 +1,128 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export interface CrawlStep { + name: string; + status: "pending" | "in_progress" | "completed" | "failed"; + progress: number; // 0-100 + message: string; + error?: string; +} + +export interface CrawlState { + projectId: string | null; + isRunning: boolean; + steps: { + fetching: CrawlStep; + extracting: CrawlStep; + merging: CrawlStep; + validating: CrawlStep; + }; + stats: { + pagesFound: number; + pagesCrawled: number; + entitiesExtracted: number; + entitiesMerged: number; + claimsValidated: number; + }; + logs: Array<{ + timestamp: string; + level: "info" | "warning" | "error"; + message: string; + }>; + error: string | null; +} + +const initialCrawlStep: CrawlStep = { + name: "", + status: "pending", + progress: 0, + message: "", +}; + +const initialState: CrawlState = { + projectId: null, + isRunning: false, + steps: { + fetching: { ...initialCrawlStep, name: "Fetching pages" }, + extracting: { ...initialCrawlStep, name: "Extracting entities" }, + merging: { ...initialCrawlStep, name: "Merging duplicates" }, + validating: { ...initialCrawlStep, name: "Validating results" }, + }, + stats: { + pagesFound: 0, + pagesCrawled: 0, + entitiesExtracted: 0, + entitiesMerged: 0, + claimsValidated: 0, + }, + logs: [], + error: null, +}; + +const crawlSlice = createSlice({ + name: "crawl", + initialState, + reducers: { + startCrawl: (state, action: PayloadAction) => { + state.projectId = action.payload; + state.isRunning = true; + state.error = null; + state.logs = []; + Object.values(state.steps).forEach((step) => { + step.status = "pending"; + step.progress = 0; + step.error = undefined; + }); + }, + updateStep: ( + state, + action: PayloadAction<{ + step: keyof typeof state.steps; + status: CrawlStep["status"]; + progress: number; + message: string; + error?: string; + }> + ) => { + const { step, status, progress, message, error } = action.payload; + state.steps[step] = { ...state.steps[step], status, progress, message, error }; + }, + updateStats: (state, action: PayloadAction>) => { + Object.assign(state.stats, action.payload); + }, + addLog: ( + state, + action: PayloadAction<{ + level: "info" | "warning" | "error"; + message: string; + }> + ) => { + state.logs.push({ + timestamp: new Date().toISOString(), + ...action.payload, + }); + }, + setCrawlError: (state, action: PayloadAction) => { + state.error = action.payload; + state.isRunning = false; + }, + completeCrawl: (state) => { + state.isRunning = false; + }, + resetCrawl: (state) => { + Object.assign(state, initialState); + }, + }, +}); + +export const { + startCrawl, + updateStep, + updateStats, + addLog, + setCrawlError, + completeCrawl, + resetCrawl, +} = crawlSlice.actions; + +export default crawlSlice.reducer; diff --git a/crawler_platform/app/web/frontend/src/stores/slices/ontologySlice.ts b/crawler_platform/app/web/frontend/src/stores/slices/ontologySlice.ts new file mode 100644 index 0000000..d19339c --- /dev/null +++ b/crawler_platform/app/web/frontend/src/stores/slices/ontologySlice.ts @@ -0,0 +1,87 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export interface OntologyEntity { + id: string; + label: string; + type: string; + description?: string; +} + +export interface OntologyRelation { + id: string; + source: string; + target: string; + predicate: string; +} + +export interface OntologyState { + id: string | null; + name: string; + domain: string; + format: "yaml" | "json" | "owl" | null; + entities: OntologyEntity[]; + relations: OntologyRelation[]; + isLoaded: boolean; + isSaving: boolean; + error: string | null; +} + +const initialState: OntologyState = { + id: null, + name: "", + domain: "", + format: null, + entities: [], + relations: [], + isLoaded: false, + isSaving: false, + error: null, +}; + +const ontologySlice = createSlice({ + name: "ontology", + initialState, + reducers: { + setOntology: (state, action: PayloadAction>) => { + Object.assign(state, action.payload); + }, + addEntity: (state, action: PayloadAction) => { + state.entities.push(action.payload); + }, + removeEntity: (state, action: PayloadAction) => { + state.entities = state.entities.filter((e) => e.id !== action.payload); + }, + addRelation: (state, action: PayloadAction) => { + state.relations.push(action.payload); + }, + removeRelation: (state, action: PayloadAction) => { + state.relations = state.relations.filter((r) => r.id !== action.payload); + }, + setLoading: (state, action: PayloadAction) => { + state.isLoaded = action.payload; + }, + setSaving: (state, action: PayloadAction) => { + state.isSaving = action.payload; + }, + setError: (state, action: PayloadAction) => { + state.error = action.payload; + }, + resetOntology: (state) => { + Object.assign(state, initialState); + }, + }, +}); + +export const { + setOntology, + addEntity, + removeEntity, + addRelation, + removeRelation, + setLoading, + setSaving, + setError, + resetOntology, +} = ontologySlice.actions; + +export default ontologySlice.reducer; diff --git a/crawler_platform/app/web/frontend/src/stores/slices/uiSlice.ts b/crawler_platform/app/web/frontend/src/stores/slices/uiSlice.ts new file mode 100644 index 0000000..b2cc075 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/stores/slices/uiSlice.ts @@ -0,0 +1,65 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export interface UIState { + sidebarOpen: boolean; + currentStep: number; // 0: dashboard, 1: onboard, 2: sources, 3: crawl, 4: review + selectedProjectId: string | null; + isLoading: boolean; + notification: { + type: "success" | "error" | "info" | "warning" | null; + message: string; + }; +} + +const initialState: UIState = { + sidebarOpen: true, + currentStep: 0, + selectedProjectId: null, + isLoading: false, + notification: { + type: null, + message: "", + }, +}; + +const uiSlice = createSlice({ + name: "ui", + initialState, + reducers: { + toggleSidebar: (state) => { + state.sidebarOpen = !state.sidebarOpen; + }, + setCurrentStep: (state, action: PayloadAction) => { + state.currentStep = action.payload; + }, + setSelectedProject: (state, action: PayloadAction) => { + state.selectedProjectId = action.payload; + }, + setLoading: (state, action: PayloadAction) => { + state.isLoading = action.payload; + }, + showNotification: ( + state, + action: PayloadAction<{ + type: "success" | "error" | "info" | "warning"; + message: string; + }> + ) => { + state.notification = action.payload; + }, + clearNotification: (state) => { + state.notification = { type: null, message: "" }; + }, + }, +}); + +export const { + toggleSidebar, + setCurrentStep, + setSelectedProject, + setLoading, + showNotification, + clearNotification, +} = uiSlice.actions; + +export default uiSlice.reducer; diff --git a/crawler_platform/app/web/frontend/src/styles/globals.css b/crawler_platform/app/web/frontend/src/styles/globals.css new file mode 100644 index 0000000..28ea494 --- /dev/null +++ b/crawler_platform/app/web/frontend/src/styles/globals.css @@ -0,0 +1,60 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --muted: 221.2 63.6% 97%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 221.2 83.2% 53.3%; + --accent-foreground: 210 40% 98%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --radius: 0.5rem; + --primary: 222.2 47.6% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96%; + --secondary-foreground: 222.2 47.6% 11.2%; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 91.2% 59.8%; + --accent-foreground: 222.2 47.6% 11.2%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.6% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + } +} + +@layer base { + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + } +} diff --git a/crawler_platform/app/web/frontend/tailwind.config.js b/crawler_platform/app/web/frontend/tailwind.config.js new file mode 100644 index 0000000..83b8a92 --- /dev/null +++ b/crawler_platform/app/web/frontend/tailwind.config.js @@ -0,0 +1,44 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + }, + }, + plugins: [], +} diff --git a/crawler_platform/app/web/frontend/tsconfig.json b/crawler_platform/app/web/frontend/tsconfig.json new file mode 100644 index 0000000..9b838db --- /dev/null +++ b/crawler_platform/app/web/frontend/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + /* Path mapping */ + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@components/*": ["src/components/*"], + "@pages/*": ["src/pages/*"], + "@hooks/*": ["src/hooks/*"], + "@stores/*": ["src/stores/*"], + "@types/*": ["src/types/*"], + "@utils/*": ["src/utils/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/crawler_platform/app/web/frontend/tsconfig.node.json b/crawler_platform/app/web/frontend/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/crawler_platform/app/web/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/crawler_platform/app/web/frontend/vite.config.js b/crawler_platform/app/web/frontend/vite.config.js index 1c7989e..23496f3 100644 --- a/crawler_platform/app/web/frontend/vite.config.js +++ b/crawler_platform/app/web/frontend/vite.config.js @@ -1,9 +1,11 @@ import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; const BACKEND = "http://127.0.0.1:8000"; const PROXY_PREFIXES = [ "/projects", "/ontology", + "/ontologies", "/extractors", "/crawl", "/crawl-site", @@ -13,9 +15,12 @@ const PROXY_PREFIXES = [ "/claims", "/entities", "/health", + "/api/v1/graph", + "/api/v1/llm", ]; export default defineConfig({ + plugins: [react()], root: ".", base: "/static/", build: {