Claude commited on
Commit
3410c2b
·
unverified ·
1 Parent(s): f7156b4

feat: Improve UI/UX with step-by-step layout and better docs

Browse files

- Add step-by-step instructions in Analyze tab
- Improve login button placement with status display
- Add placeholder text and info tooltips for inputs
- Add chatbot height and better layout in Chat tab
- Update README with features and usage instructions
- Update E2E tests for new UI structure

Files changed (3) hide show
  1. README.md +25 -1
  2. app.py +44 -19
  3. tests/test_e2e.py +27 -16
README.md CHANGED
@@ -14,4 +14,28 @@ hf_oauth_expiration_minutes: 480
14
 
15
  # Video Analyzer
16
 
17
- A Gradio application.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # Video Analyzer
16
 
17
+ A Gradio application for downloading, transcribing, and analyzing YouTube videos using AI.
18
+
19
+ ## Features
20
+
21
+ - **YouTube Video Download**: Download videos or entire playlists
22
+ - **Speech-to-Text**: Transcribe audio using OpenAI Whisper
23
+ - **Visual Analysis**: Extract and describe key frames using BLIP vision model
24
+ - **Knowledge Base**: Store analyzed content in a vector database (ChromaDB)
25
+ - **RAG Chatbot**: Ask questions about your analyzed videos using Qwen2.5-72B
26
+
27
+ ## How to Use
28
+
29
+ 1. **Sign in** with your HuggingFace account (required)
30
+ 2. **Analyze Videos** tab:
31
+ - Paste a YouTube video or playlist URL
32
+ - Enable/disable frame analysis
33
+ - Click "Analyze Video" to process
34
+ 3. **Chat with Videos** tab:
35
+ - Ask questions about videos you've analyzed
36
+ - The AI will search the knowledge base and provide answers
37
+
38
+ ## Requirements
39
+
40
+ - HuggingFace account for OAuth authentication
41
+ - FFmpeg (for audio extraction)
app.py CHANGED
@@ -408,29 +408,36 @@ def get_knowledge_stats() -> str:
408
 
409
  def create_demo() -> gr.Blocks:
410
  """Create and configure the Gradio demo application."""
411
- with gr.Blocks() as demo:
412
  gr.Markdown("# Video Analyzer")
413
  gr.Markdown("Download, transcribe, analyze, and chat with YouTube videos using AI")
414
 
415
- gr.LoginButton()
416
- m1 = gr.Markdown()
417
- m2 = gr.Markdown()
 
 
 
418
 
419
  gr.Markdown("---")
420
 
421
  with gr.Tabs():
422
  with gr.TabItem("Analyze Videos"):
423
- with gr.Row():
424
- url_input = gr.Textbox(
425
- label="YouTube URL",
426
- placeholder="Enter a YouTube video or playlist URL",
427
- scale=4,
428
- )
429
 
 
 
 
 
 
 
 
430
  with gr.Row():
431
  analyze_frames = gr.Checkbox(
432
  label="Analyze video frames (visual context)",
433
  value=True,
 
434
  )
435
  num_frames = gr.Slider(
436
  label="Number of frames to analyze",
@@ -438,10 +445,17 @@ def create_demo() -> gr.Blocks:
438
  maximum=10,
439
  value=5,
440
  step=1,
 
441
  )
442
 
443
- submit_btn = gr.Button("Analyze Video", variant="primary")
444
- output = gr.Markdown(label="Analysis")
 
 
 
 
 
 
445
 
446
  submit_btn.click(
447
  fn=process_youtube,
@@ -451,12 +465,23 @@ def create_demo() -> gr.Blocks:
451
 
452
  with gr.TabItem("Chat with Videos"):
453
  kb_stats = gr.Markdown()
454
- chatbot = gr.Chatbot(label="Video Chat")
455
- chat_input = gr.Textbox(
456
- label="Ask a question about your videos",
457
- placeholder="What did the video say about...?",
 
 
 
458
  )
459
- chat_btn = gr.Button("Ask", variant="primary")
 
 
 
 
 
 
 
 
460
 
461
  def respond(
462
  message: str,
@@ -484,8 +509,8 @@ def create_demo() -> gr.Blocks:
484
  # Update stats on tab load
485
  demo.load(get_knowledge_stats, outputs=kb_stats)
486
 
487
- demo.load(hello, inputs=None, outputs=m1)
488
- demo.load(list_organizations, inputs=None, outputs=m2)
489
 
490
  return demo
491
 
 
408
 
409
  def create_demo() -> gr.Blocks:
410
  """Create and configure the Gradio demo application."""
411
+ with gr.Blocks(title="Video Analyzer") as demo:
412
  gr.Markdown("# Video Analyzer")
413
  gr.Markdown("Download, transcribe, analyze, and chat with YouTube videos using AI")
414
 
415
+ with gr.Row():
416
+ with gr.Column(scale=3):
417
+ login_status = gr.Markdown()
418
+ org_status = gr.Markdown()
419
+ with gr.Column(scale=1):
420
+ gr.LoginButton()
421
 
422
  gr.Markdown("---")
423
 
424
  with gr.Tabs():
425
  with gr.TabItem("Analyze Videos"):
426
+ gr.Markdown("### Step 1: Enter a YouTube URL")
427
+ gr.Markdown("*Paste a video or playlist URL below*")
 
 
 
 
428
 
429
+ url_input = gr.Textbox(
430
+ label="YouTube URL",
431
+ placeholder="https://www.youtube.com/watch?v=...",
432
+ lines=1,
433
+ )
434
+
435
+ gr.Markdown("### Step 2: Configure Analysis Options")
436
  with gr.Row():
437
  analyze_frames = gr.Checkbox(
438
  label="Analyze video frames (visual context)",
439
  value=True,
440
+ info="Extract and describe key frames from the video",
441
  )
442
  num_frames = gr.Slider(
443
  label="Number of frames to analyze",
 
445
  maximum=10,
446
  value=5,
447
  step=1,
448
+ info="More frames = better context but slower",
449
  )
450
 
451
+ gr.Markdown("### Step 3: Analyze")
452
+ submit_btn = gr.Button("Analyze Video", variant="primary", size="lg")
453
+
454
+ gr.Markdown("### Results")
455
+ output = gr.Markdown(
456
+ value="*Results will appear here after analysis*",
457
+ label="Analysis Output",
458
+ )
459
 
460
  submit_btn.click(
461
  fn=process_youtube,
 
465
 
466
  with gr.TabItem("Chat with Videos"):
467
  kb_stats = gr.Markdown()
468
+
469
+ gr.Markdown("*Ask questions about videos you've analyzed*")
470
+
471
+ chatbot = gr.Chatbot(
472
+ label="Video Chat",
473
+ height=400,
474
+ placeholder="Your conversation will appear here...",
475
  )
476
+
477
+ with gr.Row():
478
+ chat_input = gr.Textbox(
479
+ label="Your Question",
480
+ placeholder="What topics were discussed in the videos?",
481
+ scale=4,
482
+ lines=1,
483
+ )
484
+ chat_btn = gr.Button("Ask", variant="primary", scale=1)
485
 
486
  def respond(
487
  message: str,
 
509
  # Update stats on tab load
510
  demo.load(get_knowledge_stats, outputs=kb_stats)
511
 
512
+ demo.load(hello, inputs=None, outputs=login_status)
513
+ demo.load(list_organizations, inputs=None, outputs=org_status)
514
 
515
  return demo
516
 
tests/test_e2e.py CHANGED
@@ -40,6 +40,13 @@ class TestVideoAnalyzerUI:
40
  # Check title is visible
41
  expect(page.locator("text=Video Analyzer")).to_be_visible()
42
 
 
 
 
 
 
 
 
43
  def test_login_button_visible(self, page: Page, app_url: str):
44
  """Test that the login button is visible."""
45
  page.goto(app_url)
@@ -68,8 +75,8 @@ class TestVideoAnalyzerUI:
68
  """Test that the YouTube URL input field exists."""
69
  page.goto(app_url)
70
 
71
- # Check for URL input
72
- url_input = page.locator("textarea[placeholder*='YouTube']")
73
  expect(url_input).to_be_visible()
74
 
75
  def test_analyze_button_exists(self, page: Page, app_url: str):
@@ -84,7 +91,7 @@ class TestVideoAnalyzerUI:
84
  """Test that the frame analysis checkbox exists."""
85
  page.goto(app_url)
86
 
87
- # Check for checkbox
88
  checkbox = page.locator("text=Analyze video frames")
89
  expect(checkbox).to_be_visible()
90
 
@@ -96,6 +103,15 @@ class TestVideoAnalyzerUI:
96
  slider_label = page.locator("text=Number of frames")
97
  expect(slider_label).to_be_visible()
98
 
 
 
 
 
 
 
 
 
 
99
  def test_can_switch_to_chat_tab(self, page: Page, app_url: str):
100
  """Test switching to the Chat tab."""
101
  page.goto(app_url)
@@ -103,9 +119,8 @@ class TestVideoAnalyzerUI:
103
  # Click Chat tab
104
  page.click("text=Chat with Videos")
105
 
106
- # Verify chat input is visible
107
- chat_input = page.locator("textarea[placeholder*='question']")
108
- expect(chat_input).to_be_visible()
109
 
110
  def test_ask_button_in_chat_tab(self, page: Page, app_url: str):
111
  """Test that Ask button exists in Chat tab."""
@@ -118,16 +133,12 @@ class TestVideoAnalyzerUI:
118
  ask_btn = page.locator("button:has-text('Ask')")
119
  expect(ask_btn).to_be_visible()
120
 
121
- def test_empty_url_shows_message(self, page: Page, app_url: str):
122
- """Test that submitting empty URL shows appropriate message."""
123
  page.goto(app_url)
124
 
125
- # Click analyze without entering URL
126
- page.click("button:has-text('Analyze Video')")
127
-
128
- # Wait for response
129
- time.sleep(2)
130
 
131
- # Check for login or URL prompt message
132
- response = page.locator("text=Please")
133
- expect(response).to_be_visible()
 
40
  # Check title is visible
41
  expect(page.locator("text=Video Analyzer")).to_be_visible()
42
 
43
+ def test_app_description_visible(self, page: Page, app_url: str):
44
+ """Test that the app description is visible."""
45
+ page.goto(app_url)
46
+
47
+ # Check description
48
+ expect(page.locator("text=Download, transcribe, analyze")).to_be_visible()
49
+
50
  def test_login_button_visible(self, page: Page, app_url: str):
51
  """Test that the login button is visible."""
52
  page.goto(app_url)
 
75
  """Test that the YouTube URL input field exists."""
76
  page.goto(app_url)
77
 
78
+ # Check for URL input with placeholder
79
+ url_input = page.locator("input[type='text'], textarea").first
80
  expect(url_input).to_be_visible()
81
 
82
  def test_analyze_button_exists(self, page: Page, app_url: str):
 
91
  """Test that the frame analysis checkbox exists."""
92
  page.goto(app_url)
93
 
94
+ # Check for checkbox label
95
  checkbox = page.locator("text=Analyze video frames")
96
  expect(checkbox).to_be_visible()
97
 
 
103
  slider_label = page.locator("text=Number of frames")
104
  expect(slider_label).to_be_visible()
105
 
106
+ def test_step_instructions_visible(self, page: Page, app_url: str):
107
+ """Test that step instructions are visible."""
108
+ page.goto(app_url)
109
+
110
+ # Check for step labels
111
+ expect(page.locator("text=Step 1")).to_be_visible()
112
+ expect(page.locator("text=Step 2")).to_be_visible()
113
+ expect(page.locator("text=Step 3")).to_be_visible()
114
+
115
  def test_can_switch_to_chat_tab(self, page: Page, app_url: str):
116
  """Test switching to the Chat tab."""
117
  page.goto(app_url)
 
119
  # Click Chat tab
120
  page.click("text=Chat with Videos")
121
 
122
+ # Verify chat elements are visible
123
+ expect(page.locator("text=Ask questions about videos")).to_be_visible()
 
124
 
125
  def test_ask_button_in_chat_tab(self, page: Page, app_url: str):
126
  """Test that Ask button exists in Chat tab."""
 
133
  ask_btn = page.locator("button:has-text('Ask')")
134
  expect(ask_btn).to_be_visible()
135
 
136
+ def test_knowledge_base_status_in_chat_tab(self, page: Page, app_url: str):
137
+ """Test that knowledge base status is shown in Chat tab."""
138
  page.goto(app_url)
139
 
140
+ # Switch to Chat tab
141
+ page.click("text=Chat with Videos")
 
 
 
142
 
143
+ # Should show empty knowledge base message
144
+ expect(page.locator("text=Knowledge base")).to_be_visible()