feat(类型): 添加视频搜索类型定义和工具函数
- 新增 SearchVideoData 类型定义,包含视频标题、链接、时长、作者等字段 - 新增 Message 类型的 searchVideos 属性用于存储视频搜索结果 - 添加 videoUtils.ts 视频工具函数库: - parseVideoUrl: 解析B站/YouTube/抖音视频链接 - formatDuration: 格式化视频时长显示 - getPlatformName/getPlatformColor: 获取平台名称和主题色 - detectPlatform: 检测视频平台类型 - supportsEmbed: 判断是否支持嵌入播放 - getEmbedUrlWithPage: 生成带选集参数的嵌入URL - supportsEpisodes: 判断是否支持选集功能
This commit is contained in:
parent
92ec88e1a3
commit
159009dd56
185
src/lib/videoUtils.ts
Normal file
185
src/lib/videoUtils.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* 视频工具函数
|
||||||
|
* 处理视频链接解析、时长格式化等
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频平台类型
|
||||||
|
*/
|
||||||
|
export type VideoPlatform = 'bilibili' | 'youtube' | 'douyin' | 'other';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频解析信息
|
||||||
|
*/
|
||||||
|
export interface VideoInfo {
|
||||||
|
platform: VideoPlatform;
|
||||||
|
videoId: string;
|
||||||
|
embedUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析视频链接,提取平台和视频ID
|
||||||
|
* @param url 视频链接
|
||||||
|
* @returns 视频信息,解析失败返回 null
|
||||||
|
*/
|
||||||
|
export function parseVideoUrl(url: string): VideoInfo | null {
|
||||||
|
if (!url) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// B站视频解析 - av号格式
|
||||||
|
// 格式: bilibili.com/video/av123456 或 www.bilibili.com/video/av123456
|
||||||
|
const bilibiliAvMatch = url.match(/bilibili\.com\/video\/av(\d+)/i);
|
||||||
|
if (bilibiliAvMatch) {
|
||||||
|
const aid = bilibiliAvMatch[1];
|
||||||
|
return {
|
||||||
|
platform: 'bilibili',
|
||||||
|
videoId: `av${aid}`,
|
||||||
|
embedUrl: `//player.bilibili.com/player.html?aid=${aid}&high_quality=1&danmaku=0`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// B站视频解析 - BV号格式
|
||||||
|
// 格式: bilibili.com/video/BV1xx411c7mD
|
||||||
|
const bilibiliBvMatch = url.match(/bilibili\.com\/video\/(BV[\w]+)/i);
|
||||||
|
if (bilibiliBvMatch) {
|
||||||
|
const bvid = bilibiliBvMatch[1];
|
||||||
|
return {
|
||||||
|
platform: 'bilibili',
|
||||||
|
videoId: bvid,
|
||||||
|
embedUrl: `//player.bilibili.com/player.html?bvid=${bvid}&high_quality=1&danmaku=0`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// YouTube 视频解析
|
||||||
|
// 格式: youtube.com/watch?v=xxx 或 youtu.be/xxx
|
||||||
|
const youtubeMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
|
||||||
|
if (youtubeMatch) {
|
||||||
|
const videoId = youtubeMatch[1];
|
||||||
|
return {
|
||||||
|
platform: 'youtube',
|
||||||
|
videoId,
|
||||||
|
embedUrl: `https://www.youtube.com/embed/${videoId}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抖音视频解析 (预留)
|
||||||
|
// 格式: douyin.com/video/xxx
|
||||||
|
const douyinMatch = url.match(/douyin\.com\/video\/(\d+)/);
|
||||||
|
if (douyinMatch) {
|
||||||
|
return {
|
||||||
|
platform: 'douyin',
|
||||||
|
videoId: douyinMatch[1],
|
||||||
|
embedUrl: '', // 抖音暂不支持嵌入
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化视频时长
|
||||||
|
* @param seconds 秒数(字符串或数字)
|
||||||
|
* @returns 格式化后的时长 (如 "2:34" 或 "1:02:34")
|
||||||
|
*/
|
||||||
|
export function formatDuration(seconds: string | number): string {
|
||||||
|
const totalSeconds = typeof seconds === 'string' ? parseInt(seconds, 10) : seconds;
|
||||||
|
|
||||||
|
if (isNaN(totalSeconds) || totalSeconds < 0) {
|
||||||
|
return '--:--';
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||||
|
const secs = totalSeconds % 60;
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频平台显示名称
|
||||||
|
* @param platform 平台标识
|
||||||
|
* @returns 平台中文名称
|
||||||
|
*/
|
||||||
|
export function getPlatformName(platform: VideoPlatform): string {
|
||||||
|
const names: Record<VideoPlatform, string> = {
|
||||||
|
bilibili: 'B站',
|
||||||
|
youtube: 'YouTube',
|
||||||
|
douyin: '抖音',
|
||||||
|
other: '视频',
|
||||||
|
};
|
||||||
|
return names[platform] || '视频';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频平台主题色
|
||||||
|
* @param platform 平台标识
|
||||||
|
* @returns 平台主题色 (hex)
|
||||||
|
*/
|
||||||
|
export function getPlatformColor(platform: VideoPlatform): string {
|
||||||
|
const colors: Record<VideoPlatform, string> = {
|
||||||
|
bilibili: '#fb7299',
|
||||||
|
youtube: '#ff0000',
|
||||||
|
douyin: '#000000',
|
||||||
|
other: '#6b7280',
|
||||||
|
};
|
||||||
|
return colors[platform] || '#6b7280';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测视频链接的平台
|
||||||
|
* @param url 视频链接
|
||||||
|
* @returns 平台标识
|
||||||
|
*/
|
||||||
|
export function detectPlatform(url: string): VideoPlatform {
|
||||||
|
if (!url) return 'other';
|
||||||
|
|
||||||
|
if (url.includes('bilibili.com')) return 'bilibili';
|
||||||
|
if (url.includes('youtube.com') || url.includes('youtu.be')) return 'youtube';
|
||||||
|
if (url.includes('douyin.com')) return 'douyin';
|
||||||
|
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查视频是否支持嵌入播放
|
||||||
|
* @param platform 平台标识
|
||||||
|
* @returns 是否支持嵌入
|
||||||
|
*/
|
||||||
|
export function supportsEmbed(platform: VideoPlatform): boolean {
|
||||||
|
return platform === 'bilibili' || platform === 'youtube';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成带选集参数的嵌入 URL
|
||||||
|
* @param url 原始视频链接
|
||||||
|
* @param page 选集编号(从1开始)
|
||||||
|
* @returns 嵌入 URL
|
||||||
|
*/
|
||||||
|
export function getEmbedUrlWithPage(url: string, page: number = 1): string | null {
|
||||||
|
const videoInfo = parseVideoUrl(url);
|
||||||
|
if (!videoInfo) return null;
|
||||||
|
|
||||||
|
if (videoInfo.platform === 'bilibili') {
|
||||||
|
// B站嵌入链接支持 p 参数指定分P
|
||||||
|
return `${videoInfo.embedUrl}&p=${page}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他平台暂不支持选集
|
||||||
|
return videoInfo.embedUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查平台是否支持选集
|
||||||
|
* @param platform 平台标识
|
||||||
|
* @returns 是否支持选集
|
||||||
|
*/
|
||||||
|
export function supportsEpisodes(platform: VideoPlatform): boolean {
|
||||||
|
return platform === 'bilibili';
|
||||||
|
}
|
||||||
@ -36,6 +36,8 @@ export interface Message {
|
|||||||
usedTools?: string[];
|
usedTools?: string[];
|
||||||
/** 搜索到的图片(图片搜索工具结果) */
|
/** 搜索到的图片(图片搜索工具结果) */
|
||||||
searchImages?: SearchImageData[];
|
searchImages?: SearchImageData[];
|
||||||
|
/** 搜索到的视频(视频搜索工具结果) */
|
||||||
|
searchVideos?: SearchVideoData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索图片数据类型
|
// 搜索图片数据类型
|
||||||
@ -49,6 +51,28 @@ export interface SearchImageData {
|
|||||||
sourceUrl?: string;
|
sourceUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索视频数据类型
|
||||||
|
export interface SearchVideoData {
|
||||||
|
/** 视频标题 */
|
||||||
|
title: string;
|
||||||
|
/** 视频链接 */
|
||||||
|
link: string;
|
||||||
|
/** 视频摘要 */
|
||||||
|
snippet: string;
|
||||||
|
/** 相关度评分 */
|
||||||
|
score: string;
|
||||||
|
/** 排序位置 */
|
||||||
|
position: number;
|
||||||
|
/** 作者列表 */
|
||||||
|
authors: string[];
|
||||||
|
/** 发布日期 */
|
||||||
|
date: string;
|
||||||
|
/** 时长(秒) */
|
||||||
|
duration: string;
|
||||||
|
/** 封面图URL */
|
||||||
|
coverImage: string;
|
||||||
|
}
|
||||||
|
|
||||||
// 工具调用记录
|
// 工具调用记录
|
||||||
export interface ToolCall {
|
export interface ToolCall {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user