Files
2026-07-14 00:01:48 +03:00

208 lines
7.4 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('admin.layouts.app')
@section('title', 'AI — Toplu Anime Meta Doldurma')
@section('page-title', 'AI — Toplu Anime Meta Doldurma')
@section('content')
<div class="row justify-content-center">
<div class="col-lg-9">
{{-- İstatistikler --}}
<div class="row g-3 mb-4">
<div class="col-sm-4">
<div class="card text-center">
<div class="fs-3 fw-800 text-info">{{ $total }}</div>
<div class="text-muted small">Toplam anime</div>
</div>
</div>
<div class="col-sm-4">
<div class="card text-center">
<div class="fs-3 fw-800 text-warning">{{ $missing }}</div>
<div class="text-muted small">Eksik meta alanı olan</div>
</div>
</div>
<div class="col-sm-4">
<div class="card text-center">
<div class="fs-3 fw-800 text-danger">{{ $noGenres }}</div>
<div class="text-muted small">Kategori ataması yok</div>
</div>
</div>
</div>
<div class="card mb-4">
<h6 class="mb-1">Toplu Anime Meta Doldurma</h6>
<p class="text-muted small mb-3">
DeepSeek AI, eksik alanı olan animeleri tek tek işler. Açıklama, yıl, stüdyo, tür, durum, puan ve kategorileri doldurur.
Dolu alanların üstüne yazmaz. Anime başına ~3-5 sn sürer.
</p>
<div class="d-flex align-items-center gap-3 flex-wrap mb-3">
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" id="force-mode">
<label class="form-check-label small" for="force-mode">Dolu alanları da yenile (force)</label>
</div>
</div>
<div class="d-flex gap-2 mb-4">
<button id="start-btn" class="btn btn-info px-4" onclick="startFill()">
<i class="bi bi-stars"></i> Başlat
</button>
<button id="stop-btn" class="btn btn-outline-danger px-4" onclick="stopFill()" style="display:none">
<i class="bi bi-stop-fill"></i> Durdur
</button>
<button id="reset-btn" class="btn btn-outline-secondary px-4" onclick="resetFill()" style="display:none">
<i class="bi bi-arrow-counterclockwise"></i> Sıfırla
</button>
</div>
{{-- İlerleme --}}
<div id="progress-wrap" style="display:none">
<div class="d-flex justify-content-between small text-muted mb-1">
<span id="prog-label">Hazırlanıyor…</span>
<span id="prog-count">0 / 0</span>
</div>
<div class="progress mb-3" style="height:10px;border-radius:6px">
<div id="prog-bar" class="progress-bar bg-info progress-bar-striped progress-bar-animated" style="width:0%"></div>
</div>
<div class="d-flex gap-3 small text-muted mb-3">
<span> <span id="cnt-ok">0</span> tamamlandı</span>
<span> <span id="cnt-skip">0</span> atlandı</span>
<span> <span id="cnt-err">0</span> hata</span>
</div>
</div>
{{-- Log --}}
<div id="log-box" style="display:none;background:#0d0d0d;border:1px solid #222;border-radius:8px;padding:12px;max-height:420px;overflow-y:auto;font-family:monospace;font-size:.78rem;line-height:1.6"></div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
let _animes = [], _idx = 0, _stop = false;
let _ok = 0, _skip = 0, _err = 0;
const logBox = document.getElementById('log-box');
const progWrap = document.getElementById('progress-wrap');
const progBar = document.getElementById('prog-bar');
const progLbl = document.getElementById('prog-label');
const progCnt = document.getElementById('prog-count');
function log(html) {
logBox.insertAdjacentHTML('beforeend', `<div>${html}</div>`);
logBox.scrollTop = logBox.scrollHeight;
}
async function startFill() {
document.getElementById('start-btn').style.display = 'none';
document.getElementById('stop-btn').style.display = '';
document.getElementById('reset-btn').style.display = 'none';
progWrap.style.display = '';
logBox.style.display = '';
logBox.innerHTML = '';
_stop = false; _ok = 0; _skip = 0; _err = 0;
updateCounts();
const force = document.getElementById('force-mode').checked;
log('<span style="color:#00c8d4">⏳ Anime listesi alınıyor…</span>');
const res = await fetch('{{ route("ai.anime-meta-ids") }}', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
body: JSON.stringify({ force }),
});
const data = await res.json();
_animes = data.animes || [];
_idx = 0;
if (!_animes.length) {
log('<span style="color:#4caf50">✅ Tüm animeler zaten dolu, yapılacak bir şey yok.</span>');
finish();
return;
}
log(`<span style="color:#ccc">📋 ${_animes.length} anime işlenecek.</span>`);
progBar.style.width = '0%';
await processNext(force);
}
async function processNext(force) {
if (_stop || _idx >= _animes.length) { finish(); return; }
const anime = _animes[_idx];
_idx++;
const pct = Math.round((_idx / _animes.length) * 100);
progBar.style.width = pct + '%';
progCnt.textContent = `${_idx} / ${_animes.length}`;
progLbl.textContent = anime.title;
const missingStr = anime.missing.length ? anime.missing.join(', ') : '—';
log(`<span style="color:#888">[${_idx}/${_animes.length}]</span> <span style="color:#e8e8e8">${anime.title}</span> <span style="color:#555">— eksik: ${missingStr}</span>`);
try {
const r = await fetch('{{ route("ai.fill-anime-meta") }}', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
body: JSON.stringify({ anime_id: anime.id, force }),
});
const d = await r.json();
if (d.ok) {
const fields = (d.filled || []).join(', ') || 'değişiklik yok';
log(` <span style="color:#4caf50">✅ OK</span> → ${fields}`);
_ok++;
} else {
log(` <span style="color:#ff5722">❌ HATA</span> → ${d.error || 'Bilinmeyen hata'}`);
_err++;
}
} catch (e) {
log(` <span style="color:#ff5722">❌ HATA</span> → ${e.message}`);
_err++;
}
updateCounts();
// 1.5sn bekle, sonraki anime'ye geç
await new Promise(r => setTimeout(r, 1500));
await processNext(force);
}
function finish() {
progBar.classList.remove('progress-bar-animated');
progBar.style.width = '100%';
progLbl.textContent = 'Tamamlandı';
document.getElementById('stop-btn').style.display = 'none';
document.getElementById('reset-btn').style.display = '';
document.getElementById('start-btn').style.display = '';
log(`<br><span style="color:#00c8d4;font-weight:700">🏁 Bitti → ${_ok} tamamlandı, ${_skip} atlandı, ${_err} hata</span>`);
}
function stopFill() {
_stop = true;
log('<span style="color:#ff9800">⏸ Kullanıcı tarafından durduruldu.</span>');
}
function resetFill() {
_animes = []; _idx = 0; _ok = 0; _skip = 0; _err = 0;
logBox.innerHTML = '';
logBox.style.display = 'none';
progWrap.style.display = 'none';
progBar.style.width = '0%';
progBar.classList.add('progress-bar-animated');
document.getElementById('reset-btn').style.display = 'none';
document.getElementById('start-btn').style.display = '';
updateCounts();
}
function updateCounts() {
document.getElementById('cnt-ok').textContent = _ok;
document.getElementById('cnt-skip').textContent = _skip;
document.getElementById('cnt-err').textContent = _err;
}
</script>
@endpush