Compare commits

..

2 Commits

4 changed files with 67 additions and 58 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,25 +0,0 @@
<IfModule mod_authz_core.c>
<FilesMatch ".*">
Require all denied
</FilesMatch>
<FilesMatch "\.log$">
Require all granted
</FilesMatch>
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
<FilesMatch "\.log$">
Order allow,deny
Allow from all
</FilesMatch>
</IfModule>
<IfModule mod_mime.c>
AddType text/plain .log
</IfModule>
<IfModule mod_dir.c>
DirectoryIndex index.php
</IfModule>
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<security>
<authorization>
<deny users="*" />
</authorization>
</security>
<requestFiltering>
<fileExtensions allowUnlisted="false">
<add fileExtension=".log" allowed="true" />
</fileExtensions>
</requestFiltering>
<staticContent>
<mimeMap fileExtension=".log" mimeType="text/plain" />
</staticContent>
<defaultDocument>
<files>
<add value="index.php" />
</files>
</defaultDocument>
<directoryBrowse enabled="false" />
</system.webServer>
</configuration>

View File

@@ -473,15 +473,24 @@ async function semanticSearch() {
const textAttr = (i.text_content || '').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
const startTime = i.start_time || (i.start_frame && i.fps ? i.start_frame / i.fps : 0);
const endTime = i.end_time || (i.end_frame && i.fps ? i.end_frame / i.fps : startTime + 10);
const thumbFrame = Math.round(startTime * 24);
return `<div class="chunk-item clickable"
data-file-uuid="${i.file_uuid}"
data-start-time="${startTime}"
data-end-time="${endTime}"
data-text="${textAttr}">
<div style="font-weight: 600; color: #1e40af;">${i.name} (${i.source || 'unknown'})</div>
<div class="chunk-time">${i.file_uuid} | ${startTime.toFixed(1)}s - ${endTime.toFixed(1)}s</div>
<div style="margin-top: 4px;">${i.text_content || '無內容'}</div>
<button class="play-btn">▶ 播放片段</button>
<div style="display:flex;gap:10px;">
<img class="search-thumb" loading="lazy"
data-src="${API_BASE}/file/${i.file_uuid}/thumbnail?api_key=${API_KEY}&frame=${thumbFrame}"
style="width:120px;height:68px;object-fit:cover;border-radius:4px;flex-shrink:0;background:#e2e8f0;"
alt="">
<div style="flex:1;">
<div style="font-weight:600;color:#1e40af;">${i.name} (${i.source || 'unknown'})</div>
<div class="chunk-time">${startTime.toFixed(1)}s - ${endTime.toFixed(1)}s${i.chunk_id ? ` | ${i.chunk_id}` : ''}</div>
<div style="margin-top:4px;font-size:13px;color:#555;">${i.text_content || ''}</div>
<button class="play-btn" style="margin-top:6px;">▶ 播放片段</button>
</div>
</div>
</div>`;
}).join('');
resultEl.innerHTML = `<p style="padding: 0.5rem; background: #d1fae5; border-radius: 4px; margin-bottom: 0.5rem;">找到 ${data.total || 0} 個身份相關片段</p>${resultsHtml || '<p>沒有結果</p>'}`;
@@ -498,23 +507,72 @@ async function semanticSearch() {
const textAttr = (r.text || '').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
const startTime = r.start_time || (r.start_frame && r.fps ? r.start_frame / r.fps : 0);
const endTime = r.end_time || (r.end_frame && r.fps ? r.end_frame / r.fps : startTime + 10);
const thumbFrame = Math.round(startTime * 24);
return `<div class="chunk-item clickable"
data-file-uuid="${r.file_uuid}"
data-start-time="${startTime}"
data-end-time="${endTime}"
data-text="${textAttr}">
<div class="chunk-time">${r.file_uuid} | ${startTime.toFixed(1)}s - ${endTime.toFixed(1)}s</div>
<div style="margin-top: 4px;">${r.text || '無內容'}</div>
<div class="badge-status completed" style="margin-top: 4px;">相似度: ${(r.score * 100).toFixed(1)}%</div>
<button class="play-btn">▶ 播放片段</button>
<div style="display:flex;gap:10px;">
<img class="search-thumb" loading="lazy"
data-src="${API_BASE}/file/${r.file_uuid}/thumbnail?api_key=${API_KEY}&frame=${thumbFrame}"
style="width:120px;height:68px;object-fit:cover;border-radius:4px;flex-shrink:0;background:#e2e8f0;"
alt="">
<div style="flex:1;">
<div class="chunk-time">${startTime.toFixed(1)}s - ${endTime.toFixed(1)}s${r.chunk_id ? ` | ${r.chunk_id}` : ''}</div>
<div style="margin-top:4px;font-size:13px;color:#555;">${r.text || ''}</div>
<div class="badge-status completed" style="margin-top:4px;">相似度: ${(r.score * 100).toFixed(1)}%</div>
<button class="play-btn" style="margin-top:6px;">▶ 播放片段</button>
</div>
</div>
</div>`;
}).join('');
resultEl.innerHTML = `<p style="padding: 0.5rem; background: #d1fae5; border-radius: 4px; margin-bottom: 0.5rem;">找到 ${data.total || 0} 個片段</p>${resultsHtml || '<p>沒有結果</p>'}`;
}
loadSearchThumbnails();
} catch (e) {
resultEl.innerHTML = '<p style="color:#ef4444; padding: 1rem;">錯誤: ' + e.message + '</p>';
}
}
function loadSearchThumbnails(){
const imgs = Array.from(document.querySelectorAll('img.search-thumb[data-src]'));
const MAX = 4;
let queue = [];
let inflight = 0;
function next(){
while (inflight < MAX && queue.length) {
const img = queue.shift();
inflight++;
img.onload = function(){ inflight--; next(); };
img.onerror = function(){ inflight--; next(); };
img.src = img.dataset.src;
}
}
if ('IntersectionObserver' in window) {
const obs = new IntersectionObserver(function(entries){
entries.forEach(function(e){
if (!e.isIntersecting) return;
var img = e.target;
if (img.dataset.loaded) return;
img.dataset.loaded = '1';
queue.push(img);
obs.unobserve(img);
next();
});
}, { rootMargin: '200px 0px' });
imgs.forEach(function(img){ obs.observe(img); });
} else {
imgs.forEach(function(img){
if (img.dataset.loaded) return;
img.dataset.loaded = '1';
queue.push(img);
});
next();
}
}
</script>
<?php get_footer(); ?>