SCGR commited on
Commit
ab3b988
Β·
1 Parent(s): 26c355d

Docker configs

Browse files
.github/workflows/huggingFace.yml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face hub
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ workflow_dispatch:
6
+
7
+ jobs:
8
+ sync-to-hub:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+ with:
13
+ fetch-depth: 0
14
+ lfs: true
15
+ - name: Push to HF Space
16
+ env:
17
+ HF_API_KEY: ${{ secrets.HF_API_KEY }}
18
+ run: |
19
+ git push https://HF_USERNAME:${HF_API_KEY}@huggingface.co/spaces/SCGR/Promptaid_Vision main
README.md CHANGED
@@ -1,8 +1,45 @@
1
- # PromptAid-Vision
2
- UCL IXN summer project - IFRC
 
 
 
 
 
 
3
 
4
- System architecture
5
- React => Python => postgreSQL => docker => fly.io/hugging face
6
 
7
- VLMs
8
- GPT-4O, Gemini, Claude, hugging face inference api (llava, Blip)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: PromptAid Vision
3
+ emoji: πŸš€
4
+ colorFrom: blue
5
+ colorTo: red
6
+ sdk: docker
7
+ app_port: 8080
8
+ ---
9
 
10
+ # PromptAid Vision
 
11
 
12
+ AI-powered crisis map analysis platform using Vision Language Models.
13
+
14
+ ## Features
15
+
16
+ - **Crisis map/drone imagery analysis**
17
+ - **Explore & export database**
18
+ - **Analytics dashboard**
19
+
20
+ ## Tech Stack
21
+
22
+ - **Backend**: FastAPI, Python 3.11
23
+ - **AI Models**: OpenAI GPT-4V, Google Gemini, Hugging Face models
24
+ - **Database**: PostgreSQL
25
+ - **Storage**: S3-compatible storage
26
+ - **Frontend**: React with TypeScript
27
+
28
+ ## Environment Variables
29
+
30
+ - `DATABASE_URL`: PostgreSQL connection string
31
+ - `S3_ENDPOINT`: S3-compatible storage endpoint
32
+ - `S3_ACCESS_KEY`: Storage access key
33
+ - `S3_SECRET_KEY`: Storage secret key
34
+ - `S3_BUCKET`: Storage bucket name
35
+ - `OPENAI_API_KEY`: OpenAI API key
36
+ - `GOOGLE_API_KEY`: Google API key
37
+ - `HF_API_KEY`: Hugging Face API key
38
+
39
+ ## API Endpoints
40
+
41
+ - `/api/images` - Image management
42
+ - `/api/captions` - AI caption generation
43
+ - `/api/metadata` - Metadata operations
44
+ - `/api/models` - Available AI models
45
+ - `/api/contribute` - Contribution system
deploy.sh ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ echo "πŸš€ PromptAid Vision Deployment Script"
4
+ echo "====================================="
5
+
6
+ echo "πŸ“¦ Building backend Docker image..."
7
+ cd py_backend
8
+ docker build -t promptaid-vision-backend .
9
+
10
+ if [ $? -eq 0 ]; then
11
+ echo "βœ… Backend image built successfully!"
12
+ else
13
+ echo "❌ Backend image build failed!"
14
+ exit 1
15
+ fi
16
+
17
+ cd ..
18
+
19
+ echo "πŸ§ͺ Testing production setup locally..."
20
+ docker-compose -f docker-compose.prod.yml up -d
21
+
22
+ echo "⏳ Waiting for services to start..."
23
+ sleep 10
24
+
25
+ echo "πŸ” Checking backend health..."
26
+ response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/models)
27
+
28
+ if [ $response -eq 200 ]; then
29
+ echo "βœ… Backend is healthy and responding!"
30
+ else
31
+ echo "❌ Backend health check failed (HTTP $response)"
32
+ echo "πŸ“‹ Stopping services..."
33
+ docker-compose -f docker-compose.prod.yml down
34
+ exit 1
35
+ fi
36
+
37
+ echo "πŸ“‹ Stopping test services..."
38
+ docker-compose -f docker-compose.prod.yml down
39
+
40
+ echo ""
41
+ echo "πŸŽ‰ Deployment preparation completed!"
42
+ echo ""
43
+ echo "πŸ“‹ Next steps:"
44
+ echo "1. Set up cloud database (PostgreSQL)"
45
+ echo "2. Set up cloud storage (S3-compatible)"
46
+ echo "3. Configure environment variables in Hugging Face Spaces"
47
+ echo "4. Push your code to Hugging Face Spaces"
48
+ echo ""
49
+ echo "πŸ”§ To test locally again, run:"
50
+ echo " docker-compose -f docker-compose.prod.yml up"
51
+ echo ""
52
+ echo "🌍 To deploy to Hugging Face Spaces:"
53
+ echo " git add ."
54
+ echo " git commit -m 'Prepare for Hugging Face Spaces deployment'"
55
+ echo " git push"
docker-compose.prod.yml ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: "3.8"
2
+
3
+ services:
4
+ backend:
5
+ build: ./py_backend
6
+ ports:
7
+ - "8080:8080"
8
+ environment:
9
+ - DATABASE_URL=postgresql://promptaid:promptaid@postgres:5432/promptaid
10
+ - S3_ENDPOINT=http://minio:9000
11
+ - S3_ACCESS_KEY=promptaid
12
+ - S3_SECRET_KEY=promptaid
13
+ - S3_BUCKET=promptaid
14
+ - ENVIRONMENT=production
15
+ - BASE_URL=http://localhost:8080
16
+ depends_on:
17
+ - postgres
18
+ - minio
19
+ restart: unless-stopped
20
+
21
+ postgres:
22
+ image: postgres:16
23
+ restart: always
24
+ environment:
25
+ POSTGRES_USER: promptaid
26
+ POSTGRES_PASSWORD: promptaid
27
+ POSTGRES_DB: promptaid
28
+ ports:
29
+ - "5433:5432"
30
+ volumes:
31
+ - pgdata:/var/lib/postgresql/data
32
+
33
+ minio:
34
+ image: minio/minio:latest
35
+ restart: always
36
+ command: server /data --console-address ":9001"
37
+ environment:
38
+ MINIO_ROOT_USER: promptaid
39
+ MINIO_ROOT_PASSWORD: promptaid
40
+ ports:
41
+ - "9000:9000"
42
+ - "9001:9001"
43
+ volumes:
44
+ - minio_data:/data
45
+
46
+ volumes:
47
+ pgdata:
48
+ minio_data:
frontend/src/pages/UploadPage/UploadPage.tsx CHANGED
@@ -480,14 +480,15 @@ export default function UploadPage() {
480
 
481
  return (
482
  <PageContainer>
483
- <Container
484
- heading="Upload Your Image"
485
- headingLevel={2}
486
- withHeaderBorder
487
- withInternalPadding
488
- className="max-w-7xl mx-auto"
489
- >
490
- <div className={styles.uploadContainer} data-step={step}>
 
491
  {/* Drop-zone */}
492
  {step === 1 && !searchParams.get('step') && (
493
  <div className="space-y-6">
@@ -829,22 +830,6 @@ export default function UploadPage() {
829
  </div>
830
  )}
831
 
832
- {/* Success page */}
833
- {step === 3 && (
834
- <div className={styles.successContainer}>
835
- <Heading level={2} className={styles.successHeading}>Saved!</Heading>
836
- <p className={styles.successText}>Your caption has been successfully saved.</p>
837
- <div className={styles.successButton}>
838
- <Button
839
- name="upload-another"
840
- onClick={resetToStep1}
841
- >
842
- Upload Another
843
- </Button>
844
- </div>
845
- </div>
846
- )}
847
-
848
  {/* Full Size Image Modal */}
849
  {isFullSizeModalOpen && (
850
  <div className={styles.fullSizeModalOverlay} onClick={() => setIsFullSizeModalOpen(false)}>
@@ -870,7 +855,24 @@ export default function UploadPage() {
870
  )}
871
 
872
  </div>
873
- </Container>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
874
  </PageContainer>
875
  );
876
  }
 
480
 
481
  return (
482
  <PageContainer>
483
+ {step !== 3 && (
484
+ <Container
485
+ heading="Upload Your Image"
486
+ headingLevel={2}
487
+ withHeaderBorder
488
+ withInternalPadding
489
+ className="max-w-7xl mx-auto"
490
+ >
491
+ <div className={styles.uploadContainer} data-step={step}>
492
  {/* Drop-zone */}
493
  {step === 1 && !searchParams.get('step') && (
494
  <div className="space-y-6">
 
830
  </div>
831
  )}
832
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  {/* Full Size Image Modal */}
834
  {isFullSizeModalOpen && (
835
  <div className={styles.fullSizeModalOverlay} onClick={() => setIsFullSizeModalOpen(false)}>
 
855
  )}
856
 
857
  </div>
858
+ </Container>
859
+ )}
860
+
861
+ {/* Success page - outside the upload container */}
862
+ {step === 3 && (
863
+ <div className={styles.successContainer}>
864
+ <Heading level={2} className={styles.successHeading}>Saved!</Heading>
865
+ <p className={styles.successText}>Your caption has been successfully saved.</p>
866
+ <div className={styles.successButton}>
867
+ <Button
868
+ name="upload-another"
869
+ onClick={resetToStep1}
870
+ >
871
+ Upload Another
872
+ </Button>
873
+ </div>
874
+ </div>
875
+ )}
876
  </PageContainer>
877
  );
878
  }
py_backend/.dockerignore ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ env
7
+ pip-log.txt
8
+ pip-delete-this-directory.txt
9
+ .tox
10
+ .coverage
11
+ .coverage.*
12
+ .cache
13
+ nosetests.xml
14
+ coverage.xml
15
+ *.cover
16
+ *.log
17
+ .git
18
+ .mypy_cache
19
+ .pytest_cache
20
+ .hypothesis
21
+ .env
22
+ .venv
23
+ venv/
24
+ tests/
25
+ *.md
26
+ .gitignore
27
+ README.md
py_backend/Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ gcc \
7
+ postgresql-client \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ COPY requirements.txt .
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ COPY . .
14
+
15
+ RUN mkdir -p /app/logs
16
+
17
+ EXPOSE 8080
18
+
19
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
py_backend/app/config.py CHANGED
@@ -1,17 +1,19 @@
1
  from pydantic_settings import BaseSettings
2
 
3
  class Settings(BaseSettings):
4
- DATABASE_URL: str
5
- S3_ENDPOINT: str
6
- S3_ACCESS_KEY: str
7
- S3_SECRET_KEY: str
8
- S3_BUCKET: str
9
-
10
  OPENAI_API_KEY: str = ""
11
  ANTHROPIC_API_KEY: str = ""
12
  GOOGLE_API_KEY: str = ""
13
  HF_API_KEY: str = ""
14
-
 
 
 
15
  class Config:
16
  env_file = ".env"
17
  env_file_encoding = "utf-8-sig"
 
1
  from pydantic_settings import BaseSettings
2
 
3
  class Settings(BaseSettings):
4
+ DATABASE_URL: str = "postgresql://user:pass@host:5432/dbname"
5
+ S3_ENDPOINT: str = "https://your-s3-endpoint.com"
6
+ S3_ACCESS_KEY: str = ""
7
+ S3_SECRET_KEY: str = ""
8
+ S3_BUCKET: str = ""
 
9
  OPENAI_API_KEY: str = ""
10
  ANTHROPIC_API_KEY: str = ""
11
  GOOGLE_API_KEY: str = ""
12
  HF_API_KEY: str = ""
13
+ SPACE_ID: str = ""
14
+ ENVIRONMENT: str = "development"
15
+ BASE_URL: str = "http://localhost:8080"
16
+
17
  class Config:
18
  env_file = ".env"
19
  env_file_encoding = "utf-8-sig"
py_backend/app/main.py CHANGED
@@ -4,15 +4,17 @@ from app.routers import upload, caption, metadata, models
4
  from app.config import settings
5
  from app.routers.images import router as images_router
6
 
7
-
8
-
9
  app = FastAPI(title="PromptAid Vision")
10
 
11
  app.add_middleware(
12
  CORSMiddleware,
13
  allow_origins=[
14
  "http://localhost:3000",
15
- "http://localhost:5173"
 
 
 
 
16
  ],
17
  allow_credentials=True,
18
  allow_methods=["*"],
@@ -27,6 +29,8 @@ app.include_router(images_router, prefix="/api/contribute", tags=["contribute"]
27
 
28
  print("πŸš€ PromptAid Vision API server ready")
29
  print("πŸ“Š Available endpoints: /api/images, /api/captions, /api/metadata, /api/models")
 
 
30
 
31
 
32
 
 
4
  from app.config import settings
5
  from app.routers.images import router as images_router
6
 
 
 
7
  app = FastAPI(title="PromptAid Vision")
8
 
9
  app.add_middleware(
10
  CORSMiddleware,
11
  allow_origins=[
12
  "http://localhost:3000",
13
+ "http://localhost:5173",
14
+ "https://huggingface.co",
15
+ "https://*.hf.space",
16
+ "https://*.hf.space",
17
+ "*"
18
  ],
19
  allow_credentials=True,
20
  allow_methods=["*"],
 
29
 
30
  print("πŸš€ PromptAid Vision API server ready")
31
  print("πŸ“Š Available endpoints: /api/images, /api/captions, /api/metadata, /api/models")
32
+ print(f"🌍 Environment: {settings.ENVIRONMENT}")
33
+ print("πŸ”— CORS enabled for Hugging Face Spaces")
34
 
35
 
36
 
py_backend/app/routers/caption.py CHANGED
@@ -3,12 +3,12 @@ from sqlalchemy.orm import Session
3
  from typing import List
4
  from .. import crud, database, schemas, storage
5
  from ..services.vlm_service import vlm_manager
 
6
 
7
  from ..services.stub_vlm_service import StubVLMService
8
  from ..services.gpt4v_service import GPT4VService
9
  from ..services.gemini_service import GeminiService
10
  from ..services.huggingface_service import LLaVAService, BLIP2Service, InstructBLIPService
11
- from ..config import settings
12
 
13
  stub_service = StubVLMService()
14
  vlm_manager.register_service(stub_service)
@@ -129,6 +129,9 @@ async def create_caption(
129
  except Exception:
130
  url = f"/api/images/{c.image_id}/file"
131
 
 
 
 
132
  img_dict = convert_image_to_dict(c, url)
133
  return schemas.ImageOut(**img_dict)
134
 
@@ -152,6 +155,10 @@ def get_caption(
152
  except Exception:
153
  url = f"/api/images/{caption.image_id}/file"
154
 
 
 
 
 
155
  img_dict = convert_image_to_dict(caption, url)
156
  return schemas.ImageOut(**img_dict)
157
 
@@ -176,6 +183,9 @@ def get_captions_by_image(
176
  except Exception:
177
  url = f"/api/images/{caption.image_id}/file"
178
 
 
 
 
179
  img_dict = convert_image_to_dict(caption, url)
180
  result.append(schemas.ImageOut(**img_dict))
181
 
@@ -232,6 +242,9 @@ def update_caption(
232
  except Exception:
233
  url = f"/api/images/{caption.image_id}/file"
234
 
 
 
 
235
  img_dict = convert_image_to_dict(caption, url)
236
  return schemas.ImageOut(**img_dict)
237
 
 
3
  from typing import List
4
  from .. import crud, database, schemas, storage
5
  from ..services.vlm_service import vlm_manager
6
+ from ..config import settings
7
 
8
  from ..services.stub_vlm_service import StubVLMService
9
  from ..services.gpt4v_service import GPT4VService
10
  from ..services.gemini_service import GeminiService
11
  from ..services.huggingface_service import LLaVAService, BLIP2Service, InstructBLIPService
 
12
 
13
  stub_service = StubVLMService()
14
  vlm_manager.register_service(stub_service)
 
129
  except Exception:
130
  url = f"/api/images/{c.image_id}/file"
131
 
132
+ if url and url.startswith('/'):
133
+ url = f"{settings.BASE_URL}{url}"
134
+
135
  img_dict = convert_image_to_dict(c, url)
136
  return schemas.ImageOut(**img_dict)
137
 
 
155
  except Exception:
156
  url = f"/api/images/{caption.image_id}/file"
157
 
158
+
159
+ if url and url.startswith('/'):
160
+ url = f"{settings.BASE_URL}{url}"
161
+
162
  img_dict = convert_image_to_dict(caption, url)
163
  return schemas.ImageOut(**img_dict)
164
 
 
183
  except Exception:
184
  url = f"/api/images/{caption.image_id}/file"
185
 
186
+ if url and url.startswith('/'):
187
+ url = f"{settings.BASE_URL}{url}"
188
+
189
  img_dict = convert_image_to_dict(caption, url)
190
  result.append(schemas.ImageOut(**img_dict))
191
 
 
242
  except Exception:
243
  url = f"/api/images/{caption.image_id}/file"
244
 
245
+ if url and url.startswith('/'):
246
+ url = f"{settings.BASE_URL}{url}"
247
+
248
  img_dict = convert_image_to_dict(caption, url)
249
  return schemas.ImageOut(**img_dict)
250
 
py_backend/app/routers/upload.py CHANGED
@@ -3,6 +3,7 @@ from pydantic import BaseModel
3
  import io
4
  from sqlalchemy.orm import Session
5
  from .. import crud, schemas, storage, database
 
6
  from typing import List
7
  import boto3
8
  import time
@@ -35,6 +36,9 @@ def convert_image_to_dict(img, image_url):
35
  print(f"Warning: Error processing countries for image {img.image_id}: {e}")
36
  countries_list = []
37
 
 
 
 
38
  img_dict = {
39
  "image_id": img.image_id,
40
  "file_key": img.file_key,
 
3
  import io
4
  from sqlalchemy.orm import Session
5
  from .. import crud, schemas, storage, database
6
+ from ..config import settings
7
  from typing import List
8
  import boto3
9
  import time
 
36
  print(f"Warning: Error processing countries for image {img.image_id}: {e}")
37
  countries_list = []
38
 
39
+ if image_url and image_url.startswith('/'):
40
+ image_url = f"{settings.BASE_URL}{image_url}"
41
+
42
  img_dict = {
43
  "image_id": img.image_id,
44
  "file_key": img.file_key,
py_backend/requirements.txt CHANGED
@@ -19,3 +19,4 @@ httpx
19
  requests
20
  pycountry>=22.3.5
21
  pycountry-convert>=0.7.2
 
 
19
  requests
20
  pycountry>=22.3.5
21
  pycountry-convert>=0.7.2
22
+ python-multipart