Skip to content

Commit 9b15b2e

Browse files
committed
Add movie gallery feature with navigation and display of AI-generated movies
1 parent 5c6a190 commit 9b15b2e

File tree

4 files changed

+190
-2
lines changed

4 files changed

+190
-2
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Azure Rambi Coding Instructions
22

33
## Azure Guidelines
4-
- @azure Rule - Use Azure Best Practices: When generating code for Azure, running terminal commands for Azure, or performing operations related to Azure, invoke your `get_azure_best_practices` tool if available.
4+
- @azure Rule - Use Azure Best Practices: When generating code for Azure, running terminal commands for Azure, or performing operations related to Azure, invoke your `azure_development-get_best_practices` tool if available.
55
- Use Infrastructure as Code (IaC) with Bicep for all Azure deployments
66
- Prefer Azure Container Apps for microservices deployment
77
- Use Managed Identity for authentication when possible

src/gui_svc/app.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import base64
1212

1313
from collections import OrderedDict
14-
from flask import Flask, render_template, request
14+
from flask import Flask, render_template, request, jsonify
1515
from flask_wtf import FlaskForm
1616
from azure.monitor.opentelemetry import configure_azure_monitor
1717
from opentelemetry.instrumentation.flask import FlaskInstrumentor
@@ -216,6 +216,29 @@ def movie_generate():
216216
}
217217
return render_template('generated_movie.html', generated_movie=generated_movie)
218218

219+
@app.route('/gallery', methods=['GET'])
220+
def movie_gallery():
221+
"""Display all movies from the movie gallery service."""
222+
logger.info("Accessing movie gallery")
223+
movies = []
224+
try:
225+
with DaprClient() as d:
226+
logger.info("Invoking movie gallery service to get all movies")
227+
resp = d.invoke_method(
228+
app_id="movie-gallery-svc",
229+
method_name="movies",
230+
http_verb='GET'
231+
)
232+
# Properly access data from Dapr InvokeMethodResponse object
233+
if resp.data:
234+
movies = json.loads(resp.data.decode('utf-8'))
235+
logger.info(f"Retrieved {len(movies)} movies from gallery")
236+
else:
237+
logger.warning("No data returned from movie gallery service")
238+
except Exception as e:
239+
logger.exception("Error retrieving movies from gallery service", exc_info=e)
240+
241+
return render_template('gallery.html', movies=movies, github=GitHubContext())
219242

220243
if __name__ == '__main__':
221244
app.run( debug=True)

src/gui_svc/templates/base.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
<script src="https://unpkg.com/htmx.org@1.6.1"></script>
2020
<script type="module" src="https://md-block.verou.me/md-block.js"></script>
21+
<!-- Bootstrap Bundle with Popper -->
22+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>
2123
</head>
2224

2325
<body data-bs-theme="light" class="container">
@@ -31,6 +33,16 @@ <h1 class="col-12 text-center">
3133
</div>
3234
</header>
3335

36+
<!-- Navigation tabs -->
37+
<ul class="nav nav-tabs mb-4">
38+
<li class="nav-item">
39+
<a class="nav-link {{ 'active' if request.path == '/' else '' }}" href="/">Movie Generator</a>
40+
</li>
41+
<li class="nav-item">
42+
<a class="nav-link {{ 'active' if request.path == '/gallery' else '' }}" href="/gallery">Movie Gallery</a>
43+
</li>
44+
</ul>
45+
3446
<div class="my-3 p-2">
3547
<main layout:fragment="content">
3648
{% block content %}{% endblock %}

src/gui_svc/templates/gallery.html

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
{% extends "base.html" %}
2+
3+
{% block title %}Movie Gallery{% endblock %}
4+
5+
{% block content %}
6+
<section id="movie-gallery">
7+
<div class="container">
8+
<div class="row mb-4">
9+
<div class="col">
10+
<h2>Movie Gallery</h2>
11+
<p class="lead">Browse all AI-generated movies</p>
12+
</div>
13+
</div>
14+
15+
{% if movies %}
16+
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
17+
{% for movie in movies %}
18+
<div class="col">
19+
<div class="card h-100">
20+
{% if movie.poster_url %}
21+
<img src="{{ movie.poster_url }}" class="card-img-top" alt="{{ movie.title }} poster" style="height: 300px; object-fit: cover;">
22+
{% else %}
23+
<div class="card-img-top bg-secondary text-white d-flex align-items-center justify-content-center" style="height: 300px;">
24+
<span>No poster available</span>
25+
</div>
26+
{% endif %}
27+
<div class="card-body">
28+
<h5 class="card-title">{{ movie.title }}</h5>
29+
<p class="card-text small text-muted">ID: {{ movie.id }}</p>
30+
<p class="card-text">{{ movie.plot[:150] }}{% if movie.plot|length > 150 %}...{% endif %}</p>
31+
</div>
32+
<div class="card-footer">
33+
<button class="btn btn-sm btn-outline-primary" type="button" data-bs-toggle="modal"
34+
data-bs-target="#movieModal{{ loop.index }}">
35+
<i class="bi bi-film"></i> View Movie Details
36+
</button>
37+
</div>
38+
39+
<!-- Modal for Movie Details -->
40+
<div class="modal fade" id="movieModal{{ loop.index }}" tabindex="-1" aria-labelledby="movieModalLabel{{ loop.index }}" aria-hidden="true">
41+
<div class="modal-dialog modal-xl">
42+
<div class="modal-content">
43+
<div class="modal-header">
44+
<h5 class="modal-title" id="movieModalLabel{{ loop.index }}">{{ movie.title }}</h5>
45+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
46+
</div>
47+
<div class="modal-body">
48+
<div class="container-fluid">
49+
<div class="row">
50+
<!-- Main movie poster and details -->
51+
<div class="col-md-4">
52+
{% if movie.poster_url %}
53+
<img src="{{ movie.poster_url }}" class="img-fluid rounded" alt="{{ movie.title }} poster">
54+
{% else %}
55+
<div class="bg-secondary text-white d-flex align-items-center justify-content-center rounded" style="height: 300px;">
56+
<span>No poster available</span>
57+
</div>
58+
{% endif %}
59+
</div>
60+
<div class="col-md-8">
61+
<h3>{{ movie.title }}</h3>
62+
<p><strong>Movie ID:</strong> {{ movie.id }}</p>
63+
<h4>Plot</h4>
64+
<p>{{ movie.plot }}</p>
65+
66+
{% if movie.poster_description %}
67+
<h4>Poster Description</h4>
68+
<p>{{ movie.poster_description }}</p>
69+
{% endif %}
70+
</div>
71+
</div>
72+
73+
<!-- Source Movies Section, if available -->
74+
{% if movie.payload is defined and movie.payload.movie1 is defined and movie.payload.movie2 is defined %}
75+
<hr class="my-4">
76+
<h4>Source Movies</h4>
77+
<div class="row mt-3">
78+
<!-- Movie 1 Info -->
79+
<div class="col-md-6">
80+
<div class="card h-100">
81+
<div class="card-header bg-light">
82+
<h5>Movie 1: {{ movie.payload.movie1.title }}</h5>
83+
</div>
84+
<div class="card-body">
85+
<div class="row">
86+
<div class="col-md-4">
87+
{% if movie.payload.movie1.poster_url %}
88+
<img src="{{ movie.payload.movie1.poster_url }}" class="img-fluid rounded" alt="{{ movie.payload.movie1.title }} poster">
89+
{% else %}
90+
<div class="bg-secondary text-white d-flex align-items-center justify-content-center rounded" style="height: 150px;">
91+
<span>No poster</span>
92+
</div>
93+
{% endif %}
94+
</div>
95+
<div class="col-md-8">
96+
<p>{{ movie.payload.movie1.plot }}</p>
97+
</div>
98+
</div>
99+
</div>
100+
</div>
101+
</div>
102+
103+
<!-- Movie 2 Info -->
104+
<div class="col-md-6">
105+
<div class="card h-100">
106+
<div class="card-header bg-light">
107+
<h5>Movie 2: {{ movie.payload.movie2.title }}</h5>
108+
</div>
109+
<div class="card-body">
110+
<div class="row">
111+
<div class="col-md-4">
112+
{% if movie.payload.movie2.poster_url %}
113+
<img src="{{ movie.payload.movie2.poster_url }}" class="img-fluid rounded" alt="{{ movie.payload.movie2.title }} poster">
114+
{% else %}
115+
<div class="bg-secondary text-white d-flex align-items-center justify-content-center rounded" style="height: 150px;">
116+
<span>No poster</span>
117+
</div>
118+
{% endif %}
119+
</div>
120+
<div class="col-md-8">
121+
<p>{{ movie.payload.movie2.plot }}</p>
122+
</div>
123+
</div>
124+
</div>
125+
</div>
126+
</div>
127+
</div>
128+
{% if movie.payload.genre is defined %}
129+
<div class="mt-3">
130+
<strong>Genre:</strong> {{ movie.payload.genre }}
131+
</div>
132+
{% endif %}
133+
{% endif %}
134+
</div>
135+
</div>
136+
<div class="modal-footer">
137+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
138+
</div>
139+
</div>
140+
</div>
141+
</div>
142+
</div>
143+
</div>
144+
{% endfor %}
145+
</div>
146+
{% else %}
147+
<div class="alert alert-info">
148+
No movies found in the gallery. Generate some movies first!
149+
</div>
150+
{% endif %}
151+
</div>
152+
</section>
153+
{% endblock %}

0 commit comments

Comments
 (0)