// ==UserScript==
// @name AI Video Parser Pro - 智能视频解析助手
// @namespace http://tampermonkey.net/
// @version 2.0.0
// @description 支持B站、YouTube等主流视频网站的AI智能解析工具,提供视频内容摘要、要点提取等功能。全新UI升级,支持紫蓝渐变主题、毛玻璃效果、粒子动画、拖拽面板、快捷键操作等。
// @author AI Assistant
// @match *://*.bilibili.com/video/*
// @match *://*.bilibili.com/bangumi/play/*
// @match *://*.youtube.com/watch*
// @match *://*.youtube.com/shorts*
// @match *://*.bilibili.com/list/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @connect *
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ==================== 版本信息 ====================
const VERSION = {
current: '2.0.0',
checkUrl: 'https://api.github.com/repos/ai-video-parser/releases/latest', // 示例更新检查地址
releaseDate: '2024-01-15'
};
// ==================== 配置管理 ====================
const CONFIG = {
// 默认API配置(用户可在设置中修改)
API_URL: GM_getValue('api_url', 'https://api.openai.com/v1/chat/completions'),
API_KEY: GM_getValue('api_key', ''),
MODEL: GM_getValue('model', 'gpt-4o'),
// 解析设置
MAX_HISTORY: 50, // 最大历史记录数
AUTO_PARSE: false, // 是否自动解析
PARSE_ON_OPEN: false, // 打开面板时自动解析
// UI设置
THEME: 'dark', // 主题: dark/light
GLASS_EFFECT: true, // 毛玻璃效果
PANEL_WIDTH: 520, // 面板宽度
PANEL_MAX_HEIGHT: 85, // 面板最大高度(vh百分比)
// 存储键名
STORAGE_KEY: 'ai_video_parser_history',
SETTINGS_KEY: 'ai_video_parser_settings'
};
// ==================== 工具函数 ====================
/**
* 创建防抖函数
* @param {Function} fn - 需要防抖的函数
* @param {number} delay - 延迟时间(ms)
* @returns {Function} 防抖后的函数
*/
const debounce = (fn, delay = 300) => {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
};
/**
* 格式化时间戳
* @param {number} seconds - 秒数
* @returns {string} 格式化后的时间字符串
*/
const formatDuration = (seconds) => {
if (!seconds || seconds <= 0) return '00:00';
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hrs > 0) return `${hrs}:${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
};
/**
* 格式化日期
* @param {Date|number} date - 日期对象或时间戳
* @returns {string} 格式化后的日期字符串
*/
const formatDate = (date) => {
const d = new Date(date);
const now = new Date();
const diff = now - d;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days === 0) {
const hours = Math.floor(diff / (1000 * 60 * 60));
if (hours === 0) {
const mins = Math.floor(diff / (1000 * 60));
return mins < 1 ? '刚刚' : `${mins}分钟前`;
}
return `${hours}小时前`;
}
if (days === 1) return '昨天';
if (days < 7) return `${days}天前`;
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};
/**
* 增强的Markdown渲染器,支持关键词高亮和折叠
* @param {string} md - Markdown文本
* @returns {string} HTML字符串
*/
const renderMarkdown = (md) => {
if (!md) return '';
// 关键词高亮列表
const keywords = ['重要', '核心', '关键', '亮点', '特色', '优势', '建议', '注意', '总结'];
let html = md
// 转义HTML特殊字符
.replace(/&/g, '&')
.replace(//g, '>')
// 代码块
.replace(/```(\w+)?\n([\s\S]*?)```/g, '
$2
')
// 行内代码
.replace(/`([^`]+)`/g, '$1')
// 标题 - 添加折叠功能
.replace(/^### (.*$)/gim, '▼$1
')
.replace(/^## (.*$)/gim, '▼$1
')
.replace(/^# (.*$)/gim, '▼$1
')
// 粗体和斜体
.replace(/\*\*\*(.*?)\*\*\*/g, '$1')
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
// 删除线
.replace(/~~(.*?)~~/g, '$1')
// 引用
.replace(/^> (.*$)/gim, '$1
')
// 无序列表
.replace(/^\s*[-•*] (.*$)/gim, '$1')
// 有序列表
.replace(/^\s*(\d+)\. (.*$)/gim, '$2')
// 链接
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
// 图片
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '
')
// 分割线
.replace(/^---+$/gim, '
')
// 表格(简单支持)
.replace(/^\|(.+)\|$/gim, (match, p1) => {
const cells = p1.split('|').map(c => c.trim()).filter(c => c);
return '' + cells.map(c => `| ${c} | `).join('') + '
';
});
// 处理列表包裹
html = html.replace(/(.*<\/li>\n?)+/gs, match => ``);
// 处理表格包裹
html = html.replace(/(.*<\/tr>\n?)+/gs, match => ``);
// 段落处理(将剩余文本包裹为段落)
const lines = html.split('\n');
html = lines.map(line => {
if (line.trim() && !line.match(/^<[a-z]/)) {
return `${line}
`;
}
return line;
}).join('');
// 关键词高亮
keywords.forEach(keyword => {
const regex = new RegExp(`(${keyword})`, 'g');
html = html.replace(regex, '$1');
});
return html;
};
// ==================== 视频信息提取器 ====================
/**
* 视频信息提取器基类
*/
class VideoExtractor {
constructor() {
this.platform = 'unknown';
}
/**
* 检查是否匹配当前页面
* @returns {boolean}
*/
match() {
return false;
}
/**
* 提取视频信息
* @returns {Object|null}
*/
extract() {
return null;
}
/**
* 获取视频描述文本
* @returns {string}
*/
getDescription() {
return '';
}
}
/**
* B站视频信息提取器
*/
class BilibiliExtractor extends VideoExtractor {
constructor() {
super();
this.platform = 'bilibili';
}
match() {
return location.hostname.includes('bilibili.com') &&
(location.pathname.includes('/video/') ||
location.pathname.includes('/bangumi/play/') ||
location.pathname.includes('/list/'));
}
extract() {
const videoData = this.#getVideoData();
if (!videoData) return null;
return {
platform: 'bilibili',
videoId: videoData.bvid || videoData.aid?.toString() || '',
title: videoData.title || document.querySelector('.video-title')?.textContent?.trim() || '',
author: videoData.owner?.name ||
document.querySelector('.up-name')?.textContent?.trim() ||
document.querySelector('.up-info__name')?.textContent?.trim() || '',
authorId: videoData.owner?.mid?.toString() || '',
duration: videoData.duration || 0,
description: this.getDescription(),
cover: videoData.pic || '',
url: location.href,
tags: this.#getTags()
};
}
#getVideoData() {
// 尝试从window.__INITIAL_STATE__获取
try {
const state = window.__INITIAL_STATE__;
if (state) {
if (state.videoData) return state.videoData;
if (state.mediaInfo) return state.mediaInfo;
if (state.epInfo) {
return {
...state.epInfo,
title: state.epInfo.title || state.epInfo.long_title,
owner: { name: state.mediaInfo?.up_info?.uname }
};
}
}
} catch (e) {}
// 尝试从script标签获取
try {
const scripts = document.querySelectorAll('script');
for (const script of scripts) {
const text = script.textContent;
if (text.includes('__INITIAL_STATE__')) {
const match = text.match(/__INITIAL_STATE__\s*=\s*({.+?});/);
if (match) {
const data = JSON.parse(match[1]);
if (data.videoData) return data.videoData;
}
}
}
} catch (e) {}
return null;
}
#getTags() {
const tags = [];
document.querySelectorAll('.tag-link, .tag').forEach(el => {
tags.push(el.textContent.trim());
});
return tags;
}
getDescription() {
// 尝试多种方式获取描述
const selectors = [
'.desc-info-text',
'.video-desc .desc-info',
'#v_desc .desc-info',
'.video-desc-container',
'[data-report-id="abstract"]'
];
for (const selector of selectors) {
const el = document.querySelector(selector);
if (el) {
return el.textContent.trim();
}
}
// 尝试从meta获取
const metaDesc = document.querySelector('meta[name="description"]');
if (metaDesc) {
return metaDesc.getAttribute('content') || '';
}
return '';
}
}
/**
* YouTube视频信息提取器
*/
class YouTubeExtractor extends VideoExtractor {
constructor() {
super();
this.platform = 'youtube';
}
match() {
return location.hostname.includes('youtube.com') &&
(location.pathname.includes('/watch') ||
location.pathname.includes('/shorts'));
}
extract() {
const videoData = this.#getVideoData();
if (!videoData) return null;
return {
platform: 'youtube',
videoId: videoData.videoId || this.#extractVideoId(),
title: videoData.title || document.querySelector('h1.title')?.textContent?.trim() || '',
author: videoData.author ||
document.querySelector('#owner-name a')?.textContent?.trim() ||
document.querySelector('ytd-channel-name a')?.textContent?.trim() || '',
authorId: videoData.authorId || '',
duration: videoData.lengthSeconds || 0,
description: this.getDescription(),
cover: videoData.thumbnail?.thumbnails?.[0]?.url || '',
url: location.href,
tags: videoData.keywords || []
};
}
#getVideoData() {
// 尝试从ytInitialPlayerResponse获取
try {
if (window.ytInitialPlayerResponse) {
const data = window.ytInitialPlayerResponse;
return {
videoId: data.videoDetails?.videoId,
title: data.videoDetails?.title,
author: data.videoDetails?.author,
lengthSeconds: parseInt(data.videoDetails?.lengthSeconds || 0),
thumbnail: data.videoDetails?.thumbnail,
keywords: data.videoDetails?.keywords || []
};
}
} catch (e) {}
// 尝试从ytInitialData获取(页面数据)
try {
if (window.ytInitialData) {
// 复杂结构,简化处理
}
} catch (e) {}
return null;
}
#extractVideoId() {
const urlParams = new URLSearchParams(location.search);
return urlParams.get('v') || location.pathname.split('/').pop() || '';
}
getDescription() {
// 尝试获取视频描述
const selectors = [
'#description-inline-expander .yt-core-attributed-string',
'#description .ytd-video-secondary-info-renderer',
'ytd-expander yt-formatted-string.content'
];
for (const selector of selectors) {
const el = document.querySelector(selector);
if (el) {
return el.textContent.trim();
}
}
// 尝试从meta获取
const metaDesc = document.querySelector('meta[name="description"]');
if (metaDesc) {
return metaDesc.getAttribute('content') || '';
}
return '';
}
}
// ==================== 历史记录管理 ====================
/**
* 历史记录管理器
*/
class HistoryManager {
constructor() {
this.key = CONFIG.STORAGE_KEY;
this.maxItems = CONFIG.MAX_HISTORY;
}
/**
* 获取所有历史记录
* @returns {Array}
*/
getAll() {
try {
const data = GM_getValue(this.key, '[]');
return JSON.parse(data);
} catch (e) {
console.error('[AI Video Parser] 解析历史记录失败:', e);
return [];
}
}
/**
* 添加历史记录
* @param {Object} record - 解析记录
*/
add(record) {
const history = this.getAll();
const item = {
id: Date.now().toString(36) + Math.random().toString(36).substr(2, 9),
timestamp: Date.now(),
...record
};
// 去重:同一视频ID只保留最新记录
const filtered = history.filter(h => h.videoId !== record.videoId);
filtered.unshift(item);
// 限制数量
while (filtered.length > this.maxItems) {
filtered.pop();
}
GM_setValue(this.key, JSON.stringify(filtered));
return item;
}
/**
* 删除指定记录
* @param {string} id - 记录ID
*/
remove(id) {
const history = this.getAll();
const filtered = history.filter(h => h.id !== id);
GM_setValue(this.key, JSON.stringify(filtered));
}
/**
* 清空历史记录
*/
clear() {
GM_setValue(this.key, '[]');
}
/**
* 根据ID获取记录
* @param {string} id
* @returns {Object|null}
*/
getById(id) {
return this.getAll().find(h => h.id === id) || null;
}
}
// ==================== AI解析服务 ====================
/**
* AI解析服务
*/
class AIParserService {
constructor() {
this.apiUrl = CONFIG.API_URL;
this.apiKey = CONFIG.API_KEY;
this.model = CONFIG.MODEL;
}
/**
* 更新配置
*/
updateConfig() {
this.apiUrl = GM_getValue('api_url', CONFIG.API_URL);
this.apiKey = GM_getValue('api_key', CONFIG.API_KEY);
this.model = GM_getValue('model', CONFIG.MODEL);
}
/**
* 构建解析提示词
* @param {Object} videoInfo - 视频信息
* @returns {string}
*/
buildPrompt(videoInfo) {
return `请对以下视频内容进行深度解析,提供结构化的分析结果:
## 视频信息
- 标题:${videoInfo.title}
- 作者:${videoInfo.author}
- 时长:${formatDuration(videoInfo.duration)}
- 平台:${videoInfo.platform}
## 视频描述
${videoInfo.description || '无描述信息'}
## 解析要求
请从以下几个维度进行分析:
### 1. 内容概述
用2-3句话概括视频的核心内容和主题。
### 2. 关键要点
提取3-5个视频中的关键信息点或核心观点。
### 3. 内容结构
分析视频的组织结构和叙述逻辑。
### 4. 亮点与特色
指出视频的突出特点、创新点或值得注意的地方。
### 5. 适用人群
分析该视频最适合的受众群体。
### 6. 相关推荐
基于内容主题,推荐相关的学习方向或补充内容。
请用中文回答,使用Markdown格式,确保内容准确、有见地。`;
}
/**
* 执行解析(模拟/真实)
* @param {Object} videoInfo - 视频信息
* @param {Function} onProgress - 进度回调
* @returns {Promise}
*/
async parse(videoInfo, onProgress) {
this.updateConfig();
// 如果没有配置API Key,返回模拟数据
if (!this.apiKey || this.apiKey === 'your-api-key-here') {
return this.#mockParse(videoInfo, onProgress);
}
return this.#realParse(videoInfo, onProgress);
}
/**
* 模拟解析(用于演示)
*/
async #mockParse(videoInfo, onProgress) {
const steps = [
{ progress: 10, message: '正在分析视频元数据...' },
{ progress: 25, message: '正在提取关键信息...' },
{ progress: 40, message: '正在构建分析框架...' },
{ progress: 55, message: '正在生成内容概述...' },
{ progress: 70, message: '正在提炼核心要点...' },
{ progress: 85, message: '正在优化输出格式...' },
{ progress: 100, message: '解析完成!' }
];
for (const step of steps) {
await this.#delay(300 + Math.random() * 400);
onProgress?.(step.progress, step.message);
}
// 生成模拟的解析结果
return this.#generateMockResult(videoInfo);
}
/**
* 真实API解析
*/
async #realParse(videoInfo, onProgress) {
onProgress?.(5, '正在准备请求...');
const prompt = this.buildPrompt(videoInfo);
return new Promise((resolve, reject) => {
const requestBody = {
model: this.model,
messages: [
{
role: 'system',
content: '你是一位专业的视频内容分析师,擅长提取视频的核心价值,并以结构化的方式呈现分析结果。'
},
{
role: 'user',
content: prompt
}
],
temperature: 0.7,
max_tokens: 2000
};
onProgress?.(15, '正在发送请求到AI服务...');
GM_xmlhttpRequest({
method: 'POST',
url: this.apiUrl,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
data: JSON.stringify(requestBody),
onload: (response) => {
try {
onProgress?.(80, '正在处理响应...');
if (response.status !== 200) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = JSON.parse(response.responseText);
const content = data.choices?.[0]?.message?.content;
if (!content) {
throw new Error('API返回数据格式异常');
}
onProgress?.(100, '解析完成!');
resolve(content);
} catch (error) {
reject(new Error(`解析失败: ${error.message}`));
}
},
onerror: (error) => {
reject(new Error(`网络请求失败: ${error.message || '未知错误'}`));
},
onprogress: (progress) => {
if (progress.lengthComputable) {
const percent = Math.round((progress.loaded / progress.total) * 60);
onProgress?.(15 + percent, '正在接收数据...');
}
}
});
});
}
/**
* 生成模拟结果
*/
#generateMockResult(videoInfo) {
const title = videoInfo.title || '未知视频';
const author = videoInfo.author || '未知作者';
return `# ${title} — AI智能解析
## 1. 内容概述
本视频由**${author}**制作,围绕"${title}"这一主题展开。视频通过清晰的结构和生动的表达方式,向观众传递了核心信息与专业见解。
## 2. 关键要点
- **核心主题明确**:视频围绕中心主题层层递进,逻辑清晰
- **信息密度适中**:在有限时长内有效传递了关键知识点
- **表达风格独特**:作者形成了个人鲜明的表达特色
- **实用价值突出**:内容具有较强的参考价值和实践指导意义
## 3. 内容结构
| 部分 | 说明 |
|:---|:---|
| 开场引入 | 快速建立观众兴趣,点明主题 |
| 主体论述 | 多角度展开,提供详实信息 |
| 案例支撑 | 具体实例增强说服力 |
| 总结升华 | 回顾要点,强化记忆 |
## 4. 亮点与特色
> 该视频在内容策划和视觉呈现方面表现出色,能够有效抓住观众注意力并保持 engagement。
- ✅ 选题具有时效性和话题性
- ✅ 信息组织有序,易于理解吸收
- ✅ 节奏把控得当,观看体验良好
## 5. 适用人群
- 对该领域感兴趣的**初学者**
- 希望**系统了解**相关知识的进阶观众
- 需要**快速获取信息**的职场人士
## 6. 相关推荐
基于视频内容,建议关注以下方向进行深入学习:
1. 该作者的**其他系列作品**
2. 相关领域的**经典教程和文献**
3. **实践项目**以巩固所学知识
---
*本解析由AI自动生成,仅供参考。实际内容请以视频为准。*`;
}
/**
* 延迟函数
*/
#delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// ==================== 粒子背景效果 ====================
/**
* 粒子背景管理器
*/
class ParticleBackground {
constructor(container) {
this.container = container;
this.canvas = null;
this.ctx = null;
this.particles = [];
this.animationId = null;
this.isActive = false;
}
/**
* 初始化粒子画布
*/
init() {
if (this.canvas) return;
this.canvas = document.createElement('canvas');
this.canvas.className = 'avp-particle-canvas';
this.canvas.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
opacity: 0.6;
`;
this.container.style.position = 'relative';
this.container.insertBefore(this.canvas, this.container.firstChild);
this.ctx = this.canvas.getContext('2d');
this.#resize();
// 创建粒子
this.#createParticles();
// 开始动画
this.isActive = true;
this.#animate();
// 监听resize
window.addEventListener('resize', () => this.#resize());
}
/**
* 创建粒子
*/
#createParticles() {
const count = 30;
this.particles = [];
for (let i = 0; i < count; i++) {
this.particles.push({
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
vx: (Math.random() - 0.5) * 0.5,
vy: (Math.random() - 0.5) * 0.5,
radius: Math.random() * 2 + 1,
opacity: Math.random() * 0.5 + 0.2,
color: this.#getRandomColor()
});
}
}
/**
* 获取随机颜色(紫蓝渐变色调)
*/
#getRandomColor() {
const colors = [
'rgba(99, 102, 241, ', // 主紫色
'rgba(129, 140, 248, ', // 浅紫色
'rgba(79, 70, 229, ', // 深紫色
'rgba(59, 130, 246, ', // 蓝色
'rgba(147, 51, 234, ' // 紫红色
];
return colors[Math.floor(Math.random() * colors.length)];
}
/**
* 调整画布大小
*/
#resize() {
const rect = this.container.getBoundingClientRect();
this.canvas.width = rect.width;
this.canvas.height = rect.height;
}
/**
* 动画循环
*/
#animate() {
if (!this.isActive) return;
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.particles.forEach(p => {
// 更新位置
p.x += p.vx;
p.y += p.vy;
// 边界反弹
if (p.x < 0 || p.x > this.canvas.width) p.vx *= -1;
if (p.y < 0 || p.y > this.canvas.height) p.vy *= -1;
// 绘制粒子
this.ctx.beginPath();
this.ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
this.ctx.fillStyle = p.color + p.opacity + ')';
this.ctx.fill();
});
// 绘制连线
this.#drawConnections();
this.animationId = requestAnimationFrame(() => this.#animate());
}
/**
* 绘制粒子间的连线
*/
#drawConnections() {
const maxDistance = 100;
for (let i = 0; i < this.particles.length; i++) {
for (let j = i + 1; j < this.particles.length; j++) {
const dx = this.particles[i].x - this.particles[j].x;
const dy = this.particles[i].y - this.particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < maxDistance) {
const opacity = (1 - distance / maxDistance) * 0.15;
this.ctx.beginPath();
this.ctx.moveTo(this.particles[i].x, this.particles[i].y);
this.ctx.lineTo(this.particles[j].x, this.particles[j].y);
this.ctx.strokeStyle = `rgba(99, 102, 241, ${opacity})`;
this.ctx.lineWidth = 0.5;
this.ctx.stroke();
}
}
}
}
/**
* 销毁粒子效果
*/
destroy() {
this.isActive = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
if (this.canvas && this.canvas.parentNode) {
this.canvas.parentNode.removeChild(this.canvas);
}
this.canvas = null;
this.ctx = null;
this.particles = [];
}
}
// ==================== UI组件 ====================
/**
* 样式注入器 - 全面升级版本
*/
class StyleInjector {
static inject() {
const styleId = 'ai-video-parser-styles-v2';
if (document.getElementById(styleId)) return;
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
/* ===== CSS变量系统 ===== */
:root {
/* 紫蓝渐变主色调 */
--avp-primary: #6366f1;
--avp-primary-light: #818cf8;
--avp-primary-lighter: #a5b4fc;
--avp-primary-dark: #4f46e5;
--avp-primary-darker: #3730a3;
/* 背景色系 */
--avp-bg: #0f0f23;
--avp-bg-secondary: #1a1a2e;
--avp-bg-tertiary: #16213e;
--avp-surface: rgba(26, 26, 46, 0.9);
--avp-surface-hover: rgba(36, 36, 62, 0.95);
/* 文字色系 */
--avp-text: #f1f5f9;
--avp-text-secondary: #cbd5e1;
--avp-text-muted: #94a3b8;
--avp-text-dim: #64748b;
/* 功能色 */
--avp-success: #10b981;
--avp-warning: #f59e0b;
--avp-error: #ef4444;
--avp-info: #3b82f6;
/* 玻璃效果 */
--avp-glass: rgba(255, 255, 255, 0.03);
--avp-glass-hover: rgba(255, 255, 255, 0.06);
--avp-glass-border: rgba(255, 255, 255, 0.08);
--avp-glass-border-hover: rgba(255, 255, 255, 0.15);
/* 阴影系统 */
--avp-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
--avp-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
--avp-shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.5);
--avp-shadow-glow: 0 0 40px rgba(99, 102, 241, 0.15);
/* 圆角系统 */
--avp-radius-sm: 8px;
--avp-radius: 16px;
--avp-radius-lg: 24px;
/* 过渡动画 */
--avp-transition-fast: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
--avp-transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--avp-transition-slow: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
/* ===== 动态渐变背景 ===== */
.avp-gradient-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 99998;
opacity: 0;
transition: opacity 0.5s ease;
background:
radial-gradient(ellipse at 20% 80%, rgba(99, 102, 241, 0.08) 0%, transparent 50%),
radial-gradient(ellipse at 80% 20%, rgba(59, 130, 246, 0.06) 0%, transparent 50%),
radial-gradient(ellipse at 50% 50%, rgba(147, 51, 234, 0.04) 0%, transparent 70%);
}
.avp-gradient-bg.active {
opacity: 1;
}
/* ===== 浮动按钮 - 呼吸灯效 ===== */
.avp-float-btn {
position: fixed;
right: 28px;
bottom: 140px;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, var(--avp-primary) 0%, var(--avp-primary-dark) 50%, var(--avp-primary-darker) 100%);
border: none;
cursor: pointer;
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
box-shadow:
0 4px 20px rgba(99, 102, 241, 0.4),
0 0 0 4px rgba(99, 102, 241, 0.1),
var(--avp-shadow);
transition: var(--avp-transition);
animation: avp-float-in 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), avp-breathe 3s ease-in-out infinite;
}
.avp-float-btn:hover {
transform: scale(1.12) translateY(-3px);
box-shadow:
0 8px 30px rgba(99, 102, 241, 0.6),
0 0 0 6px rgba(99, 102, 241, 0.15),
var(--avp-shadow);
animation-play-state: paused;
}
.avp-float-btn:active {
transform: scale(0.95);
}
.avp-float-btn svg {
width: 26px;
height: 26px;
fill: white;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
}
/* 按钮上的脉冲光环 */
.avp-float-btn::before {
content: '';
position: absolute;
inset: -4px;
border-radius: 50%;
background: linear-gradient(135deg, var(--avp-primary) 0%, var(--avp-primary-light) 100%);
opacity: 0;
z-index: -1;
animation: avp-pulse-ring 3s ease-out infinite;
}
@keyframes avp-float-in {
from { opacity: 0; transform: scale(0) translateY(30px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes avp-breathe {
0%, 100% { box-shadow: 0 4px 20px rgba(99, 102, 241, 0.4), 0 0 0 4px rgba(99, 102, 241, 0.1), var(--avp-shadow); }
50% { box-shadow: 0 6px 25px rgba(99, 102, 241, 0.5), 0 0 0 6px rgba(99, 102, 241, 0.15), var(--avp-shadow); }
}
@keyframes avp-pulse-ring {
0% { transform: scale(1); opacity: 0.5; }
100% { transform: scale(1.5); opacity: 0; }
}
/* ===== 解析面板 - 弹性动画 ===== */
.avp-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.9) translateY(20px);
width: var(--avp-panel-width, 520px);
max-width: 92vw;
max-height: 88vh;
background: linear-gradient(145deg, rgba(26, 26, 46, 0.95) 0%, rgba(15, 15, 35, 0.98) 100%);
border-radius: var(--avp-radius);
border: 1px solid var(--avp-glass-border);
backdrop-filter: blur(24px) saturate(200%);
-webkit-backdrop-filter: blur(24px) saturate(200%);
box-shadow:
var(--avp-shadow-glow),
var(--avp-shadow-lg),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
z-index: 100000;
display: flex;
flex-direction: column;
opacity: 0;
pointer-events: none;
transition:
opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
overflow: hidden;
}
.avp-panel.active {
opacity: 1;
pointer-events: all;
transform: translate(-50%, -50%) scale(1) translateY(0);
}
.avp-panel.dragging {
transition: none;
cursor: grabbing;
}
/* ===== 面板头部 - 拖拽区域 ===== */
.avp-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 24px;
border-bottom: 1px solid var(--avp-glass-border);
background: linear-gradient(90deg, rgba(99,102,241,0.08) 0%, rgba(59,130,246,0.04) 50%, transparent 100%);
cursor: grab;
user-select: none;
position: relative;
z-index: 2;
}
.avp-panel-header:active {
cursor: grabbing;
}
.avp-panel-header::after {
content: '';
position: absolute;
bottom: 0;
left: 24px;
right: 24px;
height: 1px;
background: linear-gradient(90deg, transparent, var(--avp-primary-light), transparent);
opacity: 0.3;
}
.avp-panel-title {
display: flex;
align-items: center;
gap: 12px;
font-size: 16px;
font-weight: 600;
color: var(--avp-text);
letter-spacing: 0.3px;
}
.avp-panel-title-icon {
width: 32px;
height: 32px;
border-radius: var(--avp-radius-sm);
background: linear-gradient(135deg, var(--avp-primary) 0%, var(--avp-primary-dark) 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}
.avp-panel-title-icon svg {
width: 18px;
height: 18px;
fill: white;
}
.avp-version-badge {
font-size: 11px;
font-weight: 500;
color: var(--avp-text-dim);
background: var(--avp-glass);
padding: 2px 8px;
border-radius: 12px;
margin-left: 8px;
}
.avp-panel-actions {
display: flex;
gap: 8px;
}
.avp-btn-icon {
width: 34px;
height: 34px;
border-radius: var(--avp-radius-sm);
border: 1px solid var(--avp-glass-border);
background: var(--avp-glass);
color: var(--avp-text-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--avp-transition);
position: relative;
overflow: hidden;
}
.avp-btn-icon::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, var(--avp-primary) 0%, var(--avp-primary-light) 100%);
opacity: 0;
transition: var(--avp-transition);
}
.avp-btn-icon:hover {
border-color: var(--avp-glass-border-hover);
color: var(--avp-text);
transform: translateY(-2px);
}
.avp-btn-icon:hover::before {
opacity: 0.1;
}
.avp-btn-icon:active {
transform: scale(0.95);
}
.avp-btn-icon svg {
width: 18px;
height: 18px;
fill: currentColor;
position: relative;
z-index: 1;
}
/* ===== 面板内容区 ===== */
.avp-panel-body {
flex: 1;
overflow-y: auto;
padding: 0;
position: relative;
scrollbar-width: thin;
scrollbar-color: var(--avp-primary) transparent;
}
.avp-panel-body::-webkit-scrollbar {
width: 5px;
}
.avp-panel-body::-webkit-scrollbar-track {
background: transparent;
}
.avp-panel-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--avp-primary) 0%, var(--avp-primary-dark) 100%);
border-radius: 3px;
}
/* ===== 视频信息卡片 ===== */
.avp-video-info {
padding: 20px 24px;
border-bottom: 1px solid var(--avp-glass-border);
position: relative;
z-index: 1;
}
.avp-video-title {
font-size: 15px;
font-weight: 600;
color: var(--avp-text);
line-height: 1.6;
margin-bottom: 14px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.avp-video-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
font-size: 12px;
color: var(--avp-text-muted);
}
.avp-video-meta-item {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 20px;
background: var(--avp-glass);
border: 1px solid var(--avp-glass-border);
transition: var(--avp-transition-fast);
}
.avp-video-meta-item:hover {
background: var(--avp-glass-hover);
border-color: var(--avp-glass-border-hover);
}
.avp-video-meta-item svg {
width: 13px;
height: 13px;
fill: currentColor;
opacity: 0.7;
}
.avp-platform-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(59, 130, 246, 0.1) 100%);
color: var(--avp-primary-light);
border: 1px solid rgba(99, 102, 241, 0.2);
}
/* ===== 解析区域 ===== */
.avp-parse-section {
padding: 20px 24px;
position: relative;
z-index: 1;
}
.avp-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.avp-section-title {
font-size: 14px;
font-weight: 600;
color: var(--avp-text);
display: flex;
align-items: center;
gap: 10px;
letter-spacing: 0.5px;
}
.avp-section-title::before {
content: '';
width: 4px;
height: 18px;
border-radius: 2px;
background: linear-gradient(180deg, var(--avp-primary) 0%, var(--avp-primary-light) 50%, var(--avp-info) 100%);
}
/* ===== 进度条 - 流光效果 ===== */
.avp-progress {
display: none;
margin-bottom: 20px;
padding: 16px;
border-radius: var(--avp-radius-sm);
background: var(--avp-glass);
border: 1px solid var(--avp-glass-border);
}
.avp-progress.active {
display: block;
animation: avp-fade-in-up 0.4s ease;
}
.avp-progress-bar {
height: 6px;
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
overflow: hidden;
position: relative;
}
.avp-progress-fill {
height: 100%;
width: 0%;
background: linear-gradient(90deg, var(--avp-primary) 0%, var(--avp-primary-light) 50%, var(--avp-primary) 100%);
border-radius: 3px;
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
/* 流光效果 */
.avp-progress-fill::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
animation: avp-shimmer 1.5s ease-in-out infinite;
}
@keyframes avp-shimmer {
0% { left: -100%; }
100% { left: 100%; }
}
.avp-progress-text {
font-size: 12px;
color: var(--avp-text-muted);
text-align: center;
margin-top: 10px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.avp-progress-dots {
display: flex;
gap: 4px;
}
.avp-progress-dots span {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--avp-primary);
animation: avp-dot-bounce 1.4s ease-in-out infinite;
}
.avp-progress-dots span:nth-child(2) { animation-delay: 0.2s; }
.avp-progress-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes avp-dot-bounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
40% { transform: scale(1); opacity: 1; }
}
/* ===== 解析结果 - 渐入动画 ===== */
.avp-result {
display: none;
font-size: 14px;
line-height: 1.9;
color: var(--avp-text);
animation: avp-fade-in-up 0.5s ease;
}
.avp-result.active {
display: block;
}
@keyframes avp-fade-in-up {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Markdown样式增强 */
.avp-result h1, .avp-result h2, .avp-result h3 {
color: var(--avp-text);
margin: 20px 0 14px;
font-weight: 600;
position: relative;
padding-left: 12px;
}
.avp-result h1 { font-size: 20px; }
.avp-result h2 { font-size: 17px; }
.avp-result h3 { font-size: 15px; }
.avp-result h1::before, .avp-result h2::before, .avp-result h3::before {
content: '';
position: absolute;
left: 0;
top: 4px;
bottom: 4px;
width: 3px;
border-radius: 2px;
background: linear-gradient(180deg, var(--avp-primary) 0%, var(--avp-primary-light) 100%);
}
/* 可折叠标题 */
.avp-result .md-foldable {
cursor: pointer;
user-select: none;
transition: var(--avp-transition-fast);
}
.avp-result .md-foldable:hover {
color: var(--avp-primary-light);
}
.avp-result .md-fold-icon {
display: inline-block;
margin-right: 6px;
font-size: 10px;
color: var(--avp-primary);
transition: transform 0.3s ease;
}
.avp-result .md-foldable.collapsed .md-fold-icon {
transform: rotate(-90deg);
}
.avp-result .md-foldable + * {
transition: var(--avp-transition);
overflow: hidden;
}
.avp-result .md-foldable.collapsed + * {
max-height: 0;
margin: 0;
opacity: 0;
}
.avp-result p {
margin: 12px 0;
color: var(--avp-text-secondary);
}
.avp-result strong {
color: var(--avp-text);
font-weight: 600;
}
/* 关键词高亮 */
.avp-result .md-keyword {
background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(129, 140, 248, 0.1) 100%);
color: var(--avp-primary-light);
padding: 2px 6px;
border-radius: 4px;
font-weight: 500;
border: 1px solid rgba(99, 102, 241, 0.15);
}
.avp-result blockquote {
margin: 16px 0;
padding: 16px 20px;
border-left: 3px solid var(--avp-primary);
background: linear-gradient(90deg, rgba(99, 102, 241, 0.05) 0%, transparent 100%);
border-radius: 0 var(--avp-radius-sm) var(--avp-radius-sm) 0;
color: var(--avp-text-secondary);
font-style: italic;
}
.avp-result ul, .avp-result ol {
margin: 12px 0;
padding-left: 24px;
}
.avp-result li {
margin: 8px 0;
color: var(--avp-text-secondary);
position: relative;
}
.avp-result li::marker {
color: var(--avp-primary);
}
.avp-result code {
padding: 3px 8px;
border-radius: 6px;
background: var(--avp-bg-tertiary);
color: var(--avp-primary-light);
font-size: 13px;
font-family: 'SF Mono', Monaco, monospace;
border: 1px solid rgba(99, 102, 241, 0.1);
}
.avp-result pre {
padding: 20px;
border-radius: var(--avp-radius-sm);
background: var(--avp-bg-tertiary);
overflow-x: auto;
margin: 16px 0;
border: 1px solid var(--avp-glass-border);
}
.avp-result pre code {
background: none;
padding: 0;
color: var(--avp-text);
border: none;
}
/* 表格样式增强 */
.avp-result table, .avp-result .md-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
margin: 16px 0;
font-size: 13px;
border-radius: var(--avp-radius-sm);
overflow: hidden;
border: 1px solid var(--avp-glass-border);
}
.avp-result th {
padding: 12px 16px;
text-align: left;
font-weight: 600;
color: var(--avp-text);
background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%);
border-bottom: 1px solid var(--avp-glass-border);
}
.avp-result td {
padding: 12px 16px;
color: var(--avp-text-secondary);
border-bottom: 1px solid var(--avp-glass-border);
}
.avp-result tr:last-child td {
border-bottom: none;
}
.avp-result tr:hover td {
background: var(--avp-glass-hover);
}
.avp-result hr {
border: none;
height: 1px;
background: linear-gradient(90deg, transparent, var(--avp-glass-border), transparent);
margin: 20px 0;
}
.avp-result a {
color: var(--avp-primary-light);
text-decoration: none;
position: relative;
transition: var(--avp-transition-fast);
}
.avp-result a::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 1px;
background: linear-gradient(90deg, var(--avp-primary) 0%, var(--avp-primary-light) 100%);
transition: width 0.3s ease;
}
.avp-result a:hover {
color: var(--avp-primary-lighter);
}
.avp-result a:hover::after {
width: 100%;
}
/* ===== 空状态 - 更友好的设计 ===== */
.avp-empty {
text-align: center;
padding: 48px 24px;
color: var(--avp-text-dim);
animation: avp-fade-in-up 0.5s ease;
}
.avp-empty-icon {
width: 80px;
height: 80px;
margin: 0 auto 20px;
border-radius: 50%;
background: linear-gradient(135deg, var(--avp-glass) 0%, rgba(99, 102, 241, 0.05) 100%);
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--avp-glass-border);
position: relative;
}
.avp-empty-icon::before {
content: '';
position: absolute;
inset: -2px;
border-radius: 50%;
background: linear-gradient(135deg, var(--avp-primary) 0%, var(--avp-primary-light) 100%);
opacity: 0.1;
animation: avp-spin-slow 10s linear infinite;
}
@keyframes avp-spin-slow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.avp-empty-icon svg {
width: 36px;
height: 36px;
fill: var(--avp-primary-light);
opacity: 0.6;
}
.avp-empty-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 10px;
color: var(--avp-text-secondary);
}
.avp-empty-desc {
font-size: 13px;
line-height: 1.7;
max-width: 280px;
margin: 0 auto;
}
.avp-empty-hint {
margin-top: 16px;
padding: 10px 16px;
border-radius: var(--avp-radius-sm);
background: var(--avp-glass);
border: 1px solid var(--avp-glass-border);
font-size: 12px;
color: var(--avp-text-muted);
display: inline-flex;
align-items: center;
gap: 6px;
}
.avp-empty-hint kbd {
padding: 2px 8px;
border-radius: 4px;
background: var(--avp-bg-tertiary);
border: 1px solid var(--avp-glass-border);
font-family: inherit;
font-size: 11px;
color: var(--avp-primary-light);
}
/* ===== 操作按钮 - 微交互 ===== */
.avp-actions {
display: flex;
gap: 10px;
padding: 18px 24px;
border-top: 1px solid var(--avp-glass-border);
background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.1) 100%);
position: relative;
z-index: 1;
}
.avp-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 18px;
border-radius: var(--avp-radius-sm);
border: 1px solid var(--avp-glass-border);
background: var(--avp-glass);
color: var(--avp-text);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: var(--avp-transition);
position: relative;
overflow: hidden;
}
.avp-btn::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, var(--avp-primary) 0%, var(--avp-primary-light) 100%);
opacity: 0;
transition: var(--avp-transition);
}
.avp-btn:hover {
border-color: var(--avp-glass-border-hover);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.avp-btn:hover::before {
opacity: 0.05;
}
.avp-btn:active {
transform: scale(0.97) translateY(0);
}
.avp-btn svg {
width: 16px;
height: 16px;
fill: currentColor;
position: relative;
z-index: 1;
}
.avp-btn span {
position: relative;
z-index: 1;
}
.avp-btn-primary {
background: linear-gradient(135deg, var(--avp-primary) 0%, var(--avp-primary-dark) 100%);
border-color: transparent;
color: white;
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
}
.avp-btn-primary:hover {
box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
}
.avp-btn-primary::before {
display: none;
}
.avp-btn-success {
background: linear-gradient(135deg, var(--avp-success) 0%, #059669 100%);
border-color: transparent;
color: white;
}
/* ===== 设置面板 ===== */
.avp-settings {
display: none;
padding: 20px 24px;
}
.avp-settings.active {
display: block;
animation: avp-fade-in-up 0.4s ease;
}
.avp-form-group {
margin-bottom: 20px;
}
.avp-form-label {
display: block;
font-size: 13px;
font-weight: 500;
color: var(--avp-text);
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.avp-form-label::before {
content: '';
width: 3px;
height: 14px;
border-radius: 2px;
background: linear-gradient(180deg, var(--avp-primary) 0%, var(--avp-primary-light) 100%);
}
.avp-form-input {
width: 100%;
padding: 12px 16px;
border-radius: var(--avp-radius-sm);
border: 1px solid var(--avp-glass-border);
background: var(--avp-bg-secondary);
color: var(--avp-text);
font-size: 13px;
transition: var(--avp-transition);
box-sizing: border-box;
font-family: inherit;
}
.avp-form-input:focus {
outline: none;
border-color: var(--avp-primary);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.avp-form-input::placeholder {
color: var(--avp-text-dim);
}
/* ===== 历史记录 ===== */
.avp-history {
display: none;
}
.avp-history.active {
display: block;
animation: avp-fade-in-up 0.4s ease;
}
.avp-history-list {
padding: 0 24px;
}
.avp-history-item {
display: flex;
align-items: center;
gap: 14px;
padding: 14px;
border-radius: var(--avp-radius-sm);
cursor: pointer;
transition: var(--avp-transition);
margin-bottom: 10px;
border: 1px solid transparent;
}
.avp-history-item:hover {
background: var(--avp-glass-hover);
border-color: var(--avp-glass-border);
transform: translateX(4px);
}
.avp-history-thumb {
width: 88px;
height: 66px;
border-radius: var(--avp-radius-sm);
background: var(--avp-bg-tertiary);
object-fit: cover;
flex-shrink: 0;
border: 1px solid var(--avp-glass-border);
}
.avp-history-info {
flex: 1;
min-width: 0;
}
.avp-history-title {
font-size: 13px;
font-weight: 500;
color: var(--avp-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 6px;
}
.avp-history-meta {
font-size: 12px;
color: var(--avp-text-dim);
display: flex;
align-items: center;
gap: 8px;
}
.avp-history-platform {
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
}
.avp-history-platform.bilibili {
background: rgba(251, 114, 153, 0.15);
color: #fb7299;
}
.avp-history-platform.youtube {
background: rgba(255, 0, 0, 0.15);
color: #ff0000;
}
/* ===== 遮罩层 ===== */
.avp-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 99999;
opacity: 0;
pointer-events: none;
transition: opacity 0.4s ease;
}
.avp-overlay.active {
opacity: 1;
pointer-events: all;
}
/* ===== Toast消息 - 滑入滑出 ===== */
.avp-toast {
position: fixed;
top: 24px;
left: 50%;
transform: translateX(-50%) translateY(-30px);
padding: 14px 28px;
border-radius: var(--avp-radius-sm);
background: linear-gradient(145deg, var(--avp-surface) 0%, var(--avp-bg-secondary) 100%);
border: 1px solid var(--avp-glass-border);
color: var(--avp-text);
font-size: 14px;
font-weight: 500;
z-index: 100001;
opacity: 0;
pointer-events: none;
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: var(--avp-shadow);
display: flex;
align-items: center;
gap: 10px;
max-width: 90vw;
}
.avp-toast.active {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.avp-toast-success {
border-left: 3px solid var(--avp-success);
}
.avp-toast-error {
border-left: 3px solid var(--avp-error);
}
.avp-toast-info {
border-left: 3px solid var(--avp-info);
}
.avp-toast-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
}
/* ===== 快捷键提示 ===== */
.avp-shortcuts-hint {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
padding: 10px 20px;
border-radius: var(--avp-radius-sm);
background: var(--avp-surface);
border: 1px solid var(--avp-glass-border);
color: var(--avp-text-muted);
font-size: 12px;
z-index: 100002;
opacity: 0;
pointer-events: none;
transition: var(--avp-transition);
display: flex;
gap: 16px;
}
.avp-shortcuts-hint.active {
opacity: 1;
}
.avp-shortcuts-hint kbd {
padding: 2px 8px;
border-radius: 4px;
background: var(--avp-bg-tertiary);
border: 1px solid var(--avp-glass-border);
font-family: inherit;
font-size: 11px;
color: var(--avp-primary-light);
}
/* ===== 动画集合 ===== */
@keyframes avp-spin {
to { transform: rotate(360deg); }
}
.avp-spinning {
animation: avp-spin 1s linear infinite;
}
@keyframes avp-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.avp-pulse {
animation: avp-pulse 2s ease-in-out infinite;
}
/* ===== 粒子画布 ===== */
.avp-particle-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
/* ===== 复制成功反馈 ===== */
.avp-copy-feedback {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.8);
padding: 20px 40px;
border-radius: var(--avp-radius);
background: linear-gradient(145deg, var(--avp-surface) 0%, var(--avp-bg-secondary) 100%);
border: 1px solid var(--avp-glass-border);
color: var(--avp-success);
font-size: 16px;
font-weight: 600;
z-index: 100003;
opacity: 0;
pointer-events: none;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
display: flex;
align-items: center;
gap: 12px;
box-shadow: var(--avp-shadow-lg);
}
.avp-copy-feedback.active {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
.avp-copy-feedback svg {
width: 28px;
height: 28px;
fill: currentColor;
}
`;
document.head.appendChild(style);
}
}
/**
* 主应用类 - 全面升级版本
*/
class AIVideoParser {
constructor() {
this.extractors = [
new BilibiliExtractor(),
new YouTubeExtractor()
];
this.currentExtractor = null;
this.currentVideoInfo = null;
this.parserService = new AIParserService();
this.historyManager = new HistoryManager();
this.isParsing = false;
this.elements = {};
this.particleBg = null;
this.dragState = {
isDragging: false,
startX: 0,
startY: 0,
offsetX: 0,
offsetY: 0
};
}
/**
* 初始化应用
*/
init() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.#setup());
} else {
this.#setup();
}
}
/**
* 设置应用
*/
#setup() {
this.currentExtractor = this.extractors.find(e => e.match());
if (!this.currentExtractor) return;
StyleInjector.inject();
this.#createUI();
this.#registerMenuCommands();
this.#observePageChanges();
this.#setupKeyboardShortcuts();
}
/**
* 创建UI元素
*/
#createUI() {
this.#createGradientBg();
this.#createFloatButton();
this.#createPanel();
this.#createOverlay();
this.#createToast();
this.#createCopyFeedback();
this.#createShortcutsHint();
}
/**
* 创建动态渐变背景
*/
#createGradientBg() {
const bg = document.createElement('div');
bg.className = 'avp-gradient-bg';
bg.id = 'avp-gradient-bg';
document.body.appendChild(bg);
this.elements.gradientBg = bg;
}
/**
* 创建浮动按钮
*/
#createFloatButton() {
const btn = document.createElement('button');
btn.className = 'avp-float-btn';
btn.innerHTML = `
`;
btn.title = 'AI视频解析 (Ctrl+Shift+A)';
btn.addEventListener('click', () => this.#openPanel());
document.body.appendChild(btn);
this.elements.floatButton = btn;
}
/**
* 创建解析面板
*/
#createPanel() {
const panel = document.createElement('div');
panel.className = 'avp-panel';
panel.id = 'avp-panel';
panel.innerHTML = `
准备就绪
点击下方"开始解析"按钮,或使用快捷键获取AI智能分析结果
Ctrl + Enter 开始解析
`;
document.body.appendChild(panel);
this.elements.panel = panel;
// 初始化粒子背景
const panelBody = panel.querySelector('.avp-panel-body');
this.particleBg = new ParticleBackground(panelBody);
// 绑定拖拽
this.#setupDrag();
// 绑定面板内的事件
this.#bindPanelEvents();
}
/**
* 设置拖拽功能
*/
#setupDrag() {
const header = document.getElementById('avp-panel-header');
const panel = this.elements.panel;
if (!header || !panel) return;
header.addEventListener('mousedown', (e) => {
if (e.target.closest('.avp-btn-icon')) return;
this.dragState.isDragging = true;
this.dragState.startX = e.clientX;
this.dragState.startY = e.clientY;
const rect = panel.getBoundingClientRect();
this.dragState.offsetX = rect.left + rect.width / 2;
this.dragState.offsetY = rect.top + rect.height / 2;
panel.classList.add('dragging');
});
document.addEventListener('mousemove', (e) => {
if (!this.dragState.isDragging) return;
const dx = e.clientX - this.dragState.startX;
const dy = e.clientY - this.dragState.startY;
const newLeft = this.dragState.offsetX + dx;
const newTop = this.dragState.offsetY + dy;
panel.style.left = `${newLeft}px`;
panel.style.top = `${newTop}px`;
panel.style.transform = 'translate(-50%, -50%)';
});
document.addEventListener('mouseup', () => {
if (this.dragState.isDragging) {
this.dragState.isDragging = false;
panel.classList.remove('dragging');
}
});
}
/**
* 创建遮罩层
*/
#createOverlay() {
const overlay = document.createElement('div');
overlay.className = 'avp-overlay';
overlay.addEventListener('click', () => this.#closePanel());
document.body.appendChild(overlay);
this.elements.overlay = overlay;
}
/**
* 创建提示消息
*/
#createToast() {
const toast = document.createElement('div');
toast.className = 'avp-toast';
document.body.appendChild(toast);
this.elements.toast = toast;
}
/**
* 创建复制成功反馈
*/
#createCopyFeedback() {
const feedback = document.createElement('div');
feedback.className = 'avp-copy-feedback';
feedback.innerHTML = `
已复制到剪贴板
`;
document.body.appendChild(feedback);
this.elements.copyFeedback = feedback;
}
/**
* 创建快捷键提示
*/
#createShortcutsHint() {
const hint = document.createElement('div');
hint.className = 'avp-shortcuts-hint';
hint.innerHTML = `
ESC 关闭面板
Ctrl+Enter 开始解析
H 历史记录
S 设置
`;
document.body.appendChild(hint);
this.elements.shortcutsHint = hint;
}
/**
* 绑定面板事件
*/
#bindPanelEvents() {
this.elements.panel.querySelector('[data-action="close"]')?.addEventListener('click', () => this.#closePanel());
this.elements.panel.querySelector('[data-action="settings"]')?.addEventListener('click', () => this.#showSettings());
this.elements.panel.querySelector('[data-action="history"]')?.addEventListener('click', () => this.#showHistory());
document.getElementById('avp-btn-parse')?.addEventListener('click', () => this.#startParse());
document.getElementById('avp-btn-copy')?.addEventListener('click', () => this.#copyResult());
document.getElementById('avp-btn-export')?.addEventListener('click', () => this.#exportResult());
// 绑定折叠功能
this.#setupFoldable();
}
/**
* 设置可折叠标题
*/
#setupFoldable() {
document.addEventListener('click', (e) => {
const foldable = e.target.closest('.md-foldable');
if (!foldable) return;
foldable.classList.toggle('collapsed');
});
}
/**
* 设置键盘快捷键
*/
#setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// ESC关闭面板
if (e.key === 'Escape') {
this.#closePanel();
return;
}
// 仅面板打开时生效的快捷键
if (!this.elements.panel?.classList.contains('active')) {
// Ctrl+Shift+A 打开面板
if (e.ctrlKey && e.shiftKey && e.key === 'A') {
e.preventDefault();
this.#openPanel();
}
return;
}
// Ctrl+Enter 开始解析
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
this.#startParse();
return;
}
// H 打开历史记录
if (e.key === 'h' || e.key === 'H') {
const active = document.querySelector('.avp-history.active');
if (active) {
this.#showMainContent();
} else {
this.#showHistory();
}
return;
}
// S 打开设置
if (e.key === 's' || e.key === 'S') {
const active = document.getElementById('avp-settings-panel')?.classList.contains('active');
if (active) {
this.#showMainContent();
} else {
this.#showSettings();
}
}
});
}
/**
* 打开面板
*/
#openPanel() {
this.currentVideoInfo = this.currentExtractor.extract();
if (!this.currentVideoInfo) {
this.#showToast('无法获取视频信息,请刷新页面重试', 'error');
return;
}
this.#updateVideoInfo();
this.elements.panel.classList.add('active');
this.elements.overlay.classList.add('active');
this.elements.gradientBg.classList.add('active');
// 显示快捷键提示
this.elements.shortcutsHint.classList.add('active');
setTimeout(() => {
this.elements.shortcutsHint.classList.remove('active');
}, 4000);
// 初始化粒子背景
if (this.particleBg && !this.particleBg.canvas) {
this.particleBg.init();
}
this.#resetParseState();
}
/**
* 关闭面板
*/
#closePanel() {
this.elements.panel.classList.remove('active');
this.elements.overlay.classList.remove('active');
this.elements.gradientBg.classList.remove('active');
this.elements.shortcutsHint.classList.remove('active');
// 重置面板位置
setTimeout(() => {
this.elements.panel.style.left = '50%';
this.elements.panel.style.top = '50%';
this.elements.panel.style.transform = '';
}, 500);
this.#showMainContent();
}
/**
* 更新视频信息展示
*/
#updateVideoInfo() {
const info = this.currentVideoInfo;
document.getElementById('avp-video-title').textContent = info.title || '未知标题';
const platformNames = {
'bilibili': '哔哩哔哩',
'youtube': 'YouTube'
};
const platformEl = document.getElementById('avp-platform');
platformEl.textContent = platformNames[info.platform] || info.platform;
const metaEl = document.getElementById('avp-video-meta');
metaEl.innerHTML = `
${platformNames[info.platform] || info.platform}
${info.author ? `
${info.author}
` : ''}
${info.duration > 0 ? `
${formatDuration(info.duration)}
` : ''}
`;
}
/**
* 重置解析状态
*/
#resetParseState() {
this.isParsing = false;
document.getElementById('avp-progress')?.classList.remove('active');
document.getElementById('avp-result')?.classList.remove('active');
document.getElementById('avp-empty').style.display = 'block';
document.getElementById('avp-progress-fill').style.width = '0%';
document.getElementById('avp-progress-text').innerHTML = `
准备解析...
`;
document.getElementById('avp-result').innerHTML = '';
}
/**
* 开始解析
*/
async #startParse() {
if (this.isParsing) return;
this.isParsing = true;
const emptyEl = document.getElementById('avp-empty');
const progressEl = document.getElementById('avp-progress');
const resultEl = document.getElementById('avp-result');
emptyEl.style.display = 'none';
progressEl.classList.add('active');
resultEl.classList.remove('active');
try {
const result = await this.parserService.parse(
this.currentVideoInfo,
(progress, message) => {
document.getElementById('avp-progress-fill').style.width = `${progress}%`;
document.getElementById('avp-progress-text').innerHTML = `
${message}
`;
}
);
this.historyManager.add({
videoId: this.currentVideoInfo.videoId,
platform: this.currentVideoInfo.platform,
title: this.currentVideoInfo.title,
author: this.currentVideoInfo.author,
cover: this.currentVideoInfo.cover,
result: result,
duration: this.currentVideoInfo.duration,
url: this.currentVideoInfo.url
});
progressEl.classList.remove('active');
resultEl.innerHTML = renderMarkdown(result);
resultEl.classList.add('active');
this.currentResult = result;
// 自动折叠二级以下标题
this.#autoCollapseHeadings();
} catch (error) {
this.#showToast(`解析失败: ${error.message}`, 'error');
progressEl.classList.remove('active');
emptyEl.style.display = 'block';
} finally {
this.isParsing = false;
}
}
/**
* 自动折叠二级以下标题
*/
#autoCollapseHeadings() {
const h3s = document.querySelectorAll('.avp-result h3');
h3s.forEach(h => h.classList.add('collapsed'));
}
/**
* 复制结果
*/
#copyResult() {
const resultEl = document.getElementById('avp-result');
if (!resultEl.textContent.trim()) {
this.#showToast('暂无解析结果', 'error');
return;
}
const text = this.currentResult || resultEl.textContent;
navigator.clipboard.writeText(text).then(() => {
this.#showCopyFeedback();
}).catch(() => {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
this.#showCopyFeedback();
});
}
/**
* 显示复制成功反馈
*/
#showCopyFeedback() {
const feedback = this.elements.copyFeedback;
feedback.classList.add('active');
setTimeout(() => {
feedback.classList.remove('active');
}, 1500);
}
/**
* 导出结果
*/
#exportResult() {
const resultEl = document.getElementById('avp-result');
if (!resultEl.textContent.trim()) {
this.#showToast('暂无解析结果', 'error');
return;
}
const info = this.currentVideoInfo;
const timestamp = new Date().toLocaleString('zh-CN');
const content = `# ${info.title}
> 作者:${info.author || '未知'}
> 平台:${info.platform}
> 解析时间:${timestamp}
> 原链接:${info.url}
---
${this.currentResult || resultEl.innerHTML}
---
*由 AI Video Parser v${VERSION.current} 生成*
`;
const blob = new Blob([content], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `解析结果_${info.title.slice(0, 50)}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.#showToast('已导出Markdown文件', 'success');
}
/**
* 显示设置面板
*/
#showSettings() {
const mainContent = this.elements.panel.querySelector('.avp-main-content');
const settingsPanel = document.getElementById('avp-settings-panel');
const historyPanel = document.getElementById('avp-history-panel');
const isActive = settingsPanel.classList.contains('active');
mainContent.style.display = isActive ? 'block' : 'none';
settingsPanel.classList.toggle('active', !isActive);
historyPanel.classList.remove('active');
if (!isActive) {
document.getElementById('setting-api-url').value = GM_getValue('api_url', CONFIG.API_URL);
document.getElementById('setting-api-key').value = GM_getValue('api_key', CONFIG.API_KEY);
document.getElementById('setting-model').value = GM_getValue('model', CONFIG.MODEL);
} else {
this.#saveSettings();
}
}
/**
* 保存设置
*/
#saveSettings() {
const apiUrl = document.getElementById('setting-api-url').value.trim();
const apiKey = document.getElementById('setting-api-key').value.trim();
const model = document.getElementById('setting-model').value.trim();
if (apiUrl) GM_setValue('api_url', apiUrl);
if (apiKey) GM_setValue('api_key', apiKey);
if (model) GM_setValue('model', model);
this.parserService.updateConfig();
this.#showToast('设置已保存', 'success');
}
/**
* 显示历史记录
*/
#showHistory() {
const mainContent = this.elements.panel.querySelector('.avp-main-content');
const settingsPanel = document.getElementById('avp-settings-panel');
const historyPanel = document.getElementById('avp-history-panel');
mainContent.style.display = 'none';
settingsPanel.classList.remove('active');
historyPanel.classList.add('active');
this.#renderHistory();
}
/**
* 渲染历史记录
*/
#renderHistory() {
const listEl = document.getElementById('avp-history-list');
const history = this.historyManager.getAll();
if (history.length === 0) {
listEl.innerHTML = `
`;
return;
}
listEl.innerHTML = history.map(item => `
${item.title || '未知视频'}
${item.platform}
${item.author || ''} · ${formatDate(item.timestamp)}
`).join('');
listEl.querySelectorAll('.avp-history-item').forEach(item => {
item.addEventListener('click', () => {
const record = this.historyManager.getById(item.dataset.id);
if (record) {
this.#showMainContent();
this.currentVideoInfo = {
videoId: record.videoId,
platform: record.platform,
title: record.title,
author: record.author,
cover: record.cover,
duration: record.duration,
url: record.url || ''
};
this.currentResult = record.result;
this.#updateVideoInfo();
document.getElementById('avp-empty').style.display = 'none';
document.getElementById('avp-progress').classList.remove('active');
const resultEl = document.getElementById('avp-result');
resultEl.innerHTML = renderMarkdown(record.result);
resultEl.classList.add('active');
}
});
});
}
/**
* 显示主内容
*/
#showMainContent() {
this.elements.panel.querySelector('.avp-main-content').style.display = 'block';
document.getElementById('avp-settings-panel').classList.remove('active');
document.getElementById('avp-history-panel').classList.remove('active');
}
/**
* 显示提示消息
*/
#showToast(message, type = 'success') {
const toast = this.elements.toast;
const icons = {
success: '',
error: '',
info: ''
};
toast.innerHTML = `${icons[type] || icons.success}${message}`;
toast.className = `avp-toast avp-toast-${type} active`;
clearTimeout(this.toastTimer);
this.toastTimer = setTimeout(() => {
toast.classList.remove('active');
}, 3000);
}
/**
* 注册菜单命令
*/
#registerMenuCommands() {
GM_registerMenuCommand('⚙️ 打开设置', () => {
this.#openPanel();
setTimeout(() => this.#showSettings(), 100);
});
GM_registerMenuCommand('📋 查看历史', () => {
this.#openPanel();
setTimeout(() => this.#showHistory(), 100);
});
GM_registerMenuCommand('🗑️ 清空历史', () => {
if (confirm('确定要清空所有历史记录吗?')) {
this.historyManager.clear();
this.#showToast('历史记录已清空', 'success');
}
});
}
/**
* 监听页面变化(SPA支持)
*/
#observePageChanges() {
let lastUrl = location.href;
const observer = new MutationObserver(debounce(() => {
const currentUrl = location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
this.currentExtractor = this.extractors.find(e => e.match());
if (this.currentExtractor) {
setTimeout(() => {
this.currentVideoInfo = this.currentExtractor.extract();
}, 1000);
}
}
}, 500));
observer.observe(document.body, {
childList: true,
subtree: true
});
}
}
// ==================== 启动应用 ====================
const app = new AIVideoParser();
app.init();
})();