возможность поделиться ссылкой
Gitea Actions Demo / build_and_push (push) Successful in 34m54s Details

This commit is contained in:
artem 2026-02-25 22:48:19 +03:00
parent 90331a7036
commit 33215d33cb
6 changed files with 131 additions and 19 deletions

View File

@ -63,5 +63,6 @@ class BreedsController(Controller):
context={ context={
"result": result, "result": result,
"attachment": attachments[0], "attachment": attachments[0],
"share_id": result_id,
}, },
) )

View File

@ -47,6 +47,7 @@ class RecognizerResult(UJsonMixin):
images: list images: list
description: dict[str, list] | None description: dict[str, list] | None
uploaded_attach_id: str | None uploaded_attach_id: str | None
share_id: str | None
@dataclass @dataclass
@ -90,12 +91,13 @@ class RecognizerService:
async def create_result( async def create_result(
self, attachment: Attachment, user_id: str, device_id: str, beerd_results: list[ResultNameBeerds] self, attachment: Attachment, user_id: str, device_id: str, beerd_results: list[ResultNameBeerds]
): ) -> str:
beerd_names = {b.name: b for b in beerd_results} beerd_names = {b.name: b for b in beerd_results}
characters = await self._repository_characters.get_characters() characters = await self._repository_characters.get_characters()
share_id = str(uuid4())
await self._repository.create_result_with_beerds( await self._repository.create_result_with_beerds(
models.Results( models.Results(
id=str(uuid4()), id=share_id,
attachment_id=attachment.id, attachment_id=attachment.id,
user_id=user_id, user_id=user_id,
device_id=device_id, device_id=device_id,
@ -107,15 +109,16 @@ class RecognizerService:
if ch.name in beerd_names if ch.name in beerd_names
], ],
) )
return share_id
async def get_results(self, result_id: str) -> SharingResult: async def get_results(self, result_id: str) -> SharingResult:
results = await self._repository.get_results() results = await self._repository.get_results()
beerds_store: dict[str, Breed] = {b.id: b for b in await self._repository_characters.get_characters()} beerds_store: dict[str, Breed] = {b.id: b for b in await self._repository_characters.get_characters()}
images_dogs = await self._repository.images_dogs() images_dogs = await self._repository.images_dogs()
beers: list[SharingBeerds] = []
for r in results: for r in results:
if r.result.id != result_id: if r.result.id != result_id:
continue continue
beers: list[SharingBeerds] = []
for beerd in r.beerds: for beerd in r.beerds:
name = beerds_store[beerd.beerd_id].name.replace(" ", "_") name = beerds_store[beerd.beerd_id].name.replace(" ", "_")
beers.append( beers.append(
@ -148,16 +151,20 @@ class RecognizerService:
) )
description.setdefault(name, []).append(f"/dogs-characteristics/{name.replace(' ', '_')}") description.setdefault(name, []).append(f"/dogs-characteristics/{name.replace(' ', '_')}")
results[probabilities] = name results[probabilities] = name
asyncio.create_task(
self.create_result( share_id = await self.create_result(
attachment, attachment,
user_id, user_id,
device_id, device_id,
[ResultNameBeerds(name=results[key], probability=key*100) for key in results], [ResultNameBeerds(name=results[key], probability=key*100) for key in results],
)
) )
return RecognizerResult( return RecognizerResult(
results=results, images=images, description=description, uploaded_attach_id=attachment.id results=results,
images=images,
description=description,
uploaded_attach_id=attachment.id,
share_id=share_id
) )
async def predict_cat_image(self, image: bytes) -> RecognizerResult: async def predict_cat_image(self, image: bytes) -> RecognizerResult:
@ -176,7 +183,13 @@ class RecognizerService:
) )
) )
results[probabilities] = name results[probabilities] = name
return RecognizerResult(results=results, images=images, description=None, uploaded_attach_id=attachment.id) return RecognizerResult(
results=results,
images=images,
description=None,
uploaded_attach_id=attachment.id,
share_id=None
)
def _predict(self, image: bytes, model, device="cpu") -> list[Any]: def _predict(self, image: bytes, model, device="cpu") -> list[Any]:
img_size = (224, 224) img_size = (224, 224)

View File

@ -16,8 +16,23 @@ async function SavePhoto(self) {
if (response.ok) { if (response.ok) {
let json = await response.json(); let json = await response.json();
currentAttachmentID = json.uploaded_attach_id; currentAttachmentID = json.uploaded_attach_id;
let shareID = json.share_id;
let style = "";
if (!shareID) {
style = "style='display:none'"
}
console.log(style, !shareID);
let text = `
<div class = "title-block">
<h3 class='image-results'>Результаты</h3>
<div id = "share-icon" data-link = "https://порода-по-фото.рф/beerds/dogs/share/${shareID}" ${style}>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M6.58900253,18.0753965 C6.5573092,17.8852365 6.54138127,17.692783 6.54138127,17.5 C6.54138127,15.5670034 8.10838464,14 10.0413813,14 L12.5,14 C12.7761424,14 13,14.2238576 13,14.5 L13,17.7267988 C13,17.8027316 13.0270017,17.8761901 13.0761794,17.9340463 C13.190639,18.0687047 13.3925891,18.085079 13.5272475,17.9706194 L20.5626572,11.9905211 C20.6161112,11.9450852 20.6657995,11.8953969 20.7112354,11.8419429 C21.1762277,11.2948932 21.1097069,10.4744711 20.5626572,10.0094789 L13.5272475,4.02938061 C13.4693913,3.98020285 13.3959328,3.95320119 13.32,3.95320119 C13.1432689,3.95320119 13,4.09647007 13,4.27320119 L13,7.5 C13,7.77614237 12.7761424,8 12.5,8 L9.5,8 C5.91014913,8 3,10.9101491 3,14.5 C3,17.3494045 4.26637093,19.0973664 6.88288761,19.8387069 L6.58900253,18.0753965 Z M10.0413813,15 C8.66066939,15 7.54138127,16.1192881 7.54138127,17.5 C7.54138127,17.6377022 7.55275836,17.7751689 7.57539646,17.9109975 L7.99319696,20.4178005 C8.0506764,20.7626772 7.74549866,21.0585465 7.40256734,20.990415 C3.83673227,20.2819767 2,18.0778979 2,14.5 C2,10.3578644 5.35786438,7 9.5,7 L12,7 L12,4.27320119 C12,3.54418532 12.5909841,2.95320119 13.32,2.95320119 C13.6332228,2.95320119 13.9362392,3.06458305 14.1748959,3.26744129 L21.2103057,9.24753957 C22.1781628,10.0702182 22.2958533,11.5217342 21.4731747,12.4895914 C21.3927882,12.5841638 21.3048781,12.6720739 21.2103057,12.7524604 L14.1748959,18.7325587 C13.6194301,19.2047047 12.7863861,19.1371606 12.3142401,18.5816947 C12.1113819,18.343038 12,18.0400216 12,17.7267988 L12,15 L10.0413813,15 Z"/>
</svg>
</div>
</div>
`;
let text = "<h3 class='image-results'>Результаты</h3>";
let uniqChecker = {}; let uniqChecker = {};
// Функция для создания HTML галереи // Функция для создания HTML галереи
@ -199,7 +214,10 @@ function closeModal() {
/* ────────────────────────────────────────────────────── */ /* ────────────────────────────────────────────────────── */
(() => { (() => {
const ratingContainer = document.querySelector('.star-rating'); const ratingContainer = document.querySelector('.star-rating');
const stars = Array.from(ratingContainer.querySelectorAll('.star')); let stars = []
if (ratingContainer) {
stars = Array.from(ratingContainer.querySelectorAll('.star'));
}
const feedback = document.getElementById('feedback'); const feedback = document.getElementById('feedback');
let currentRating = 0; // 0 = пока не выбрано let currentRating = 0; // 0 = пока не выбрано
@ -278,3 +296,35 @@ function closeModal() {
// По умолчанию не показываем выбранную оценку // По умолчанию не показываем выбранную оценку
setRating(0); setRating(0);
})(); })();
document.addEventListener('click', function (event) {
const shareBtn = event.target.closest('#share-icon');
if (shareBtn) {
const link = shareBtn.getAttribute('data-link');
if (link) {
navigator.clipboard.writeText(link).then(() => {
showTooltip(shareBtn, 'Скопировано!');
});
}
}
});
function showTooltip(parent, text) {
// Проверяем, не висит ли уже тултип
if (parent.querySelector('.tooltip')) return;
const tooltip = document.createElement('span');
tooltip.className = 'tooltip';
tooltip.textContent = text;
parent.appendChild(tooltip);
// Удаляем тултип через 2 секунды
setTimeout(() => {
tooltip.classList.add('fade-out');
setTimeout(() => tooltip.remove(), 300);
}, 2000);
}

View File

@ -370,3 +370,44 @@ input[type="text"]:hover {
outline: 2px solid #333; outline: 2px solid #333;
outline-offset: 2px; outline-offset: 2px;
} }
.title-block {
display: flex;
justify-content: space-around;
width: 164px;
margin: 0 auto;
}
#share-icon {
position: relative; /* Нужно для позиционирования тултипа */
cursor: pointer;
display: inline-flex;
height: 10px;
padding: 20px 0 0 0;
}
.tooltip {
position: absolute;
bottom: 120%; /* Над иконкой */
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
margin: 5px 10px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
animation: fadeIn 0.3s ease;
padding: 0 4px 0 4px;
}
.tooltip.fade-out {
opacity: 0;
transition: opacity 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translate(-50%, 10px); }
to { opacity: 1; transform: translate(-50%, 0); }
}

View File

@ -8,7 +8,7 @@
{% block meta %}{% endblock %} {% block meta %}{% endblock %}
<link rel="icon" type="image/x-icon" href="/static/favicon.ico"> <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/static/styles.css?v=5"> <link rel="stylesheet" href="/static/styles.css?v=6">
<!-- Yandex.Metrika counter --> <!-- Yandex.Metrika counter -->
<script type="text/javascript" > <script type="text/javascript" >
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)}; (function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
@ -45,6 +45,6 @@
{% block form %}{% endblock %} {% block form %}{% endblock %}
</body> </body>
</section> </section>
<script src="/static/scripts.js?v=8"></script> <script src="/static/scripts.js?v=9"></script>
</html> </html>

View File

@ -9,12 +9,19 @@
{% endblock %} {% endblock %}
{% block form %} {% block form %}
<div> <div>
<div id="upload-image"> <div id="upload-image">
<div id="upload-image-text">Ваше изображение:</div> <div id="upload-image-text">Ваше изображение:</div>
<img id="image" style="max-width: 200px;" src="/attachments{{ attachment.path }}.original.jpg"> <img id="image" style="max-width: 200px;" src="/attachments{{ attachment.path }}.original.jpg">
</div> </div>
<div id="result"> <div id="result">
<div class = "title-block">
<h3 class="image-results">Результаты</h3> <h3 class="image-results">Результаты</h3>
<div id = "share-icon" data-link = "https://порода-по-фото.рф/beerds/dogs/share/{{ share_id }}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M6.58900253,18.0753965 C6.5573092,17.8852365 6.54138127,17.692783 6.54138127,17.5 C6.54138127,15.5670034 8.10838464,14 10.0413813,14 L12.5,14 C12.7761424,14 13,14.2238576 13,14.5 L13,17.7267988 C13,17.8027316 13.0270017,17.8761901 13.0761794,17.9340463 C13.190639,18.0687047 13.3925891,18.085079 13.5272475,17.9706194 L20.5626572,11.9905211 C20.6161112,11.9450852 20.6657995,11.8953969 20.7112354,11.8419429 C21.1762277,11.2948932 21.1097069,10.4744711 20.5626572,10.0094789 L13.5272475,4.02938061 C13.4693913,3.98020285 13.3959328,3.95320119 13.32,3.95320119 C13.1432689,3.95320119 13,4.09647007 13,4.27320119 L13,7.5 C13,7.77614237 12.7761424,8 12.5,8 L9.5,8 C5.91014913,8 3,10.9101491 3,14.5 C3,17.3494045 4.26637093,19.0973664 6.88288761,19.8387069 L6.58900253,18.0753965 Z M10.0413813,15 C8.66066939,15 7.54138127,16.1192881 7.54138127,17.5 C7.54138127,17.6377022 7.55275836,17.7751689 7.57539646,17.9109975 L7.99319696,20.4178005 C8.0506764,20.7626772 7.74549866,21.0585465 7.40256734,20.990415 C3.83673227,20.2819767 2,18.0778979 2,14.5 C2,10.3578644 5.35786438,7 9.5,7 L12,7 L12,4.27320119 C12,3.54418532 12.5909841,2.95320119 13.32,2.95320119 C13.6332228,2.95320119 13.9362392,3.06458305 14.1748959,3.26744129 L21.2103057,9.24753957 C22.1781628,10.0702182 22.2958533,11.5217342 21.4731747,12.4895914 C21.3927882,12.5841638 21.3048781,12.6720739 21.2103057,12.7524604 L14.1748959,18.7325587 C13.6194301,19.2047047 12.7863861,19.1371606 12.3142401,18.5816947 C12.1113819,18.343038 12,18.0400216 12,17.7267988 L12,15 L10.0413813,15 Z"/>
</svg>
</div>
</div>
{% for result in result.beerds %} {% for result in result.beerds %}
<div class="image-block"><div class="image-text">{{ result.name }} (вероятность: {{ result.probability }}%) <br><a href="{{ result.alias }}" target="_blank">Описание и фото</a></div> <div class="image-block"><div class="image-text">{{ result.name }} (вероятность: {{ result.probability }}%) <br><a href="{{ result.alias }}" target="_blank">Описание и фото</a></div>
<div class="gallery-container"> <div class="gallery-container">