Initial TrueGrowth source import
This commit is contained in:
183
packages/drawnix/src/utils/gemini-api/utils.ts
Normal file
183
packages/drawnix/src/utils/gemini-api/utils.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* Gemini API 工具函数
|
||||
*/
|
||||
|
||||
import { ImageInput, ProcessedContent } from './types';
|
||||
|
||||
/**
|
||||
* 将文件转换为 base64 格式
|
||||
*/
|
||||
export async function fileToBase64(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const result = reader.result as string;
|
||||
resolve(result);
|
||||
};
|
||||
reader.onerror = () => reject(new Error('文件读取失败'));
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备图片数据,转换为 API 所需格式
|
||||
*/
|
||||
export async function prepareImageData(image: ImageInput): Promise<string> {
|
||||
if (image.file) {
|
||||
return await fileToBase64(image.file);
|
||||
} else if (image.base64) {
|
||||
// 确保 base64 数据包含正确的前缀
|
||||
if (image.base64.startsWith('data:')) {
|
||||
return image.base64;
|
||||
} else {
|
||||
return `data:image/png;base64,${image.base64}`;
|
||||
}
|
||||
} else if (image.url) {
|
||||
// 对于 URL,直接返回(API 可能支持 URL 格式)
|
||||
return image.url;
|
||||
} else {
|
||||
throw new Error('无效的图片输入:必须提供 file、base64 或 url');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 base64 数据转换为 Blob URL
|
||||
*/
|
||||
export function base64ToBlobUrl(base64Data: string, mimeType: string = 'image/png'): string {
|
||||
const byteCharacters = atob(base64Data);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type: mimeType });
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理混合内容(文字、base64图片、URL图片、视频链接)
|
||||
*/
|
||||
export function processMixedContent(content: string): ProcessedContent {
|
||||
// 查找 base64 图片
|
||||
const base64Pattern = /data:image\/[^;]+;base64,([A-Za-z0-9+/=]+)/g;
|
||||
const base64Matches = Array.from(content.matchAll(base64Pattern));
|
||||
|
||||
// 优先查找 Markdown 格式的图片 
|
||||
// 这能正确提取带查询参数的完整 URL
|
||||
const markdownImagePattern = /!\[[^\]]*\]\((https?:\/\/[^)]+)\)/gi;
|
||||
const markdownImageMatches = Array.from(content.matchAll(markdownImagePattern));
|
||||
|
||||
// 查找图片 URL 链接(支持带查询参数的 URL)
|
||||
// Original: /https?:\/\/[^\s<>"'\]]+\.(png|jpg|jpeg|gif|webp)/gi
|
||||
const imageUrlPattern = /https?:\/\/[^\s<>"'\])]+\.(png|jpg|jpeg|gif|webp)(\?[^\s<>"'\])]*)?/gi;
|
||||
const imageUrlMatches = Array.from(content.matchAll(imageUrlPattern));
|
||||
|
||||
// 查找视频 URL 链接(包括markdown格式)
|
||||
const videoUrlPatterns = [
|
||||
// 匹配markdown链接中的视频URL:[▶️ 在线观看](url) 或 [⏬ 下载视频](url)
|
||||
/\[(?:▶️\s*在线观看|⏬\s*下载视频|.*?观看.*?|.*?下载.*?)\]\(([^)]+\.(?:mp4|avi|mov|wmv|flv|webm|mkv)(?:\?[^)]*)?)\)/gi,
|
||||
// 直接的视频URL
|
||||
/https?:\/\/[^\s<>"'\]]+\.(?:mp4|avi|mov|wmv|flv|webm|mkv)(?:\?[^\s<>"'\]]*)?/gi,
|
||||
// 特定域名的视频链接(如filesystem.site)
|
||||
/https?:\/\/filesystem\.site\/[^\s<>"'\]]+/gi
|
||||
];
|
||||
|
||||
let textContent = content;
|
||||
const images: ProcessedContent['images'] = [];
|
||||
const videos: Array<{ type: 'url'; data: string; index: number }> = [];
|
||||
let imageIndex = 1;
|
||||
let videoIndex = 1;
|
||||
|
||||
// 处理 base64 图片
|
||||
for (const match of base64Matches) {
|
||||
const fullMatch = match[0];
|
||||
const base64Data = match[1];
|
||||
|
||||
images.push({
|
||||
type: 'base64',
|
||||
data: base64Data,
|
||||
index: imageIndex,
|
||||
});
|
||||
|
||||
textContent = textContent.replace(fullMatch, `[图片 ${imageIndex}]`);
|
||||
imageIndex++;
|
||||
}
|
||||
|
||||
// 用于避免重复处理同一 URL
|
||||
const processedUrls = new Set<string>();
|
||||
|
||||
// 优先处理 Markdown 格式的图片  - 能正确提取带查询参数的完整 URL
|
||||
for (const match of markdownImageMatches) {
|
||||
const fullMatch = match[0]; // 
|
||||
const url = match[1]; // 括号内的完整 URL
|
||||
|
||||
if (!processedUrls.has(url)) {
|
||||
processedUrls.add(url);
|
||||
images.push({
|
||||
type: 'url',
|
||||
data: url,
|
||||
index: imageIndex,
|
||||
});
|
||||
|
||||
textContent = textContent.replace(fullMatch, `[图片 ${imageIndex}]`);
|
||||
imageIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理普通图片 URL(排除已处理的)
|
||||
for (const match of imageUrlMatches) {
|
||||
const url = match[0];
|
||||
|
||||
if (!processedUrls.has(url)) {
|
||||
processedUrls.add(url);
|
||||
images.push({
|
||||
type: 'url',
|
||||
data: url,
|
||||
index: imageIndex,
|
||||
});
|
||||
|
||||
textContent = textContent.replace(url, `[图片 ${imageIndex}]`);
|
||||
imageIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理视频 URL(按优先级顺序)
|
||||
for (const pattern of videoUrlPatterns) {
|
||||
const matches = Array.from(content.matchAll(pattern));
|
||||
for (const match of matches) {
|
||||
let videoUrl: string;
|
||||
|
||||
if (match.length > 1 && match[1]) {
|
||||
// markdown链接格式,提取括号内的URL
|
||||
videoUrl = match[1];
|
||||
} else {
|
||||
// 直接的URL
|
||||
videoUrl = match[0];
|
||||
}
|
||||
|
||||
// 清理URL末尾可能的标点符号
|
||||
videoUrl = videoUrl.replace(/[.,;!?]*$/, '');
|
||||
|
||||
// 检查是否已经添加过这个视频URL
|
||||
const alreadyExists = videos.some(v => v.data === videoUrl);
|
||||
if (!alreadyExists) {
|
||||
videos.push({
|
||||
type: 'url',
|
||||
data: videoUrl,
|
||||
index: videoIndex,
|
||||
});
|
||||
|
||||
// 替换原文中的内容
|
||||
textContent = textContent.replace(match[0], `[视频 ${videoIndex}]`);
|
||||
videoIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
textContent,
|
||||
images,
|
||||
videos: videos.length > 0 ? videos : undefined,
|
||||
originalContent: content,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user