mirror of
https://github.com/Cola-Echo/Cola.git
synced 2026-06-06 03:35:50 +00:00
124 lines
3.9 KiB
JavaScript
124 lines
3.9 KiB
JavaScript
/**
|
|
* 角色卡导入:从 PNG/JSON 解析 + 导入到 SillyTavern
|
|
*/
|
|
|
|
import { getRequestHeaders } from '../../../../script.js';
|
|
|
|
// 从 PNG 提取角色卡数据 (V2 格式)
|
|
export async function extractCharacterFromPNG(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = async function (e) {
|
|
try {
|
|
const arrayBuffer = e.target.result;
|
|
const dataView = new DataView(arrayBuffer);
|
|
|
|
const pngSignature = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
for (let i = 0; i < 8; i++) {
|
|
if (dataView.getUint8(i) !== pngSignature[i]) {
|
|
throw new Error('不是有效的 PNG 文件');
|
|
}
|
|
}
|
|
|
|
let offset = 8;
|
|
while (offset < arrayBuffer.byteLength) {
|
|
const length = dataView.getUint32(offset);
|
|
const type = String.fromCharCode(
|
|
dataView.getUint8(offset + 4),
|
|
dataView.getUint8(offset + 5),
|
|
dataView.getUint8(offset + 6),
|
|
dataView.getUint8(offset + 7)
|
|
);
|
|
|
|
if (type === 'tEXt' || type === 'iTXt') {
|
|
const chunkData = new Uint8Array(arrayBuffer, offset + 8, length);
|
|
const text = new TextDecoder('utf-8').decode(chunkData);
|
|
|
|
if (text.startsWith('chara\0')) {
|
|
const base64Data = text.substring(6);
|
|
|
|
const binaryStr = atob(base64Data);
|
|
const bytes = new Uint8Array(binaryStr.length);
|
|
for (let i = 0; i < binaryStr.length; i++) {
|
|
bytes[i] = binaryStr.charCodeAt(i);
|
|
}
|
|
const jsonStr = new TextDecoder('utf-8').decode(bytes);
|
|
const charData = JSON.parse(jsonStr);
|
|
|
|
const uint8Array = new Uint8Array(arrayBuffer);
|
|
let binary = '';
|
|
for (let i = 0; i < uint8Array.length; i++) {
|
|
binary += String.fromCharCode(uint8Array[i]);
|
|
}
|
|
const avatarBase64 = 'data:image/png;base64,' + btoa(binary);
|
|
|
|
resolve({
|
|
name: charData.name || charData.data?.name || '未知角色',
|
|
description: charData.description || charData.data?.description || '',
|
|
avatar: avatarBase64,
|
|
rawData: charData
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
offset += 12 + length;
|
|
}
|
|
|
|
throw new Error('PNG 文件中未找到角色卡数据');
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
};
|
|
reader.onerror = () => reject(new Error('文件读取失败'));
|
|
reader.readAsArrayBuffer(file);
|
|
});
|
|
}
|
|
|
|
// 从 JSON 导入角色卡
|
|
export async function extractCharacterFromJSON(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = function (e) {
|
|
try {
|
|
const charData = JSON.parse(e.target.result);
|
|
resolve({
|
|
name: charData.name || charData.data?.name || '未知角色',
|
|
description: charData.description || charData.data?.description || charData.personality || '',
|
|
avatar: charData.avatar || null,
|
|
rawData: charData
|
|
});
|
|
} catch (err) {
|
|
reject(new Error('JSON 解析失败'));
|
|
}
|
|
};
|
|
reader.onerror = () => reject(new Error('文件读取失败'));
|
|
reader.readAsText(file);
|
|
});
|
|
}
|
|
|
|
// 导入角色卡到 SillyTavern
|
|
export async function importCharacterToST(characterData) {
|
|
try {
|
|
const formData = new FormData();
|
|
if (characterData.file) {
|
|
formData.append('avatar', characterData.file);
|
|
}
|
|
|
|
const response = await fetch('/api/characters/import', {
|
|
method: 'POST',
|
|
headers: getRequestHeaders(),
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('导入失败');
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (err) {
|
|
console.error('[可乐] 导入角色卡失败:', err);
|
|
throw err;
|
|
}
|
|
}
|