diff --git a/main.py b/main.py index b8c868a..714f53e 100644 --- a/main.py +++ b/main.py @@ -19,6 +19,7 @@ import pathlib import re from concurrent.futures import ThreadPoolExecutor import asyncio +from collections import Counter @hydra.main(config_path='data/', config_name='config', version_base=None) def main(cfg: DictConfig): @@ -243,6 +244,84 @@ def main(cfg: DictConfig): scheduler_thread = threading.Thread(target=run_scheduler) scheduler_thread.daemon = True scheduler_thread.start() + + @app.get("/v1/hot_searches") + async def get_hot_searches(top_n: int = 5, last_n_lines: int = 2000): + """返回最热门的搜索词 + + Args: + top_n: 返回的热门搜索词数量,默认为5 + last_n_lines: 读取日志文件的最后行数,默认为1000行 + """ + try: + # 参数基本验证 + if top_n < 1: + top_n = 5 + if last_n_lines < 100: + last_n_lines = 1000 + + log_file_path = cfg.logging.log_file + if not os.path.exists(log_file_path): + logger.error(f"Log file does not exist: {log_file_path}") + raise HTTPException(status_code=404, detail="Log file does not exist") + + # 使用线程池异步读取日志文件的最后N行 + def read_last_n_lines(): + encodings = ['utf-8', 'gbk', 'iso-8859-1'] + + for encoding in encodings: + try: + with open(log_file_path, 'r', encoding=encoding) as f: + # 使用deque优化内存使用 + from collections import deque + return deque(f, last_n_lines) + except UnicodeDecodeError: + continue + except Exception as e: + logger.error(f"Error reading log file with {encoding}: {str(e)}") + continue + + raise HTTPException(status_code=500, detail="Unable to read log file with any encoding") + + # 读取日志文件最后N行 + log_content = await asyncio.wait_for( + asyncio.get_event_loop().run_in_executor(executor, read_last_n_lines), + timeout=10 + ) + + # 提取包含"AV code"但不包含"404"的行 + av_code_lines = [line for line in log_content if "AV code" in line and "404" not in line] + + # 从每行中提取代码 + search_terms = [] + for line in av_code_lines: + try: + parts = line.split(":") + if len(parts) >= 2: + search_term = parts[-1].strip().lower() + # 规范化搜索词,只保留字母和数字 + search_term = re.sub(r'[^a-zA-Z0-9]', '', search_term) + if search_term: + search_terms.append(search_term) + except Exception as e: + logger.warning(f"Error processing line: {str(e)}") + continue + + if not search_terms: + return {"status": "succeed", "data": []} + + # 统计每个搜索词的出现次数并获取前N名 + term_counts = Counter(search_terms) + top_terms = [term for term, _ in term_counts.most_common(top_n)] + + logger.info(f"Retrieved top {top_n} popular search terms from last {last_n_lines} lines") + return {"status": "succeed", "data": top_terms} + except asyncio.TimeoutError: + logger.error("Timeout while reading log file") + raise HTTPException(status_code=504, detail="Request timeout") + except Exception as e: + logger.error(f"Failed to obtain popular search terms: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/web/index.html b/web/index.html index 9691eaa..157b339 100644 --- a/web/index.html +++ b/web/index.html @@ -63,6 +63,16 @@
+ +
+
+ + 热门搜索: + +
+
+
+
diff --git a/web/script.js b/web/script.js index 4164247..aea88e8 100644 --- a/web/script.js +++ b/web/script.js @@ -54,15 +54,21 @@ function switchTab(tabName) { } } } + + // 当切换到搜索标签页时,获取热门搜索词 + if (tabName === 'search') { + fetchHotSearches(); + } } // 添加 API 配置 const API_CONFIG = { - BASE_URL: '/api/v1', + BASE_URL: '/app/v1', ENDPOINTS: { SEARCH: '/avcode', COLLECTIONS: '/hacg', - VIDEO: '/get_video' + VIDEO: '/get_video', + HOT_SEARCHES: '/hot_searches' // 添加热门搜索接口 } }; @@ -639,6 +645,11 @@ document.addEventListener('DOMContentLoaded', () => { } }); } + + // 如果当前是搜索标签页,获取热门搜索词 + if (currentTab === 'search') { + fetchHotSearches(); + } }); // 初始化复制按钮功能 @@ -986,6 +997,11 @@ document.addEventListener('DOMContentLoaded', () => { clearVideoUrl(); // 使用clearVideoUrl函数来处理 }); } + + // 如果当前是搜索标签页,获取热门搜索词 + if (currentTab === 'search') { + fetchHotSearches(); + } }); // 切换主题功能 @@ -1777,3 +1793,30 @@ function clearVideoUrl() { loadVideo(); } } + +// 添加获取热门搜索的函数 +async function fetchHotSearches() { + try { + const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.HOT_SEARCHES}`); + const data = await response.json(); + + if (data.status === 'succeed' && Array.isArray(data.data)) { + const hotSearchesContainer = document.getElementById('hotSearches'); + hotSearchesContainer.innerHTML = data.data + .map(term => ` + + `).join(''); + } + } catch (error) { + console.error('获取热门搜索失败:', error); + } +} + +// 添加点击热门搜索词的处理函数 +function searchWithTerm(term) { + const searchInput = document.getElementById('searchInput'); + searchInput.value = term; + searchMagnet(); +} diff --git a/web/style.css b/web/style.css index 8921e0f..9f7be18 100644 --- a/web/style.css +++ b/web/style.css @@ -214,34 +214,53 @@ body { border-color: var(--primary-color); } -.search-button, .copy-button { +/* 搜索按钮样式 */ +.search-button { background: var(--primary-color); color: white; font-weight: 600; border-radius: 4px; transition: all 0.2s ease; + padding: 0.5rem 1rem; + min-width: 100px; } -.search-button:hover, .copy-button:hover { +.search-button:hover { background: var(--primary-hover); transform: translateY(-1px); } -.search-button:active, .copy-button:active { +.search-button:active { transform: translateY(0); } -.copy-button { - background: var(--primary-color); +/* 统一按钮样式(复制按钮和下一个按钮) */ +.next-button, .copy-button { + background-color: var(--primary-color); color: white; font-weight: 600; border-radius: 4px; + padding: 0.5rem 1rem; transition: all 0.2s ease; + white-space: nowrap; + flex-shrink: 0; + min-width: fit-content; position: relative; overflow: hidden; } -.copy-button::before { +/* 确保下一个按钮靠右 */ +.next-button { + margin-left: auto; +} + +.next-button:hover, .copy-button:hover { + background: var(--primary-hover); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(27, 183, 110, 0.2); +} + +.next-button::before, .copy-button::before { content: ''; position: absolute; top: 0; @@ -257,14 +276,40 @@ body { transition: 0.5s; } -.copy-button:hover::before { +.next-button:hover::before, .copy-button:hover::before { left: 100%; } -.copy-button:hover { - background: var(--primary-hover); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(27, 183, 110, 0.2); +/* 视频控制区域样式 */ +.video-controls { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + border-radius: 0.5rem; + background: rgba(0, 0, 0, 0.05); + margin: 1rem 0; + gap: 1rem; +} + +/* 左侧自动播放开关组 */ +.video-controls-left { + display: flex; + align-items: center; + gap: 1rem; +} + +/* 自动播放开关容器 */ +.autoplay-toggle { + display: flex; + align-items: center; + white-space: nowrap; + flex-shrink: 0; +} + +/* 移除旧的margin-left设置 */ +.video-controls .autoplay-toggle + .autoplay-toggle { + margin-left: 0; } .tab-container { @@ -331,13 +376,6 @@ body { background: #f59e0b; } -/* 确保语言切换按钮与主题切换按钮大小一致 */ -#languageToggle { - display: flex; - align-items: center; - justify-content: center; -} - /* 修改标签颜色 */ .tag[data-type="hd"] { background: rgba(59, 130, 246, 0.1); @@ -830,7 +868,7 @@ body.light-theme select option { color: #ffffff; } -/* 加载动画容器 */ +/* 修改加载容器样式 */ .loading-container { display: flex; flex-direction: column; @@ -839,11 +877,8 @@ body.light-theme select option { gap: 16px; padding: 32px; width: 100%; - position: static; - background: var(--background-dark); - border-radius: 8px; - margin: 20px auto; - max-width: 400px; + background: transparent; + box-shadow: none; } /* 加载动画圆圈 */ @@ -869,21 +904,12 @@ body.light-theme select option { color: var(--text-secondary); font-size: 14px; text-align: center; - margin: 0 auto; } -/* 加载文本点动画 */ -.loading-text::after { - content: ''; - animation: loading-dots 1.5s steps(4, end) infinite; -} - -/* 点动画关键帧 */ -@keyframes loading-dots { - 0%, 20% { content: ''; } - 40% { content: '.'; } - 60% { content: '..'; } - 80%, 100% { content: '...'; } +/* 确保浅色主题下的加载状态也是完全透明的 */ +[data-theme="light"] .loading-container { + background: transparent; + box-shadow: none; } /* 搜索结果容器 */ @@ -894,7 +920,7 @@ body.light-theme select option { /* 搜索结果中的加载容器 */ #searchResults .loading-container { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + box-shadow: none; } /* Logo 样式 */ @@ -1060,207 +1086,70 @@ body.light-theme select option { transition: all 0.3s ease; border-radius: 12px; overflow: hidden; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); - cursor: pointer; - min-height: 300px; - background-color: rgba(0, 0, 0, 0.05); + background-color: transparent; /* 移除背景色 */ } -/* 添加提示文本,告诉用户可以点击不同区域 */ +/* 移除点击区域提示框样式 */ .cover-image-container::after { - content: '点击不同区域查看更多图片'; - position: absolute; - bottom: 10px; - left: 50%; - transform: translateX(-50%); - background-color: rgba(0, 0, 0, 0.7); - color: white; - padding: 5px 10px; - border-radius: 4px; - font-size: 12px; - opacity: 0; - transition: opacity 0.3s ease; - z-index: 4; - pointer-events: none; + display: none; /* 移除提示框 */ } -.cover-image-container:hover::after { - opacity: 1; -} - -/* 增强模态框中的图片效果 */ -.modal-image { - max-width: 100%; - max-height: 90vh; - object-fit: contain; - border-radius: 8px; - box-shadow: 0 5px 25px rgba(0, 0, 0, 0.5); - transition: opacity 0.3s ease, transform 0.3s ease; - opacity: 0; -} - -.image-modal.active .modal-image { - opacity: 1; - animation: zoomIn 0.3s forwards; -} - -@keyframes zoomIn { - from { - transform: scale(0.9); - opacity: 0; - } - to { - transform: scale(1); - opacity: 1; - } -} - -/* 修改3D卡片效果样式,减小抖动幅度 */ -.card-3d { - transform-style: preserve-3d; - transform: perspective(1500px); /* 增加透视距离,减小变形效果 */ - transition: transform 0.3s ease, box-shadow 0.3s ease; - will-change: transform, box-shadow; - border-radius: 12px; - overflow: hidden; - position: relative; - cursor: pointer; -} - -/* 修改光影效果,使光线更均匀 */ -.card-3d::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient( - 135deg, - rgba(255, 255, 255, 0.1) 0%, - rgba(255, 255, 255, 0.05) 50%, - rgba(255, 255, 255, 0.1) 100% - ); - z-index: 3; - pointer-events: none; - transition: opacity 0.3s ease; -} - -.card-3d:hover::before { - opacity: 0.6; -} - -/* 移除辅助线 */ -.card-3d:hover::after { - display: none; -} - -/* 修改图片过渡效果,使其更平滑 */ -.card-3d img { - transition: transform 0.3s ease; - transform-origin: center center; - backface-visibility: hidden; - position: relative; - z-index: 2; - filter: brightness(1.02); /* 轻微提高亮度 */ -} - -/* 确保图片加载后显示正常 */ -.cover-image { - opacity: 0; - transition: opacity 0.3s ease, transform 0.3s ease; - position: relative; - z-index: 2; -} - -.cover-image.loaded { - opacity: 1; -} - -/* 大图预览模态框样式 */ -.image-modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.95); +/* 视频控制区域样式 */ +.video-controls { display: flex; - justify-content: center; + justify-content: space-between; /* 两端对齐 */ align-items: center; - z-index: 1000; - opacity: 0; - visibility: hidden; - transition: all 0.3s ease; - cursor: pointer; + padding: 0.75rem 1rem; + border-radius: 0.5rem; + background: rgba(0, 0, 0, 0.05); + margin: 1rem 0; + gap: 1rem; } -.image-modal.active { - opacity: 1; - visibility: visible; -} - -.image-modal.hidden { - display: none; -} - -.modal-content { - position: relative; - max-width: 96vw; - max-height: 90vh; +/* 左侧自动播放开关组 */ +.video-controls-left { display: flex; - justify-content: center; align-items: center; - transition: transform 0.3s ease; + gap: 1rem; + margin-right: auto; /* 确保左侧元素靠左 */ } -.modal-close { - display: none; +/* 自动播放开关容器 */ +.autoplay-toggle { + display: flex; + align-items: center; + white-space: nowrap; + flex-shrink: 0; } -.modal-close:hover { - transform: scale(1.1); -} - -.modal-close svg { - width: 24px; - height: 24px; -} - -/* 修改点击关闭提示,支持中英文 */ -.image-modal::after { - content: attr(data-close-text); - position: fixed; - bottom: 20px; - left: 50%; - transform: translateX(-50%); - color: rgba(255, 255, 255, 0.6); - font-size: 14px; - padding: 8px 16px; - background: rgba(0, 0, 0, 0.5); - border-radius: 4px; - pointer-events: none; +/* 移除旧的margin-left设置 */ +.video-controls .autoplay-toggle + .autoplay-toggle { + margin-left: 0; /* 移除旧的margin设置 */ } /* 下一个按钮样式 */ .next-button { - background-color: var(--primary-color); - color: var(--text-color); - border-radius: 0.375rem; - font-weight: 700; /* 加粗文字 */ - transition: all 0.2s; - padding: 0.5rem 1rem; - white-space: nowrap; /* 防止文字换行 */ - flex-shrink: 0; /* 防止缩小 */ + margin-left: auto; /* 确保按钮靠右 */ } -.next-button:hover { - opacity: 0.9; - transform: translateY(-1px); -} - -.next-button:active { - transform: translateY(0); +/* 确保移动端布局正确 */ +@media (max-width: 640px) { + .video-controls { + padding: 0.5rem; + gap: 0.5rem; + } + + .video-controls-left { + gap: 0.5rem; + } + + .autoplay-toggle label { + font-size: 0.875rem; + } + + .next-button { + padding: 0.5rem 0.75rem; + } } /* 视频播放器容器样式 */ @@ -1332,53 +1221,6 @@ body.light-theme select option { border: 1px solid rgba(220, 38, 38, 0.2); } -/* 视频控制区域样式 */ -.video-controls { - background: rgba(0, 0, 0, 0.05); - padding: 0.75rem 1rem; - border-radius: 0.5rem; - display: flex; - align-items: center; - flex-wrap: nowrap; /* 防止换行 */ - gap: 1rem; /* 添加间距 */ -} - -/* 自动播放开关容器 */ -.autoplay-toggle { - display: flex; - align-items: center; - white-space: nowrap; /* 防止文字换行 */ - flex-shrink: 0; /* 防止缩小 */ -} - -/* 确保开关文字不换行 */ -.autoplay-toggle label { - white-space: nowrap; -} - -/* 调整开关间距 */ -.video-controls .autoplay-toggle + .autoplay-toggle { - margin-left: 1rem; -} - -/* 确保移动端布局正确 */ -@media (max-width: 640px) { - .video-controls { - flex-direction: row; - justify-content: space-between; - padding: 0.5rem; - gap: 0.5rem; - } - - .autoplay-toggle label { - font-size: 0.875rem; /* 稍微减小字体大小 */ - } - - .next-button { - padding: 0.5rem 0.75rem; - } -} - /* 自动播放开关样式 */ .toggle-switch { position: relative; @@ -1780,3 +1622,57 @@ main { border-radius: 4px; pointer-events: none; } + +/* 热门搜索词区域样式 */ +.hot-searches { + padding: 8px 12px; + background: var(--card-dark); + border: 1px solid var(--border-color); + border-radius: 8px; + margin: 0 4px; +} + +/* 热门搜索标签样式 */ +.hot-search-tag { + display: inline-flex; + align-items: center; + padding: 4px 12px; + border-radius: 16px; + font-size: 13px; + background: rgba(var(--primary-color-rgb), 0.1); + color: var(--primary-color); + cursor: pointer; + transition: all 0.2s; + border: 1px solid rgba(var(--primary-color-rgb), 0.2); +} + +.hot-search-tag:hover { + background: rgba(var(--primary-color-rgb), 0.15); + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* 热门搜索文字样式 */ +.hot-searches .label-text { + color: var(--text-primary); + font-weight: 500; +} + +/* 日间主题适配 */ +[data-theme="light"] .hot-searches { + background: #ffffff; + border-color: #e5e7eb; +} + +[data-theme="light"] .hot-search-tag { + background: rgba(var(--primary-color-rgb), 0.08); + border-color: rgba(var(--primary-color-rgb), 0.15); +} + +[data-theme="light"] .hot-search-tag:hover { + background: rgba(var(--primary-color-rgb), 0.12); +} + +[data-theme="light"] .hot-searches .label-text { + color: #374151; +}