diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 763379a..0c904b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,7 @@ jobs: build-args: | VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL }} VITE_SUPABASE_ANON_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY }} + VITE_REACT_APP_API_URI=${{ secrets.VITE_REACT_APP_API_URI }} cache-from: type=registry,ref=ghcr.io/kyryl-opens-ml/no-ocr-ui:buildcache cache-to: type=registry,ref=ghcr.io/kyryl-opens-ml/no-ocr-ui:buildcache,mode=max diff --git a/no-ocr-api/api.py b/no-ocr-api/api.py index 0354f7b..c3cfb27 100644 --- a/no-ocr-api/api.py +++ b/no-ocr-api/api.py @@ -24,7 +24,7 @@ from qdrant_client import QdrantClient, models from tqdm import tqdm - +import shutil app = FastAPI() # Add CORS middleware to allow any application to call this API @@ -402,3 +402,40 @@ def get_collections(): if not collection_data: return {"message": "No collection data found.", "collections": []} return {"collections": collection_data} + +@app.delete("/delete_all_collections") +def delete_all_collections(): + """ + Delete all collections from storage and Qdrant. + """ + # Delete all collections from storage + if os.path.exists(settings.STORAGE_DIR): + for collection in os.listdir(settings.STORAGE_DIR): + shutil.rmtree(os.path.join(settings.STORAGE_DIR, collection)) + + # Delete all collections from Qdrant + collections = ingest_client.qdrant_client.get_collections().collections + for collection in collections: + ingest_client.qdrant_client.delete_collection(collection.name) + + return {"message": "All collections have been deleted from storage and Qdrant."} + +@app.delete("/delete_collection/{collection_name}") +def delete_collection(collection_name: str): + """ + Delete a specific collection from storage and Qdrant. + """ + # Delete the collection from storage + collection_dir = os.path.join(settings.STORAGE_DIR, collection_name) + if os.path.exists(collection_dir): + shutil.rmtree(collection_dir) + else: + raise HTTPException(status_code=404, detail="Collection not found in storage.") + + # Delete the collection from Qdrant + try: + ingest_client.qdrant_client.delete_collection(collection_name) + except Exception as e: + raise HTTPException(status_code=500, detail=f"An error occurred while deleting the collection from Qdrant: {str(e)}") + + return {"message": f"Collection '{collection_name}' has been deleted from storage and Qdrant."} diff --git a/no-ocr-ui/Dockerfile b/no-ocr-ui/Dockerfile index 2c5fda1..69c4f49 100644 --- a/no-ocr-ui/Dockerfile +++ b/no-ocr-ui/Dockerfile @@ -15,9 +15,10 @@ COPY . . # Set build-time environment variables ARG VITE_SUPABASE_URL ARG VITE_SUPABASE_ANON_KEY +ARG VITE_REACT_APP_API_URI # Build the application -RUN VITE_SUPABASE_URL=$VITE_SUPABASE_URL VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY npm run build +RUN VITE_SUPABASE_URL=$VITE_SUPABASE_URL VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY VITE_REACT_APP_API_URI=$VITE_REACT_APP_API_URI npm run build # Stage 2: Serve the application FROM nginx:alpine diff --git a/no-ocr-ui/src/components/CreateCollection.tsx b/no-ocr-ui/src/components/CreateCollection.tsx index e22301d..680fe5a 100644 --- a/no-ocr-ui/src/components/CreateCollection.tsx +++ b/no-ocr-ui/src/components/CreateCollection.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { Upload, Loader2 } from 'lucide-react'; +import { noOcrApiUrl } from '../config/api'; export default function CreateCollection() { const [collectionName, setCollectionName] = useState(''); @@ -30,7 +31,7 @@ export default function CreateCollection() { formData.append('collection_name', collectionName); Array.from(files).forEach(file => formData.append('files', file)); - const response = await fetch(`http://0.0.0.0:8000/create_collection`, { + const response = await fetch(`${noOcrApiUrl}/create_collection`, { method: 'POST', body: formData, }); diff --git a/no-ocr-ui/src/components/Search.tsx b/no-ocr-ui/src/components/Search.tsx index ba531a4..d30b93a 100644 --- a/no-ocr-ui/src/components/Search.tsx +++ b/no-ocr-ui/src/components/Search.tsx @@ -1,7 +1,7 @@ -/// Start of Selection import React, { useState, useEffect } from 'react'; import { Search as SearchIcon } from 'lucide-react'; import { Collection } from '../types/collection'; +import { noOcrApiUrl } from '../config/api'; export default function Search() { const [selectedCollection, setSelectedCollection] = useState(''); @@ -14,7 +14,7 @@ export default function Search() { useEffect(() => { async function fetchCollections() { try { - const response = await fetch('http://0.0.0.0:8000/get_collections'); + const response = await fetch(`${noOcrApiUrl}/get_collections`); if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json(); setCollections(data.collections || []); @@ -32,7 +32,7 @@ export default function Search() { setIsSearching(true); try { - const response = await fetch('http://0.0.0.0:8000/search', { + const response = await fetch(`${noOcrApiUrl}/search`, { method: 'POST', headers: { 'Accept': 'application/json', diff --git a/no-ocr-ui/src/components/collections/CollectionCard.tsx b/no-ocr-ui/src/components/collections/CollectionCard.tsx index 9654f96..b41fa21 100644 --- a/no-ocr-ui/src/components/collections/CollectionCard.tsx +++ b/no-ocr-ui/src/components/collections/CollectionCard.tsx @@ -2,7 +2,7 @@ import { FileText, Trash2 } from 'lucide-react'; import { Collection } from '../../types/collection'; import { formatDate } from '../../utils/date'; import { useState } from 'react'; -import { supabase } from '../../lib/supabase'; +import { noOcrApiUrl } from '../../config/api'; interface CollectionCardProps { collection: Collection; @@ -16,12 +16,11 @@ export function CollectionCard({ collection }: CollectionCardProps) { setIsDeleting(true); try { - const { error } = await supabase - .from('collections') - .delete() - .eq('id', collection.id); + const response = await fetch(`${noOcrApiUrl}/delete_collection/${collection.name}`, { + method: 'DELETE', + }); - if (error) throw error; + if (!response.ok) throw new Error('Failed to delete collection'); // Collection will be removed from the list by the parent's useEffect } catch (error) { console.error('Error deleting collection:', error); diff --git a/no-ocr-ui/src/components/collections/CollectionList.tsx b/no-ocr-ui/src/components/collections/CollectionList.tsx index e716f3f..24952c9 100644 --- a/no-ocr-ui/src/components/collections/CollectionList.tsx +++ b/no-ocr-ui/src/components/collections/CollectionList.tsx @@ -3,6 +3,7 @@ import { Collection } from '../../types/collection'; import { CollectionCard } from './CollectionCard'; import { LoadingSpinner } from '../shared/LoadingSpinner'; import { EmptyState } from '../shared/EmptyState'; +import { noOcrApiUrl } from '../../config/api'; export function CollectionList() { const [collections, setCollections] = useState([]); @@ -11,7 +12,7 @@ export function CollectionList() { useEffect(() => { async function fetchCollections() { try { - const response = await fetch('http://0.0.0.0:8000/get_collections'); + const response = await fetch(`${noOcrApiUrl}/get_collections`); if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json(); setCollections(data.collections || []); diff --git a/no-ocr-ui/src/config/api.ts b/no-ocr-ui/src/config/api.ts new file mode 100644 index 0000000..b9de0f6 --- /dev/null +++ b/no-ocr-ui/src/config/api.ts @@ -0,0 +1 @@ +export const noOcrApiUrl = import.meta.env.VITE_REACT_APP_API_URI; \ No newline at end of file diff --git a/no-ocr-ui/src/env.d.ts b/no-ocr-ui/src/env.d.ts index 346d203..ddd339f 100644 --- a/no-ocr-ui/src/env.d.ts +++ b/no-ocr-ui/src/env.d.ts @@ -3,6 +3,10 @@ interface ImportMetaEnv { readonly VITE_SUPABASE_URL: string readonly VITE_SUPABASE_ANON_KEY: string + readonly RAILWAY_TOKEN: string + readonly MODAL_TOKEN_ID: string + readonly MODAL_TOKEN_SECRET: string + readonly VITE_REACT_APP_API_URI: string } interface ImportMeta {