246 lines
10 KiB
HTML
246 lines
10 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ image.filename }} - SEREACT Web Client{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-image"></i> {{ image.filename }}
|
|
</h5>
|
|
<div class="btn-group" role="group">
|
|
<a href="{{ url_for('edit_image', image_id=image.id) }}" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-edit"></i> Edit
|
|
</a>
|
|
<a href="{{ get_api_base_url() }}/api/v1/images/{{ image.id }}/download"
|
|
class="btn btn-outline-success btn-sm" target="_blank">
|
|
<i class="fas fa-download"></i> Download
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="card-body text-center">
|
|
<img src="{{ get_api_base_url() }}/api/v1/images/{{ image.id }}/download"
|
|
alt="{{ image.filename }}"
|
|
class="img-fluid rounded"
|
|
style="max-height: 600px;"
|
|
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjhmOWZhIiBzdHJva2U9IiNkZWUyZTYiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE4IiBmaWxsPSIjNmM3NTdkIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+SW1hZ2UgTm90IEZvdW5kPC90ZXh0Pjwvc3ZnPg=='">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<!-- Image Information -->
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-info-circle"></i> Image Information
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<dl class="row mb-0">
|
|
<dt class="col-sm-5">Filename:</dt>
|
|
<dd class="col-sm-7">{{ image.filename }}</dd>
|
|
|
|
<dt class="col-sm-5">Original:</dt>
|
|
<dd class="col-sm-7">{{ image.original_filename }}</dd>
|
|
|
|
<dt class="col-sm-5">Size:</dt>
|
|
<dd class="col-sm-7">{{ (image.file_size / 1024 / 1024) | round(2) }} MB</dd>
|
|
|
|
<dt class="col-sm-5">Type:</dt>
|
|
<dd class="col-sm-7">{{ image.content_type }}</dd>
|
|
|
|
<dt class="col-sm-5">Uploaded:</dt>
|
|
<dd class="col-sm-7">{{ image.upload_date.strftime('%Y-%m-%d %H:%M') if image.upload_date else 'Unknown' }}</dd>
|
|
|
|
<dt class="col-sm-5">Uploader:</dt>
|
|
<dd class="col-sm-7">{{ image.uploader_id }}</dd>
|
|
|
|
<dt class="col-sm-5">Team:</dt>
|
|
<dd class="col-sm-7">{{ image.team_id }}</dd>
|
|
|
|
{% if image.collection_id %}
|
|
<dt class="col-sm-5">Collection:</dt>
|
|
<dd class="col-sm-7">{{ image.collection_id }}</dd>
|
|
{% endif %}
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AI Status -->
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-brain"></i> AI Processing Status
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if image.has_embedding %}
|
|
<div class="alert alert-success" role="alert">
|
|
<i class="fas fa-check-circle"></i>
|
|
<strong>Ready for Search</strong><br>
|
|
This image has been processed and is available for AI-powered search.
|
|
</div>
|
|
{% else %}
|
|
<div class="alert alert-warning" role="alert">
|
|
<i class="fas fa-clock"></i>
|
|
<strong>Processing</strong><br>
|
|
AI processing is in progress. The image will be searchable once complete.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
{% if image.description %}
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-align-left"></i> Description
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="mb-0">{{ image.description }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Tags -->
|
|
{% if image.tags %}
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-tags"></i> Tags
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
{% for tag in image.tags %}
|
|
<span class="badge bg-secondary me-1 mb-1">{{ tag }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Metadata -->
|
|
{% if image.metadata %}
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-code"></i> Technical Metadata
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<pre class="small text-muted mb-0">{{ image.metadata | tojson(indent=2) }}</pre>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Actions -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-tools"></i> Actions
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a href="{{ url_for('edit_image', image_id=image.id) }}" class="btn btn-primary">
|
|
<i class="fas fa-edit"></i> Edit Image
|
|
</a>
|
|
<a href="{{ get_api_base_url() }}/api/v1/images/{{ image.id }}/download"
|
|
class="btn btn-success" target="_blank">
|
|
<i class="fas fa-download"></i> Download Original
|
|
</a>
|
|
{% if image.has_embedding %}
|
|
<button type="button" class="btn btn-info" onclick="findSimilar()">
|
|
<i class="fas fa-search-plus"></i> Find Similar Images
|
|
</button>
|
|
{% endif %}
|
|
<button type="button" class="btn btn-outline-danger"
|
|
onclick="confirmDelete('{{ image.filename }}', '{{ url_for('delete_image', image_id=image.id) }}')">
|
|
<i class="fas fa-trash"></i> Delete Image
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between">
|
|
<a href="{{ url_for('images') }}" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left"></i> Back to Images
|
|
</a>
|
|
<div class="btn-group" role="group">
|
|
<a href="{{ url_for('upload_image') }}" class="btn btn-outline-primary">
|
|
<i class="fas fa-upload"></i> Upload Another
|
|
</a>
|
|
<a href="{{ url_for('search') }}" class="btn btn-outline-success">
|
|
<i class="fas fa-search"></i> Search Images
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Confirmation Modal -->
|
|
<div class="modal fade" id="deleteModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-exclamation-triangle text-warning"></i> Confirm Delete
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Are you sure you want to delete the image "<span id="deleteImageName"></span>"?</p>
|
|
<div class="alert alert-warning" role="alert">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<strong>Warning:</strong> This action cannot be undone. The image will be permanently removed from storage.
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
<i class="fas fa-times"></i> Cancel
|
|
</button>
|
|
<form id="deleteForm" method="POST" style="display: inline;">
|
|
<button type="submit" class="btn btn-danger">
|
|
<i class="fas fa-trash"></i> Delete Image
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function confirmDelete(imageName, deleteUrl) {
|
|
document.getElementById('deleteImageName').textContent = imageName;
|
|
document.getElementById('deleteForm').action = deleteUrl;
|
|
new bootstrap.Modal(document.getElementById('deleteModal')).show();
|
|
}
|
|
|
|
function findSimilar() {
|
|
// Redirect to search with a query to find similar images
|
|
const searchUrl = "{{ url_for('search') }}";
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = searchUrl;
|
|
|
|
const queryInput = document.createElement('input');
|
|
queryInput.type = 'hidden';
|
|
queryInput.name = 'query';
|
|
queryInput.value = 'similar to {{ image.filename }}';
|
|
|
|
form.appendChild(queryInput);
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
}
|
|
</script>
|
|
{% endblock %} |