Yuvvan Talreja commited on
Commit
94bf399
·
1 Parent(s): c31d9d3

Added app files again

Browse files
.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
- *.swp
15
- *.swo
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
- # Port for Hugging Face Spaces
25
  EXPOSE 7860
26
 
27
- # Run using Uvicorn
28
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
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
- title: Flowify Api
3
- emoji: 👁
4
- colorFrom: red
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Flowify - Audio Transcription & Topic Analysis
2
+
3
+ ![Flowify Demo](https://i.imgur.com/6BEZ7NR.png)
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, HTTPException
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 topic_segmenter import TopicSegmenter
16
- import uvicorn
17
  from typing import Optional, Dict, Any, List
18
- from fastapi.middleware.cors import CORSMiddleware
19
 
20
- app = FastAPI()
21
  logging.basicConfig(level=logging.INFO)
22
 
23
- # Add CORS middleware
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 cache
39
  models = {}
40
  model_lock = threading.Lock()
41
 
42
- # Route to serve the main page
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 TranscriptRequest(BaseModel):
53
  transcript: str
54
 
55
- class ModelCheckRequest(BaseModel):
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(request: AudioRequest):
127
  try:
128
- if not request.audio:
129
- raise HTTPException(status_code=400, detail="No audio data provided")
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=request.model)
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(request: TranscriptRequest):
147
  try:
148
- if not request.transcript:
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(request: ModelCheckRequest):
217
  try:
218
- if not request.model:
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
- if __name__ == '__main__':
243
- uvicorn.run("app_fastapi:app", host="0.0.0.0", port=5000, reload=True)
 
 
 
 
 
 
 
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
- fastapi>=0.68.0
2
- uvicorn>=0.15.0
 
 
 
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 = document.getElementById('videoPlayer');
9
- const fileInput = document.getElementById('fileInput');
10
- const transcribeButton = document.getElementById('transcribeButton');
11
- const analyzeButton = document.getElementById('analyzeButton');
12
- const transcriptionDiv = document.getElementById('transcription');
13
- const topicResultsDiv = document.getElementById('topicResults');
14
- const statusDiv = document.getElementById('status');
15
- const modelSelector = document.getElementById('modelSelector');
16
- const modelStatus = document.getElementById('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
- const videoURL = URL.createObjectURL(file);
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
- const progressBarFill = document.querySelector('.progress-bar-fill');
269
- if (progressBarFill) {
270
- progressBarFill.style.width = '0%';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- initThemeToggle();
478
- initFAB();
479
- initGallery();
480
- initCustomVideoPlayer();
481
- initUploadParticles();
482
- initProgressSteps();
483
-
484
- // Original initialization
485
- mindmap = new TopicMindmap();
486
- initWhisper(modelSelector.value).catch(error => {
487
- console.error('Failed to initialize Whisper:', error);
488
- statusDiv.textContent = 'Failed to initialize the transcription model';
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
- fabContainer.querySelector('.fab-options').appendChild(recentMindmapsBtn);
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) return;
 
 
 
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
- // Show upload progress when uploading
2078
- const fileInput = document.getElementById('fileInput');
2079
-
2080
- uploadContainer.addEventListener('click', () => {
 
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
- fileInput.dispatchEvent(new Event('change'));
 
 
 
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', filename='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,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', filename='script.js') }}"></script>
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>