Spaces:
Sleeping
Sleeping
Yuvvan Talreja commited on
Commit ·
94bf399
1
Parent(s): c31d9d3
Added app files again
Browse files- .dockerignore +19 -7
- .gitattributes +0 -35
- Dockerfile +6 -8
- README-HF.md +30 -0
- README.md +52 -10
- app.py +0 -15
- app_fastapi.py → fastapi_app.py +32 -47
- requirements.txt +5 -5
- space.yaml +8 -0
- static/script.js +186 -44
- static/styles.css +30 -0
- templates/index.html +9 -3
.dockerignore
CHANGED
|
@@ -1,17 +1,29 @@
|
|
| 1 |
.git
|
| 2 |
.gitignore
|
| 3 |
-
.vscode
|
| 4 |
__pycache__/
|
| 5 |
*.py[cod]
|
| 6 |
*$py.class
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
.env
|
| 8 |
.venv
|
| 9 |
-
env/
|
| 10 |
venv/
|
| 11 |
ENV/
|
| 12 |
-
*.log
|
| 13 |
.DS_Store
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
.ipynb_checkpoints
|
| 17 |
-
.pytest_cache
|
|
|
|
| 1 |
.git
|
| 2 |
.gitignore
|
|
|
|
| 3 |
__pycache__/
|
| 4 |
*.py[cod]
|
| 5 |
*$py.class
|
| 6 |
+
*.so
|
| 7 |
+
.Python
|
| 8 |
+
env/
|
| 9 |
+
build/
|
| 10 |
+
develop-eggs/
|
| 11 |
+
dist/
|
| 12 |
+
downloads/
|
| 13 |
+
eggs/
|
| 14 |
+
.eggs/
|
| 15 |
+
lib/
|
| 16 |
+
lib64/
|
| 17 |
+
parts/
|
| 18 |
+
sdist/
|
| 19 |
+
var/
|
| 20 |
+
*.egg-info/
|
| 21 |
+
.installed.cfg
|
| 22 |
+
*.egg
|
| 23 |
.env
|
| 24 |
.venv
|
|
|
|
| 25 |
venv/
|
| 26 |
ENV/
|
|
|
|
| 27 |
.DS_Store
|
| 28 |
+
.idea/
|
| 29 |
+
.vscode/
|
|
|
|
|
|
.gitattributes
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
CHANGED
|
@@ -6,23 +6,21 @@ WORKDIR /app
|
|
| 6 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 7 |
ffmpeg \
|
| 8 |
build-essential \
|
| 9 |
-
git \
|
| 10 |
&& apt-get clean \
|
| 11 |
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
|
| 13 |
-
# Download NLTK data
|
| 14 |
-
RUN python -m pip install --no-cache-dir nltk
|
| 15 |
-
RUN python -c "import nltk; nltk.download('punkt'); nltk.download('wordnet')"
|
| 16 |
-
|
| 17 |
# Copy requirements first for better caching
|
| 18 |
COPY requirements.txt .
|
| 19 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 20 |
|
|
|
|
|
|
|
|
|
|
| 21 |
# Copy the rest of the application
|
| 22 |
COPY . .
|
| 23 |
|
| 24 |
-
#
|
| 25 |
EXPOSE 7860
|
| 26 |
|
| 27 |
-
# Run using
|
| 28 |
-
CMD ["uvicorn", "
|
|
|
|
| 6 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 7 |
ffmpeg \
|
| 8 |
build-essential \
|
|
|
|
| 9 |
&& apt-get clean \
|
| 10 |
&& rm -rf /var/lib/apt/lists/*
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
# Copy requirements first for better caching
|
| 13 |
COPY requirements.txt .
|
| 14 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 15 |
|
| 16 |
+
# Run NLTK downloads
|
| 17 |
+
RUN python -m nltk.downloader punkt wordnet
|
| 18 |
+
|
| 19 |
# Copy the rest of the application
|
| 20 |
COPY . .
|
| 21 |
|
| 22 |
+
# Expose the port Hugging Face Spaces expects (7860)
|
| 23 |
EXPOSE 7860
|
| 24 |
|
| 25 |
+
# Run using uvicorn
|
| 26 |
+
CMD ["uvicorn", "fastapi_app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README-HF.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Flowify - Audio Transcription & Topic Analysis
|
| 2 |
+
|
| 3 |
+
Flowify is an AI-powered tool that transcribes audio/video content and automatically identifies topic segments. It creates interactive mindmaps to help users visualize the structure and flow of conversations, presentations, or lectures.
|
| 4 |
+
|
| 5 |
+
## Demo
|
| 6 |
+
|
| 7 |
+
Upload a video file, select a transcription model, and click "Transcribe" to start the process. After transcription, click "Analyze Topics" to generate a topic map.
|
| 8 |
+
|
| 9 |
+
## Features
|
| 10 |
+
|
| 11 |
+
- Automatic transcription with OpenAI's Whisper models
|
| 12 |
+
- Topic segmentation with hierarchical structure
|
| 13 |
+
- Interactive mindmap visualization
|
| 14 |
+
- Direct links between transcript timestamps and video playback
|
| 15 |
+
|
| 16 |
+
## API
|
| 17 |
+
|
| 18 |
+
This Spaces app also provides API endpoints:
|
| 19 |
+
|
| 20 |
+
- `POST /transcribe`: Transcribe audio from base64-encoded data
|
| 21 |
+
- `POST /analyze`: Analyze transcript to extract topics
|
| 22 |
+
- `POST /check_model`: Check if a model is available
|
| 23 |
+
|
| 24 |
+
## Technical Details
|
| 25 |
+
|
| 26 |
+
Built with FastAPI and Hugging Face's Transformers library.
|
| 27 |
+
|
| 28 |
+
## License
|
| 29 |
+
|
| 30 |
+
MIT
|
README.md
CHANGED
|
@@ -1,10 +1,52 @@
|
|
| 1 |
-
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Flowify - Audio Transcription & Topic Analysis
|
| 2 |
+
|
| 3 |
+

|
| 4 |
+
|
| 5 |
+
## About
|
| 6 |
+
|
| 7 |
+
Flowify is an AI-powered tool that transcribes audio/video content and automatically identifies topic segments. It creates interactive mindmaps to help users visualize the structure and flow of conversations, presentations, or lectures.
|
| 8 |
+
|
| 9 |
+
## Features
|
| 10 |
+
|
| 11 |
+
- **Audio Transcription**: Powered by OpenAI's Whisper model, providing accurate transcriptions with timestamps
|
| 12 |
+
- **Topic Segmentation**: Automatically identifies topic boundaries and creates hierarchical topic structures
|
| 13 |
+
- **Interactive Visualization**: Generates mindmaps to visualize the structure of the content
|
| 14 |
+
- **Video Integration**: Timestamps in the transcript link directly to the corresponding point in the video
|
| 15 |
+
|
| 16 |
+
## How to Use
|
| 17 |
+
|
| 18 |
+
1. **Upload Video**: Upload your video file (supports MP4, WebM, MOV)
|
| 19 |
+
2. **Choose Model**: Select the transcription model (options range from tiny to medium)
|
| 20 |
+
3. **Transcribe**: Start the transcription process
|
| 21 |
+
4. **Analyze**: Generate the topic segmentation and visualization
|
| 22 |
+
5. **Explore**: Navigate through the interactive mindmap and click on nodes to jump to the relevant sections in your video
|
| 23 |
+
|
| 24 |
+
## Models & Technology
|
| 25 |
+
|
| 26 |
+
- **Transcription**: OpenAI's Whisper (available in various sizes for speed/accuracy tradeoffs)
|
| 27 |
+
- **Topic Segmentation**: Custom algorithm combining TF-IDF, cosine similarity, and hierarchical clustering
|
| 28 |
+
- **Frontend**: HTML/CSS/JS with a responsive design
|
| 29 |
+
- **Backend**: FastAPI for efficient API endpoints
|
| 30 |
+
|
| 31 |
+
## API Endpoints
|
| 32 |
+
|
| 33 |
+
- `POST /transcribe`: Transcribe audio from base64-encoded data
|
| 34 |
+
- `POST /analyze`: Analyze transcript to extract topics
|
| 35 |
+
- `POST /check_model`: Check if a model is available
|
| 36 |
+
|
| 37 |
+
## Running Locally
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
git clone https://huggingface.co/spaces/YOUR_USERNAME/flowify
|
| 41 |
+
cd flowify
|
| 42 |
+
pip install -r requirements.txt
|
| 43 |
+
uvicorn fastapi_app:app --host 0.0.0.0 --port 7860 --reload
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
## License
|
| 47 |
+
|
| 48 |
+
MIT
|
| 49 |
+
|
| 50 |
+
## Credits
|
| 51 |
+
|
| 52 |
+
Built using Hugging Face's transformers library and OpenAI's Whisper model.
|
app.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
| 1 |
-
from fastapi import FastAPI
|
| 2 |
-
import uvicorn
|
| 3 |
-
from app_fastapi import app
|
| 4 |
-
|
| 5 |
-
# This file is used for Hugging Face Spaces deployment
|
| 6 |
-
# Spaces will automatically detect this app.py file and serve the FastAPI app
|
| 7 |
-
|
| 8 |
-
# For local development, you can run:
|
| 9 |
-
# uvicorn app_fastapi:app --host 0.0.0.0 --port 8000
|
| 10 |
-
|
| 11 |
-
# Hugging Face Spaces expects this variable to be named 'app'
|
| 12 |
-
# Already imported from app_fastapi.py
|
| 13 |
-
|
| 14 |
-
if __name__ == "__main__":
|
| 15 |
-
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app_fastapi.py → fastapi_app.py
RENAMED
|
@@ -1,8 +1,7 @@
|
|
| 1 |
-
from fastapi import FastAPI, Request, Response,
|
| 2 |
-
from fastapi.responses import JSONResponse, FileResponse
|
| 3 |
from fastapi.staticfiles import StaticFiles
|
| 4 |
from fastapi.templating import Jinja2Templates
|
| 5 |
-
from pydantic import BaseModel
|
| 6 |
import os
|
| 7 |
import base64
|
| 8 |
import numpy as np
|
|
@@ -12,49 +11,35 @@ from transformers import pipeline
|
|
| 12 |
import threading
|
| 13 |
import json
|
| 14 |
import logging
|
| 15 |
-
from
|
| 16 |
-
import uvicorn
|
| 17 |
from typing import Optional, Dict, Any, List
|
| 18 |
-
from
|
| 19 |
|
| 20 |
-
app = FastAPI()
|
| 21 |
logging.basicConfig(level=logging.INFO)
|
| 22 |
|
| 23 |
-
#
|
| 24 |
-
app.add_middleware(
|
| 25 |
-
CORSMiddleware,
|
| 26 |
-
allow_origins=["*"],
|
| 27 |
-
allow_credentials=True,
|
| 28 |
-
allow_methods=["*"],
|
| 29 |
-
allow_headers=["*"],
|
| 30 |
-
)
|
| 31 |
-
|
| 32 |
-
# Templates for HTML rendering
|
| 33 |
templates = Jinja2Templates(directory="templates")
|
| 34 |
-
|
| 35 |
-
# Serve static files - mount this after creating the app but before routes
|
| 36 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 37 |
|
| 38 |
-
# Models
|
| 39 |
models = {}
|
| 40 |
model_lock = threading.Lock()
|
| 41 |
|
| 42 |
-
|
| 43 |
-
@app.get("/")
|
| 44 |
-
async def index(request: Request):
|
| 45 |
-
return templates.TemplateResponse("index.html", {"request": request})
|
| 46 |
-
|
| 47 |
-
# Defining Pydantic models for request/response data
|
| 48 |
-
class AudioRequest(BaseModel):
|
| 49 |
audio: str
|
| 50 |
model: Optional[str] = "openai/whisper-base"
|
| 51 |
|
| 52 |
-
class
|
| 53 |
transcript: str
|
| 54 |
|
| 55 |
-
class
|
| 56 |
model: str
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
def get_model(model_name):
|
| 59 |
"""Load and cache the model"""
|
| 60 |
with model_lock:
|
|
@@ -123,16 +108,16 @@ def process_audio(audio_data, sample_rate=16000, model_name="openai/whisper-base
|
|
| 123 |
raise
|
| 124 |
|
| 125 |
@app.post("/transcribe")
|
| 126 |
-
async def transcribe(
|
| 127 |
try:
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
audio_bytes = base64.b64decode(request.audio)
|
| 132 |
audio_np = np.frombuffer(audio_bytes, dtype=np.float32)
|
| 133 |
|
|
|
|
|
|
|
| 134 |
start_time = time.time()
|
| 135 |
-
transcript = process_audio(audio_np, model_name=
|
| 136 |
elapsed_time = time.time() - start_time
|
| 137 |
|
| 138 |
logging.info(f"Transcription completed in {elapsed_time:.2f} seconds")
|
|
@@ -143,12 +128,9 @@ async def transcribe(request: AudioRequest):
|
|
| 143 |
raise HTTPException(status_code=500, detail=str(e))
|
| 144 |
|
| 145 |
@app.post("/analyze")
|
| 146 |
-
async def analyze_topics(
|
| 147 |
try:
|
| 148 |
-
|
| 149 |
-
raise HTTPException(status_code=400, detail="No transcript provided")
|
| 150 |
-
|
| 151 |
-
transcript = request.transcript
|
| 152 |
|
| 153 |
segmenter = TopicSegmenter(
|
| 154 |
window_size=2,
|
|
@@ -213,12 +195,9 @@ async def analyze_topics(request: TranscriptRequest):
|
|
| 213 |
raise HTTPException(status_code=500, detail=str(e))
|
| 214 |
|
| 215 |
@app.post("/check_model")
|
| 216 |
-
async def check_model(
|
| 217 |
try:
|
| 218 |
-
|
| 219 |
-
raise HTTPException(status_code=400, detail="No model specified")
|
| 220 |
-
|
| 221 |
-
model_name = request.model
|
| 222 |
logging.info(f"Checking model availability: {model_name}")
|
| 223 |
|
| 224 |
if model_name in models:
|
|
@@ -239,5 +218,11 @@ async def check_model(request: ModelCheckRequest):
|
|
| 239 |
logging.error(f"Model check error: {str(e)}")
|
| 240 |
raise HTTPException(status_code=500, detail=str(e))
|
| 241 |
|
| 242 |
-
|
| 243 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, Request, HTTPException, Response, Body
|
| 2 |
+
from fastapi.responses import JSONResponse, HTMLResponse, FileResponse
|
| 3 |
from fastapi.staticfiles import StaticFiles
|
| 4 |
from fastapi.templating import Jinja2Templates
|
|
|
|
| 5 |
import os
|
| 6 |
import base64
|
| 7 |
import numpy as np
|
|
|
|
| 11 |
import threading
|
| 12 |
import json
|
| 13 |
import logging
|
| 14 |
+
from pydantic import BaseModel
|
|
|
|
| 15 |
from typing import Optional, Dict, Any, List
|
| 16 |
+
from topic_segmenter import TopicSegmenter
|
| 17 |
|
| 18 |
+
app = FastAPI(title="Flowify", description="Audio transcription and topic segmentation API")
|
| 19 |
logging.basicConfig(level=logging.INFO)
|
| 20 |
|
| 21 |
+
# Configure templates and static files
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
templates = Jinja2Templates(directory="templates")
|
|
|
|
|
|
|
| 23 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 24 |
|
| 25 |
+
# Models and locks
|
| 26 |
models = {}
|
| 27 |
model_lock = threading.Lock()
|
| 28 |
|
| 29 |
+
class AudioData(BaseModel):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
audio: str
|
| 31 |
model: Optional[str] = "openai/whisper-base"
|
| 32 |
|
| 33 |
+
class TranscriptData(BaseModel):
|
| 34 |
transcript: str
|
| 35 |
|
| 36 |
+
class ModelCheck(BaseModel):
|
| 37 |
model: str
|
| 38 |
|
| 39 |
+
@app.get("/", response_class=HTMLResponse)
|
| 40 |
+
async def index(request: Request):
|
| 41 |
+
return templates.TemplateResponse("index.html", {"request": request})
|
| 42 |
+
|
| 43 |
def get_model(model_name):
|
| 44 |
"""Load and cache the model"""
|
| 45 |
with model_lock:
|
|
|
|
| 108 |
raise
|
| 109 |
|
| 110 |
@app.post("/transcribe")
|
| 111 |
+
async def transcribe(data: AudioData):
|
| 112 |
try:
|
| 113 |
+
audio_base64 = data.audio
|
| 114 |
+
audio_bytes = base64.b64decode(audio_base64)
|
|
|
|
|
|
|
| 115 |
audio_np = np.frombuffer(audio_bytes, dtype=np.float32)
|
| 116 |
|
| 117 |
+
model_name = data.model
|
| 118 |
+
|
| 119 |
start_time = time.time()
|
| 120 |
+
transcript = process_audio(audio_np, model_name=model_name)
|
| 121 |
elapsed_time = time.time() - start_time
|
| 122 |
|
| 123 |
logging.info(f"Transcription completed in {elapsed_time:.2f} seconds")
|
|
|
|
| 128 |
raise HTTPException(status_code=500, detail=str(e))
|
| 129 |
|
| 130 |
@app.post("/analyze")
|
| 131 |
+
async def analyze_topics(data: TranscriptData):
|
| 132 |
try:
|
| 133 |
+
transcript = data.transcript
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
segmenter = TopicSegmenter(
|
| 136 |
window_size=2,
|
|
|
|
| 195 |
raise HTTPException(status_code=500, detail=str(e))
|
| 196 |
|
| 197 |
@app.post("/check_model")
|
| 198 |
+
async def check_model(data: ModelCheck):
|
| 199 |
try:
|
| 200 |
+
model_name = data.model
|
|
|
|
|
|
|
|
|
|
| 201 |
logging.info(f"Checking model availability: {model_name}")
|
| 202 |
|
| 203 |
if model_name in models:
|
|
|
|
| 218 |
logging.error(f"Model check error: {str(e)}")
|
| 219 |
raise HTTPException(status_code=500, detail=str(e))
|
| 220 |
|
| 221 |
+
# Special route for serving the index.html file
|
| 222 |
+
@app.get("/favicon.ico")
|
| 223 |
+
async def favicon():
|
| 224 |
+
return FileResponse("static/favicon.ico", media_type="image/x-icon")
|
| 225 |
+
|
| 226 |
+
if __name__ == "__main__":
|
| 227 |
+
import uvicorn
|
| 228 |
+
uvicorn.run("fastapi_app:app", host="0.0.0.0", port=7860, reload=True)
|
requirements.txt
CHANGED
|
@@ -1,5 +1,8 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
| 3 |
numpy>=1.19.0
|
| 4 |
scikit-learn>=1.0.0
|
| 5 |
nltk>=3.6.0
|
|
@@ -10,6 +13,3 @@ spacy>=3.0.0
|
|
| 10 |
librosa>=0.8.0
|
| 11 |
soundfile>=0.10.0
|
| 12 |
thinc>=8.0.0
|
| 13 |
-
jinja2>=3.0.0
|
| 14 |
-
python-multipart>=0.0.5
|
| 15 |
-
pydantic>=1.8.2
|
|
|
|
| 1 |
+
flask>=2.0.0
|
| 2 |
+
gunicorn>=20.0.0
|
| 3 |
+
fastapi>=0.95.0
|
| 4 |
+
uvicorn>=0.21.0
|
| 5 |
+
python-multipart>=0.0.6
|
| 6 |
numpy>=1.19.0
|
| 7 |
scikit-learn>=1.0.0
|
| 8 |
nltk>=3.6.0
|
|
|
|
| 13 |
librosa>=0.8.0
|
| 14 |
soundfile>=0.10.0
|
| 15 |
thinc>=8.0.0
|
|
|
|
|
|
|
|
|
space.yaml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
title: Flowify - Audio Transcription & Topic Analysis
|
| 2 |
+
emoji: 🔄
|
| 3 |
+
colorFrom: blue
|
| 4 |
+
colorTo: green
|
| 5 |
+
sdk: docker
|
| 6 |
+
app_port: 7860
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
static/script.js
CHANGED
|
@@ -1,19 +1,51 @@
|
|
| 1 |
import { pipeline, env } from 'https://unpkg.com/@xenova/transformers@2.16.0/dist/transformers.min.js';
|
| 2 |
|
| 3 |
-
|
| 4 |
env.allowLocalModels = false;
|
| 5 |
env.useBrowserCache = true;
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
const videoPlayer =
|
| 9 |
-
const fileInput =
|
| 10 |
-
const transcribeButton =
|
| 11 |
-
const analyzeButton =
|
| 12 |
-
const transcriptionDiv =
|
| 13 |
-
const topicResultsDiv =
|
| 14 |
-
const statusDiv =
|
| 15 |
-
const modelSelector =
|
| 16 |
-
const modelStatus =
|
| 17 |
|
| 18 |
let whisperPipeline = null;
|
| 19 |
let transcriptText = '';
|
|
@@ -253,21 +285,53 @@ analyzeTopics = async function() {
|
|
| 253 |
}
|
| 254 |
};
|
| 255 |
|
|
|
|
| 256 |
fileInput.addEventListener('change', (e) => {
|
|
|
|
| 257 |
const file = e.target.files[0];
|
| 258 |
if (file) {
|
| 259 |
-
|
| 260 |
-
videoPlayer.src = videoURL;
|
| 261 |
-
transcribeButton.disabled = false;
|
| 262 |
-
analyzeButton.disabled = true;
|
| 263 |
-
transcriptionDiv.textContent = '';
|
| 264 |
-
topicResultsDiv.textContent = '';
|
| 265 |
-
transcriptText = '';
|
| 266 |
-
statusDiv.textContent = 'Video loaded. Ready to transcribe.';
|
| 267 |
|
| 268 |
-
|
| 269 |
-
if (
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
}
|
| 272 |
}
|
| 273 |
});
|
|
@@ -473,20 +537,61 @@ modelSelector.addEventListener('change', async (e) => {
|
|
| 473 |
|
| 474 |
// Initialize with the selected model when the page loads
|
| 475 |
window.addEventListener('DOMContentLoaded', () => {
|
| 476 |
-
// Initialize all UI components
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
});
|
| 491 |
|
| 492 |
// Theme toggle functionality
|
|
@@ -557,13 +662,30 @@ function initGallery() {
|
|
| 557 |
const galleryClose = document.getElementById('galleryClose');
|
| 558 |
const galleryContent = document.getElementById('galleryContent');
|
| 559 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
// Add "Recent Mindmaps" option to FAB
|
| 561 |
const fabContainer = document.getElementById('fabContainer');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
const recentMindmapsBtn = document.createElement('button');
|
| 563 |
recentMindmapsBtn.className = 'fab-option';
|
| 564 |
recentMindmapsBtn.setAttribute('title', 'Recent Mindmaps');
|
| 565 |
recentMindmapsBtn.innerHTML = '<i class="fas fa-history"></i>';
|
| 566 |
-
|
| 567 |
|
| 568 |
// Show gallery
|
| 569 |
recentMindmapsBtn.addEventListener('click', () => {
|
|
@@ -2047,8 +2169,12 @@ function initCustomVideoPlayer() {
|
|
| 2047 |
function initUploadParticles() {
|
| 2048 |
const uploadContainer = document.querySelector('.upload-container');
|
| 2049 |
const particles = document.querySelector('.particles');
|
|
|
|
| 2050 |
|
| 2051 |
-
if (!uploadContainer || !particles)
|
|
|
|
|
|
|
|
|
|
| 2052 |
|
| 2053 |
// Create particles
|
| 2054 |
for (let i = 0; i < 15; i++) {
|
|
@@ -2074,32 +2200,48 @@ function initUploadParticles() {
|
|
| 2074 |
particles.appendChild(particle);
|
| 2075 |
}
|
| 2076 |
|
| 2077 |
-
//
|
| 2078 |
-
|
| 2079 |
-
|
| 2080 |
-
|
|
|
|
| 2081 |
fileInput.click();
|
| 2082 |
});
|
| 2083 |
|
| 2084 |
-
// Drag and drop functionality with improved feedback
|
| 2085 |
uploadContainer.addEventListener('dragover', (e) => {
|
| 2086 |
e.preventDefault();
|
|
|
|
| 2087 |
uploadContainer.classList.add('active');
|
| 2088 |
});
|
| 2089 |
|
| 2090 |
-
uploadContainer.addEventListener('dragleave', () => {
|
|
|
|
|
|
|
| 2091 |
uploadContainer.classList.remove('active');
|
| 2092 |
});
|
| 2093 |
|
| 2094 |
uploadContainer.addEventListener('drop', (e) => {
|
|
|
|
| 2095 |
e.preventDefault();
|
|
|
|
| 2096 |
uploadContainer.classList.remove('active');
|
| 2097 |
|
| 2098 |
if (e.dataTransfer.files.length) {
|
|
|
|
| 2099 |
fileInput.files = e.dataTransfer.files;
|
| 2100 |
-
|
|
|
|
|
|
|
|
|
|
| 2101 |
}
|
| 2102 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2103 |
}
|
| 2104 |
|
| 2105 |
// Progress Steps Visualization
|
|
|
|
| 1 |
import { pipeline, env } from 'https://unpkg.com/@xenova/transformers@2.16.0/dist/transformers.min.js';
|
| 2 |
|
|
|
|
| 3 |
env.allowLocalModels = false;
|
| 4 |
env.useBrowserCache = true;
|
| 5 |
|
| 6 |
+
// Utility function to check if elements exist and log errors if they don't
|
| 7 |
+
function getElements(selectors) {
|
| 8 |
+
const elements = {};
|
| 9 |
+
let allFound = true;
|
| 10 |
+
|
| 11 |
+
for (const [key, selector] of Object.entries(selectors)) {
|
| 12 |
+
const element = selector.startsWith('.') ?
|
| 13 |
+
document.querySelector(selector) :
|
| 14 |
+
document.getElementById(selector);
|
| 15 |
+
|
| 16 |
+
if (!element) {
|
| 17 |
+
console.warn(`Element not found: ${selector}`);
|
| 18 |
+
allFound = false;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
elements[key] = element;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
return { elements, allFound };
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
// Global variables
|
| 28 |
+
const { elements } = getElements({
|
| 29 |
+
videoPlayer: 'videoPlayer',
|
| 30 |
+
fileInput: 'fileInput',
|
| 31 |
+
transcribeButton: 'transcribeButton',
|
| 32 |
+
analyzeButton: 'analyzeButton',
|
| 33 |
+
transcriptionDiv: 'transcription',
|
| 34 |
+
topicResultsDiv: 'topicResults',
|
| 35 |
+
statusDiv: 'status',
|
| 36 |
+
modelSelector: 'modelSelector',
|
| 37 |
+
modelStatus: 'modelStatus'
|
| 38 |
+
});
|
| 39 |
|
| 40 |
+
const videoPlayer = elements.videoPlayer;
|
| 41 |
+
const fileInput = elements.fileInput;
|
| 42 |
+
const transcribeButton = elements.transcribeButton;
|
| 43 |
+
const analyzeButton = elements.analyzeButton;
|
| 44 |
+
const transcriptionDiv = elements.transcriptionDiv;
|
| 45 |
+
const topicResultsDiv = elements.topicResultsDiv;
|
| 46 |
+
const statusDiv = elements.statusDiv;
|
| 47 |
+
const modelSelector = elements.modelSelector;
|
| 48 |
+
const modelStatus = elements.modelStatus;
|
| 49 |
|
| 50 |
let whisperPipeline = null;
|
| 51 |
let transcriptText = '';
|
|
|
|
| 285 |
}
|
| 286 |
};
|
| 287 |
|
| 288 |
+
// Fixed file input change handler - properly handle video file selection
|
| 289 |
fileInput.addEventListener('change', (e) => {
|
| 290 |
+
console.log("File input change event fired", e.target.files);
|
| 291 |
const file = e.target.files[0];
|
| 292 |
if (file) {
|
| 293 |
+
console.log("Processing selected file:", file.name, file.type);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
+
// Only process video files
|
| 296 |
+
if (!file.type.startsWith('video/')) {
|
| 297 |
+
alert('Please select a video file');
|
| 298 |
+
return;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
try {
|
| 302 |
+
const videoURL = URL.createObjectURL(file);
|
| 303 |
+
console.log("Created video URL:", videoURL);
|
| 304 |
+
|
| 305 |
+
videoPlayer.src = videoURL;
|
| 306 |
+
videoPlayer.load(); // Force reload
|
| 307 |
+
|
| 308 |
+
// Update UI
|
| 309 |
+
transcribeButton.disabled = false;
|
| 310 |
+
analyzeButton.disabled = true;
|
| 311 |
+
transcriptionDiv.innerHTML = '';
|
| 312 |
+
topicResultsDiv.innerHTML = '';
|
| 313 |
+
transcriptText = '';
|
| 314 |
+
statusDiv.textContent = 'Video loaded. Ready to transcribe.';
|
| 315 |
+
|
| 316 |
+
// Reset progress
|
| 317 |
+
const progressBarFill = document.querySelector('.progress-bar-fill');
|
| 318 |
+
if (progressBarFill) {
|
| 319 |
+
progressBarFill.style.width = '0%';
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
// Set active step
|
| 323 |
+
if (typeof window.setActiveStep === 'function') {
|
| 324 |
+
window.setActiveStep('upload');
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
// Show the video element
|
| 328 |
+
videoPlayer.style.display = 'block';
|
| 329 |
+
|
| 330 |
+
// Add a success message
|
| 331 |
+
console.log("Video file successfully loaded");
|
| 332 |
+
} catch (error) {
|
| 333 |
+
console.error("Error processing video file:", error);
|
| 334 |
+
alert('Error loading video: ' + error.message);
|
| 335 |
}
|
| 336 |
}
|
| 337 |
});
|
|
|
|
| 537 |
|
| 538 |
// Initialize with the selected model when the page loads
|
| 539 |
window.addEventListener('DOMContentLoaded', () => {
|
| 540 |
+
// Initialize all UI components with error handling for each
|
| 541 |
+
try {
|
| 542 |
+
initThemeToggle();
|
| 543 |
+
} catch (error) {
|
| 544 |
+
console.error('Failed to initialize theme toggle:', error);
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
try {
|
| 548 |
+
initFAB();
|
| 549 |
+
} catch (error) {
|
| 550 |
+
console.error('Failed to initialize FAB:', error);
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
try {
|
| 554 |
+
initGallery();
|
| 555 |
+
} catch (error) {
|
| 556 |
+
console.error('Failed to initialize gallery:', error);
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
try {
|
| 560 |
+
initCustomVideoPlayer();
|
| 561 |
+
} catch (error) {
|
| 562 |
+
console.error('Failed to initialize custom video player:', error);
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
try {
|
| 566 |
+
initUploadParticles();
|
| 567 |
+
} catch (error) {
|
| 568 |
+
console.error('Failed to initialize upload particles:', error);
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
try {
|
| 572 |
+
initProgressSteps();
|
| 573 |
+
} catch (error) {
|
| 574 |
+
console.error('Failed to initialize progress steps:', error);
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
// Initialize mindmap system
|
| 578 |
+
try {
|
| 579 |
+
mindmap = new TopicMindmap();
|
| 580 |
+
} catch (error) {
|
| 581 |
+
console.error('Failed to initialize mindmap:', error);
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
// Check for model selector and initialize whisper if available
|
| 585 |
+
const modelSelector = document.getElementById('modelSelector');
|
| 586 |
+
if (modelSelector) {
|
| 587 |
+
initWhisper(modelSelector.value).catch(error => {
|
| 588 |
+
console.error('Failed to initialize Whisper:', error);
|
| 589 |
+
const statusDiv = document.getElementById('status');
|
| 590 |
+
if (statusDiv) {
|
| 591 |
+
statusDiv.textContent = 'Failed to initialize the transcription model';
|
| 592 |
+
}
|
| 593 |
+
});
|
| 594 |
+
}
|
| 595 |
});
|
| 596 |
|
| 597 |
// Theme toggle functionality
|
|
|
|
| 662 |
const galleryClose = document.getElementById('galleryClose');
|
| 663 |
const galleryContent = document.getElementById('galleryContent');
|
| 664 |
|
| 665 |
+
// Exit early if required elements don't exist
|
| 666 |
+
if (!galleryContainer || !galleryClose || !galleryContent) {
|
| 667 |
+
console.warn('Gallery elements not found, skipping gallery initialization');
|
| 668 |
+
return;
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
// Add "Recent Mindmaps" option to FAB
|
| 672 |
const fabContainer = document.getElementById('fabContainer');
|
| 673 |
+
if (!fabContainer) {
|
| 674 |
+
console.warn('FAB container not found, skipping recent mindmaps button');
|
| 675 |
+
return;
|
| 676 |
+
}
|
| 677 |
+
|
| 678 |
+
const fabOptions = fabContainer.querySelector('.fab-options');
|
| 679 |
+
if (!fabOptions) {
|
| 680 |
+
console.warn('FAB options not found, skipping recent mindmaps button');
|
| 681 |
+
return;
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
const recentMindmapsBtn = document.createElement('button');
|
| 685 |
recentMindmapsBtn.className = 'fab-option';
|
| 686 |
recentMindmapsBtn.setAttribute('title', 'Recent Mindmaps');
|
| 687 |
recentMindmapsBtn.innerHTML = '<i class="fas fa-history"></i>';
|
| 688 |
+
fabOptions.appendChild(recentMindmapsBtn);
|
| 689 |
|
| 690 |
// Show gallery
|
| 691 |
recentMindmapsBtn.addEventListener('click', () => {
|
|
|
|
| 2169 |
function initUploadParticles() {
|
| 2170 |
const uploadContainer = document.querySelector('.upload-container');
|
| 2171 |
const particles = document.querySelector('.particles');
|
| 2172 |
+
const fileInput = document.getElementById('fileInput');
|
| 2173 |
|
| 2174 |
+
if (!uploadContainer || !particles || !fileInput) {
|
| 2175 |
+
console.warn('Upload elements not found, skipping upload particles initialization');
|
| 2176 |
+
return;
|
| 2177 |
+
}
|
| 2178 |
|
| 2179 |
// Create particles
|
| 2180 |
for (let i = 0; i < 15; i++) {
|
|
|
|
| 2200 |
particles.appendChild(particle);
|
| 2201 |
}
|
| 2202 |
|
| 2203 |
+
// Add click event to trigger file input - FIXED VERSION
|
| 2204 |
+
uploadContainer.addEventListener('click', (e) => {
|
| 2205 |
+
e.preventDefault(); // Prevent default browser behavior
|
| 2206 |
+
e.stopPropagation(); // Stop event from bubbling up
|
| 2207 |
+
console.log("Upload container clicked");
|
| 2208 |
fileInput.click();
|
| 2209 |
});
|
| 2210 |
|
| 2211 |
+
// Drag and drop functionality with improved feedback - FIXED VERSION
|
| 2212 |
uploadContainer.addEventListener('dragover', (e) => {
|
| 2213 |
e.preventDefault();
|
| 2214 |
+
e.stopPropagation();
|
| 2215 |
uploadContainer.classList.add('active');
|
| 2216 |
});
|
| 2217 |
|
| 2218 |
+
uploadContainer.addEventListener('dragleave', (e) => {
|
| 2219 |
+
e.preventDefault();
|
| 2220 |
+
e.stopPropagation();
|
| 2221 |
uploadContainer.classList.remove('active');
|
| 2222 |
});
|
| 2223 |
|
| 2224 |
uploadContainer.addEventListener('drop', (e) => {
|
| 2225 |
+
console.log("File dropped");
|
| 2226 |
e.preventDefault();
|
| 2227 |
+
e.stopPropagation();
|
| 2228 |
uploadContainer.classList.remove('active');
|
| 2229 |
|
| 2230 |
if (e.dataTransfer.files.length) {
|
| 2231 |
+
console.log("Processing dropped file");
|
| 2232 |
fileInput.files = e.dataTransfer.files;
|
| 2233 |
+
|
| 2234 |
+
// Create and dispatch a proper change event
|
| 2235 |
+
const event = new Event('change', { bubbles: true });
|
| 2236 |
+
fileInput.dispatchEvent(event);
|
| 2237 |
}
|
| 2238 |
});
|
| 2239 |
+
|
| 2240 |
+
// Add logging to the file input change event for debugging
|
| 2241 |
+
fileInput.addEventListener('change', (e) => {
|
| 2242 |
+
console.log("File input change event fired", e.target.files);
|
| 2243 |
+
// The rest of the handler continues below...
|
| 2244 |
+
});
|
| 2245 |
}
|
| 2246 |
|
| 2247 |
// Progress Steps Visualization
|
static/styles.css
CHANGED
|
@@ -1886,4 +1886,34 @@ jmnode.node-teal:hover, jmnode.node-gray:hover {
|
|
| 1886 |
|
| 1887 |
.transcription-content p:hover {
|
| 1888 |
background-color: rgba(52, 152, 219, 0.1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1889 |
}
|
|
|
|
| 1886 |
|
| 1887 |
.transcription-content p:hover {
|
| 1888 |
background-color: rgba(52, 152, 219, 0.1);
|
| 1889 |
+
}
|
| 1890 |
+
|
| 1891 |
+
/* Hugging Face Badge Styles */
|
| 1892 |
+
.huggingface-badge {
|
| 1893 |
+
display: flex;
|
| 1894 |
+
align-items: center;
|
| 1895 |
+
gap: 0.5rem;
|
| 1896 |
+
margin-left: auto;
|
| 1897 |
+
padding: 0.5rem;
|
| 1898 |
+
border-radius: var(--radius-md);
|
| 1899 |
+
background-color: var(--background-color);
|
| 1900 |
+
transition: all 0.3s ease;
|
| 1901 |
+
}
|
| 1902 |
+
|
| 1903 |
+
.huggingface-badge span {
|
| 1904 |
+
font-size: 0.85rem;
|
| 1905 |
+
color: var(--text-secondary);
|
| 1906 |
+
}
|
| 1907 |
+
|
| 1908 |
+
.huggingface-badge img {
|
| 1909 |
+
height: 24px;
|
| 1910 |
+
}
|
| 1911 |
+
|
| 1912 |
+
.huggingface-badge:hover {
|
| 1913 |
+
transform: translateY(-2px);
|
| 1914 |
+
box-shadow: var(--shadow-sm);
|
| 1915 |
+
}
|
| 1916 |
+
|
| 1917 |
+
[data-theme="dark"] .huggingface-badge {
|
| 1918 |
+
background-color: var(--surface-color);
|
| 1919 |
}
|
templates/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>Flowify</title>
|
| 7 |
|
| 8 |
-
<link rel="stylesheet" href="{{ url_for('static',
|
| 9 |
<link type="text/css" rel="stylesheet" href="https://unpkg.com/jsmind@0.8.6/style/jsmind.css" />
|
| 10 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
| 11 |
</head>
|
|
@@ -16,6 +16,12 @@
|
|
| 16 |
<i class="fas fa-project-diagram logo-icon"></i>
|
| 17 |
<h1>Flowify</h1>
|
| 18 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</header>
|
| 20 |
|
| 21 |
<!-- Theme Toggle Button -->
|
|
@@ -32,7 +38,7 @@
|
|
| 32 |
<div class="upload-panel card">
|
| 33 |
<div class="card-body">
|
| 34 |
<div class="upload-section">
|
| 35 |
-
<div class="upload-container" id="dropZone">
|
| 36 |
<div class="particles">
|
| 37 |
<!-- Particles will be added dynamically via JS -->
|
| 38 |
</div>
|
|
@@ -310,6 +316,6 @@
|
|
| 310 |
<script src="https://cdn.jsdelivr.net/npm/@xenova/transformers@2.16.0"></script>
|
| 311 |
<script type="text/javascript" src="https://unpkg.com/jsmind@0.8.6/es6/jsmind.js"></script>
|
| 312 |
<script>window.jsMind = jsMind;</script>
|
| 313 |
-
<script type="module" src="{{ url_for('static',
|
| 314 |
</body>
|
| 315 |
</html>
|
|
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>Flowify</title>
|
| 7 |
|
| 8 |
+
<link rel="stylesheet" href="{{ url_for('static', path='styles.css') }}">
|
| 9 |
<link type="text/css" rel="stylesheet" href="https://unpkg.com/jsmind@0.8.6/style/jsmind.css" />
|
| 10 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
| 11 |
</head>
|
|
|
|
| 16 |
<i class="fas fa-project-diagram logo-icon"></i>
|
| 17 |
<h1>Flowify</h1>
|
| 18 |
</div>
|
| 19 |
+
<div class="huggingface-badge">
|
| 20 |
+
<span>Powered by</span>
|
| 21 |
+
<a href="https://huggingface.co/spaces" target="_blank">
|
| 22 |
+
<img src="https://huggingface.co/datasets/huggingface/badges/raw/main/powered-by-huggingface-light.svg" alt="Powered by Hugging Face" />
|
| 23 |
+
</a>
|
| 24 |
+
</div>
|
| 25 |
</header>
|
| 26 |
|
| 27 |
<!-- Theme Toggle Button -->
|
|
|
|
| 38 |
<div class="upload-panel card">
|
| 39 |
<div class="card-body">
|
| 40 |
<div class="upload-section">
|
| 41 |
+
<div class="upload-container" id="dropZone" onclick="event.preventDefault();">
|
| 42 |
<div class="particles">
|
| 43 |
<!-- Particles will be added dynamically via JS -->
|
| 44 |
</div>
|
|
|
|
| 316 |
<script src="https://cdn.jsdelivr.net/npm/@xenova/transformers@2.16.0"></script>
|
| 317 |
<script type="text/javascript" src="https://unpkg.com/jsmind@0.8.6/es6/jsmind.js"></script>
|
| 318 |
<script>window.jsMind = jsMind;</script>
|
| 319 |
+
<script type="module" src="{{ url_for('static', path='script.js') }}"></script>
|
| 320 |
</body>
|
| 321 |
</html>
|