ci: auto build & obfuscate [2026-04-06 00:50:28] (Jenkins #7)

This commit is contained in:
Jenkins CI
2026-04-06 00:50:28 +08:00
parent ed3f52a568
commit 49c1fa6f60
142 changed files with 38769 additions and 29661 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,171 +1,195 @@
import { extension_settings } from "/scripts/extensions.js";
import { getRequestHeaders } from "/script.js";
import { extensionName } from "../../utils/settings.js";
const DEFAULT_CONFIG = {
apiUrl: "",
apiKey: "",
model: "",
maxTokens: 4000,
temperature: 0.7
};
export function getApiConfig(role) {
const settings = extension_settings[extensionName] || {};
const configKey = `acc_${role}_config`;
return { ...DEFAULT_CONFIG, ...(settings[configKey] || {}) };
}
export function setApiConfig(role, config) {
if (!extension_settings[extensionName]) {
extension_settings[extensionName] = {};
}
const configKey = `acc_${role}_config`;
extension_settings[extensionName][configKey] = { ...getApiConfig(role), ...config };
}
export async function callAi(role, messages, options = {}, onChunk = null) {
const config = { ...getApiConfig(role), ...options };
const roleName = role === 'executor' ? '执行者(模型A)' : '规划者(模型B)';
if (!config.apiUrl || !config.apiKey || !config.model) {
throw new Error(`[自动构建器] ${roleName} API 配置不完整,请检查 URL、Key 和模型设置。`);
}
console.log(`[自动构建器] 正在调用 AI (${roleName})...`, { model: config.model, messagesCount: messages.length, stream: !!onChunk });
const body = {
chat_completion_source: 'openai',
messages: messages,
model: config.model,
reverse_proxy: config.apiUrl,
proxy_password: config.apiKey,
stream: !!onChunk,
max_tokens: config.maxTokens > 0 ? config.maxTokens : undefined,
temperature: config.temperature,
top_p: 1,
custom_prompt_post_processing: 'strict',
enable_web_search: false,
frequency_penalty: 0,
presence_penalty: 0,
};
try {
const response = await fetch('/api/backends/chat-completions/generate', {
method: 'POST',
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API 请求失败: ${response.status} - ${errorText}`);
}
if (onChunk) {
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let fullContent = "";
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('data: ')) {
const dataStr = trimmedLine.slice(6).trim();
if (dataStr === '[DONE]') continue;
try {
const data = JSON.parse(dataStr);
const delta = data.choices[0].delta?.content || "";
if (delta) {
fullContent += delta;
onChunk(delta);
}
} catch (e) {
}
}
}
}
console.log(`[自动构建器] AI (${roleName}) 流式响应结束。长度: ${fullContent.length}`);
return fullContent;
} else {
const responseData = await response.json();
if (!responseData || !responseData.choices || responseData.choices.length === 0) {
if (responseData.error) {
throw new Error(`API 返回错误: ${responseData.error.message || JSON.stringify(responseData.error)}`);
}
throw new Error('API 返回了空响应。');
}
const content = responseData.choices[0].message?.content;
if (!content) {
console.warn(`[自动构建器] AI (${roleName}) 响应内容为空。完整响应:`, responseData);
if (responseData.choices && responseData.choices[0]) {
console.warn("Choices[0]:", responseData.choices[0]);
}
}
console.log(`[自动构建器] AI (${roleName}) 响应接收成功。长度: ${content?.length}`);
return content;
}
} catch (error) {
console.error(`[自动构建器] AI (${roleName}) 调用失败:`, error);
throw error;
}
}
export async function testConnection(role, config = {}) {
try {
const response = await callAi(role, [
{ role: 'user', content: 'Say hello' }
], { maxTokens: 50, ...config });
if (!response) {
return { success: false, error: "API 返回了空内容 (可能是被安全过滤或模型无响应)" };
}
return { success: true };
} catch (error) {
console.error(`[自动构建器] ${role} 连接测试失败:`, error);
return { success: false, error: error.message };
}
}
export async function fetchModels(apiUrl, apiKey) {
try {
const response = await fetch('/api/backends/chat-completions/status', {
method: 'POST',
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({
reverse_proxy: apiUrl,
proxy_password: apiKey,
chat_completion_source: 'openai'
})
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
const models = Array.isArray(data) ? data : (data.data || data.models || []);
return models.map(m => {
const id = m.id || m.model || m.name || m;
return typeof id === 'string' ? id : JSON.stringify(id);
}).sort();
} catch (error) {
console.error('[自动构建器] 获取模型列表失败:', error);
throw error;
}
}
import { extension_settings } from "/scripts/extensions.js";
import { getRequestHeaders } from "/script.js";
import { extensionName } from "../../utils/settings.js";
import { getSlotProfile } from '../api/api-resolver.js';
const DEFAULT_CONFIG = {
apiUrl: "",
apiKey: "",
model: "",
maxTokens: 4000,
temperature: 0.7
};
/** 同步读取旧版配置UI 加载 / 保存用) */
export function getApiConfig(role) {
const settings = extension_settings[extensionName] || {};
const configKey = `acc_${role}_config`;
return { ...DEFAULT_CONFIG, ...(settings[configKey] || {}) };
}
/** 异步读取配置Profile 优先fallback 到旧版 */
async function _resolveConfig(role) {
const profile = await getSlotProfile('autoCharCard');
if (profile) {
return {
apiUrl: profile.apiUrl,
apiKey: profile.apiKey ?? '',
model: profile.model,
maxTokens: profile.maxTokens ?? DEFAULT_CONFIG.maxTokens,
temperature: profile.temperature ?? DEFAULT_CONFIG.temperature,
};
}
return getApiConfig(role);
}
export function setApiConfig(role, config) {
if (!extension_settings[extensionName]) {
extension_settings[extensionName] = {};
}
const configKey = `acc_${role}_config`;
extension_settings[extensionName][configKey] = { ...getApiConfig(role), ...config };
}
export async function callAi(role, messages, options = {}, onChunk = null) {
const config = { ...(await _resolveConfig(role)), ...options };
const roleName = role === 'executor' ? '执行者(模型A)' : '规划者(模型B)';
if (!config.apiUrl || !config.apiKey || !config.model) {
throw new Error(`[自动构建器] ${roleName} API 配置不完整,请检查 URL、Key 和模型设置。`);
}
console.log(`[自动构建器] 正在调用 AI (${roleName})...`, { model: config.model, messagesCount: messages.length, stream: !!onChunk });
const body = {
chat_completion_source: 'openai',
messages: messages,
model: config.model,
reverse_proxy: config.apiUrl,
proxy_password: config.apiKey,
stream: !!onChunk,
max_tokens: config.maxTokens > 0 ? config.maxTokens : undefined,
temperature: config.temperature,
top_p: 1,
custom_prompt_post_processing: 'strict',
enable_web_search: false,
frequency_penalty: 0,
presence_penalty: 0,
};
try {
const response = await fetch('/api/backends/chat-completions/generate', {
method: 'POST',
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API 请求失败: ${response.status} - ${errorText}`);
}
if (onChunk) {
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let fullContent = "";
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('data: ')) {
const dataStr = trimmedLine.slice(6).trim();
if (dataStr === '[DONE]') continue;
try {
const data = JSON.parse(dataStr);
const delta = data.choices[0].delta?.content || "";
if (delta) {
fullContent += delta;
onChunk(delta);
}
} catch (e) {
}
}
}
}
console.log(`[自动构建器] AI (${roleName}) 流式响应结束。长度: ${fullContent.length}`);
return fullContent;
} else {
const responseData = await response.json();
if (!responseData || !responseData.choices || responseData.choices.length === 0) {
if (responseData.error) {
throw new Error(`API 返回错误: ${responseData.error.message || JSON.stringify(responseData.error)}`);
}
throw new Error('API 返回了空响应。');
}
const content = responseData.choices[0].message?.content;
if (!content) {
console.warn(`[自动构建器] AI (${roleName}) 响应内容为空。完整响应:`, responseData);
if (responseData.choices && responseData.choices[0]) {
console.warn("Choices[0]:", responseData.choices[0]);
}
}
console.log(`[自动构建器] AI (${roleName}) 响应接收成功。长度: ${content?.length}`);
return content;
}
} catch (error) {
console.error(`[自动构建器] AI (${roleName}) 调用失败:`, error);
throw error;
}
}
export async function testConnection(role, config = {}) {
try {
const response = await callAi(role, [
{ role: 'user', content: 'Say hello' }
], { maxTokens: 50, ...config });
if (!response) {
return { success: false, error: "API 返回了空内容 (可能是被安全过滤或模型无响应)" };
}
return { success: true };
} catch (error) {
console.error(`[自动构建器] ${role} 连接测试失败:`, error);
return { success: false, error: error.message };
}
}
export async function fetchModels(apiUrl, apiKey) {
// 若未传参,尝试从 Profile 或旧配置读取
if (!apiUrl || !apiKey) {
const resolved = await _resolveConfig('executor');
apiUrl = apiUrl || resolved.apiUrl;
apiKey = apiKey || resolved.apiKey;
}
try {
const response = await fetch('/api/backends/chat-completions/status', {
method: 'POST',
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({
reverse_proxy: apiUrl,
proxy_password: apiKey,
chat_completion_source: 'openai'
})
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
const models = Array.isArray(data) ? data : (data.data || data.models || []);
return models.map(m => {
const id = m.id || m.model || m.name || m;
return typeof id === 'string' ? id : JSON.stringify(id);
}).sort();
} catch (error) {
console.error('[自动构建器] 获取模型列表失败:', error);
throw error;
}
}

View File

@@ -1,272 +1,272 @@
import { characters, saveCharacterDebounced, this_chid, getCharacters, getRequestHeaders } from "/script.js";
import { getContext } from "/scripts/extensions.js";
import { extensionName } from "../../utils/settings.js";
async function saveCharacterById(chid) {
let currentChid = undefined;
try {
const context = getContext();
if (context) currentChid = context.characterId;
} catch (e) {}
if (currentChid === undefined) currentChid = this_chid;
if (currentChid === undefined && typeof window !== 'undefined' && window.this_chid !== undefined) {
currentChid = window.this_chid;
}
if (currentChid === undefined && typeof $ !== 'undefined') {
const selected = $('.character_select.selected, .character-list-item.selected');
if (selected.length) {
currentChid = selected.attr('chid');
}
}
if (typeof saveCharacterDebounced === 'function') {
if (currentChid === undefined || chid == currentChid) {
saveCharacterDebounced();
console.log(`[Amily2 CharAPI] Triggered saveCharacterDebounced for character ${chid} (Detected: ${currentChid})`);
return { success: true };
}
}
try {
const formData = new FormData();
formData.append('avatar_url', char.avatar);
formData.append('ch_name', char.name);
formData.append('description', char.description || '');
formData.append('personality', char.personality || '');
formData.append('scenario', char.scenario || '');
formData.append('first_mes', char.first_mes || '');
formData.append('mes_example', char.mes_example || '');
formData.append('creator', char.creator || '');
formData.append('creator_notes', char.creator_notes || '');
formData.append('tags', Array.isArray(char.tags) ? char.tags.join(',') : (char.tags || ''));
formData.append('talkativeness', char.talkativeness || '0.5');
formData.append('fav', char.fav || 'false');
if (char.data) {
formData.append('extensions', JSON.stringify(char.data));
}
if (char.data && Array.isArray(char.data.alternate_greetings)) {
for (const value of char.data.alternate_greetings) {
formData.append('alternate_greetings', value);
}
}
const response = await fetch('/api/characters/edit', {
method: 'POST',
headers: getRequestHeaders({ omitContentType: true }),
body: formData
});
if (!response.ok) {
const errorText = await response.text();
console.error(`[Amily2 CharAPI] Failed to save character ${chid}:`, response.statusText, errorText);
return { success: false, message: `Save failed: ${response.statusText}` };
} else {
console.log(`[Amily2 CharAPI] Successfully saved character ${chid} (Background)`);
return { success: true };
}
} catch (e) {
console.error(`[Amily2 CharAPI] Error saving character ${chid}:`, e);
return { success: false, message: `Save error: ${e.message}` };
}
}
export function getCharacter(chid = this_chid) {
if (chid === undefined || chid < 0 || !characters[chid]) {
console.warn(`[Amily2 CharAPI] Invalid character ID: ${chid}`);
return null;
}
return characters[chid];
}
export async function updateCharacter(chid, updates) {
const char = getCharacter(chid);
if (!char) return false;
let changed = false;
const fields = ['name', 'description', 'personality', 'scenario', 'first_mes', 'mes_example'];
fields.forEach(field => {
if (updates[field] !== undefined && char[field] !== updates[field]) {
char[field] = updates[field];
changed = true;
}
});
if (changed) {
const success = await saveCharacterById(chid);
if (success) {
console.log(`[Amily2 CharAPI] Updated character ${chid}:`, Object.keys(updates));
return true;
}
return false;
}
return false;
}
export function getFirstMessages(chid) {
const char = getCharacter(chid);
if (!char) return [];
const messages = [char.first_mes];
if (char.data && Array.isArray(char.data.alternate_greetings)) {
messages.push(...char.data.alternate_greetings);
}
return messages;
}
export async function addFirstMessage(chid, message) {
const char = getCharacter(chid);
if (!char) return false;
if (!char.data) char.data = {};
if (!Array.isArray(char.data.alternate_greetings)) {
char.data.alternate_greetings = [];
}
char.data.alternate_greetings.push(message);
const success = await saveCharacterById(chid);
if (success) {
console.log(`[Amily2 CharAPI] Added alternate greeting to character ${chid}`);
return true;
}
return false;
}
export async function updateFirstMessage(chid, index, message) {
const char = getCharacter(chid);
if (!char) return false;
if (index === 0) {
char.first_mes = message;
} else {
const altIndex = index - 1;
if (char.data && Array.isArray(char.data.alternate_greetings) && char.data.alternate_greetings[altIndex] !== undefined) {
char.data.alternate_greetings[altIndex] = message;
} else {
console.warn(`[Amily2 CharAPI] Alternate greeting index out of bounds: ${altIndex}`);
return false;
}
}
const success = await saveCharacterById(chid);
if (success) {
console.log(`[Amily2 CharAPI] Updated greeting ${index} for character ${chid}`);
return true;
}
return false;
}
export async function removeFirstMessage(chid, index) {
const char = getCharacter(chid);
if (!char) return false;
if (index === 0) {
console.warn(`[Amily2 CharAPI] Cannot remove main greeting, clearing instead.`);
char.first_mes = "";
} else {
const altIndex = index - 1;
if (char.data && Array.isArray(char.data.alternate_greetings) && char.data.alternate_greetings[altIndex] !== undefined) {
char.data.alternate_greetings.splice(altIndex, 1);
} else {
console.warn(`[Amily2 CharAPI] Alternate greeting index out of bounds: ${altIndex}`);
return false;
}
}
const success = await saveCharacterById(chid);
if (success) {
console.log(`[Amily2 CharAPI] Removed greeting ${index} for character ${chid}`);
return true;
}
return false;
}
export async function createNewCharacter(name) {
try {
const formData = new FormData();
formData.append('ch_name', name);
formData.append('description', '');
formData.append('personality', '');
formData.append('scenario', '');
formData.append('first_mes', 'Hello!');
formData.append('mes_example', '');
formData.append('creator', 'Amily2-AutoChar');
formData.append('creator_notes', 'Character created automatically by Amily2 AutoChar Card.');
formData.append('tags', '');
formData.append('character_version', '1.0');
formData.append('post_history_instructions', '');
formData.append('system_prompt', '');
formData.append('talkativeness', '0.5');
formData.append('extensions', '{}');
formData.append('fav', 'false');
formData.append('world', '');
formData.append('depth_prompt_prompt', '');
formData.append('depth_prompt_depth', '4');
formData.append('depth_prompt_role', 'system');
try {
const res = await fetch(`scripts/extensions/third-party/${extensionName}/core/auto-char-card/Amily.png`);
if (res.ok) {
const blob = await res.blob();
formData.append('avatar', blob, 'default.png');
} else {
throw new Error('Failed to fetch default avatar');
}
} catch (e) {
console.warn("[Amily2 CharAPI] Failed to load default avatar, using fallback 1x1 PNG.", e);
const base64Png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
const byteCharacters = atob(base64Png);
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: 'image/png' });
formData.append('avatar', blob, 'default.png');
}
const response = await fetch('/api/characters/create', {
method: 'POST',
headers: getRequestHeaders({ omitContentType: true }),
body: formData,
});
if (response.ok) {
const avatarId = await response.text();
console.log(`[Amily2 CharAPI] Created character: ${name}, Avatar ID: ${avatarId}`);
await getCharacters();
const newChid = characters.findIndex(c => c.avatar === avatarId);
if (newChid !== -1) {
return newChid;
}
return -2;
} else {
console.error(`[Amily2 CharAPI] Failed to create character: ${response.statusText}`);
return -1;
}
} catch (error) {
console.error(`[Amily2 CharAPI] Error creating character:`, error);
return -1;
}
}
import { characters, saveCharacterDebounced, this_chid, getCharacters, getRequestHeaders } from "/script.js";
import { getContext } from "/scripts/extensions.js";
import { extensionName } from "../../utils/settings.js";
async function saveCharacterById(chid) {
let currentChid = undefined;
try {
const context = getContext();
if (context) currentChid = context.characterId;
} catch (e) {}
if (currentChid === undefined) currentChid = this_chid;
if (currentChid === undefined && typeof window !== 'undefined' && window.this_chid !== undefined) {
currentChid = window.this_chid;
}
if (currentChid === undefined && typeof $ !== 'undefined') {
const selected = $('.character_select.selected, .character-list-item.selected');
if (selected.length) {
currentChid = selected.attr('chid');
}
}
if (typeof saveCharacterDebounced === 'function') {
if (currentChid === undefined || chid == currentChid) {
saveCharacterDebounced();
console.log(`[Amily2 CharAPI] Triggered saveCharacterDebounced for character ${chid} (Detected: ${currentChid})`);
return { success: true };
}
}
try {
const formData = new FormData();
formData.append('avatar_url', char.avatar);
formData.append('ch_name', char.name);
formData.append('description', char.description || '');
formData.append('personality', char.personality || '');
formData.append('scenario', char.scenario || '');
formData.append('first_mes', char.first_mes || '');
formData.append('mes_example', char.mes_example || '');
formData.append('creator', char.creator || '');
formData.append('creator_notes', char.creator_notes || '');
formData.append('tags', Array.isArray(char.tags) ? char.tags.join(',') : (char.tags || ''));
formData.append('talkativeness', char.talkativeness || '0.5');
formData.append('fav', char.fav || 'false');
if (char.data) {
formData.append('extensions', JSON.stringify(char.data));
}
if (char.data && Array.isArray(char.data.alternate_greetings)) {
for (const value of char.data.alternate_greetings) {
formData.append('alternate_greetings', value);
}
}
const response = await fetch('/api/characters/edit', {
method: 'POST',
headers: getRequestHeaders({ omitContentType: true }),
body: formData
});
if (!response.ok) {
const errorText = await response.text();
console.error(`[Amily2 CharAPI] Failed to save character ${chid}:`, response.statusText, errorText);
return { success: false, message: `Save failed: ${response.statusText}` };
} else {
console.log(`[Amily2 CharAPI] Successfully saved character ${chid} (Background)`);
return { success: true };
}
} catch (e) {
console.error(`[Amily2 CharAPI] Error saving character ${chid}:`, e);
return { success: false, message: `Save error: ${e.message}` };
}
}
export function getCharacter(chid = this_chid) {
if (chid === undefined || chid < 0 || !characters[chid]) {
console.warn(`[Amily2 CharAPI] Invalid character ID: ${chid}`);
return null;
}
return characters[chid];
}
export async function updateCharacter(chid, updates) {
const char = getCharacter(chid);
if (!char) return false;
let changed = false;
const fields = ['name', 'description', 'personality', 'scenario', 'first_mes', 'mes_example'];
fields.forEach(field => {
if (updates[field] !== undefined && char[field] !== updates[field]) {
char[field] = updates[field];
changed = true;
}
});
if (changed) {
const success = await saveCharacterById(chid);
if (success) {
console.log(`[Amily2 CharAPI] Updated character ${chid}:`, Object.keys(updates));
return true;
}
return false;
}
return false;
}
export function getFirstMessages(chid) {
const char = getCharacter(chid);
if (!char) return [];
const messages = [char.first_mes];
if (char.data && Array.isArray(char.data.alternate_greetings)) {
messages.push(...char.data.alternate_greetings);
}
return messages;
}
export async function addFirstMessage(chid, message) {
const char = getCharacter(chid);
if (!char) return false;
if (!char.data) char.data = {};
if (!Array.isArray(char.data.alternate_greetings)) {
char.data.alternate_greetings = [];
}
char.data.alternate_greetings.push(message);
const success = await saveCharacterById(chid);
if (success) {
console.log(`[Amily2 CharAPI] Added alternate greeting to character ${chid}`);
return true;
}
return false;
}
export async function updateFirstMessage(chid, index, message) {
const char = getCharacter(chid);
if (!char) return false;
if (index === 0) {
char.first_mes = message;
} else {
const altIndex = index - 1;
if (char.data && Array.isArray(char.data.alternate_greetings) && char.data.alternate_greetings[altIndex] !== undefined) {
char.data.alternate_greetings[altIndex] = message;
} else {
console.warn(`[Amily2 CharAPI] Alternate greeting index out of bounds: ${altIndex}`);
return false;
}
}
const success = await saveCharacterById(chid);
if (success) {
console.log(`[Amily2 CharAPI] Updated greeting ${index} for character ${chid}`);
return true;
}
return false;
}
export async function removeFirstMessage(chid, index) {
const char = getCharacter(chid);
if (!char) return false;
if (index === 0) {
console.warn(`[Amily2 CharAPI] Cannot remove main greeting, clearing instead.`);
char.first_mes = "";
} else {
const altIndex = index - 1;
if (char.data && Array.isArray(char.data.alternate_greetings) && char.data.alternate_greetings[altIndex] !== undefined) {
char.data.alternate_greetings.splice(altIndex, 1);
} else {
console.warn(`[Amily2 CharAPI] Alternate greeting index out of bounds: ${altIndex}`);
return false;
}
}
const success = await saveCharacterById(chid);
if (success) {
console.log(`[Amily2 CharAPI] Removed greeting ${index} for character ${chid}`);
return true;
}
return false;
}
export async function createNewCharacter(name) {
try {
const formData = new FormData();
formData.append('ch_name', name);
formData.append('description', '');
formData.append('personality', '');
formData.append('scenario', '');
formData.append('first_mes', 'Hello!');
formData.append('mes_example', '');
formData.append('creator', 'Amily2-AutoChar');
formData.append('creator_notes', 'Character created automatically by Amily2 AutoChar Card.');
formData.append('tags', '');
formData.append('character_version', '1.0');
formData.append('post_history_instructions', '');
formData.append('system_prompt', '');
formData.append('talkativeness', '0.5');
formData.append('extensions', '{}');
formData.append('fav', 'false');
formData.append('world', '');
formData.append('depth_prompt_prompt', '');
formData.append('depth_prompt_depth', '4');
formData.append('depth_prompt_role', 'system');
try {
const res = await fetch(`scripts/extensions/third-party/${extensionName}/core/auto-char-card/Amily.png`);
if (res.ok) {
const blob = await res.blob();
formData.append('avatar', blob, 'default.png');
} else {
throw new Error('Failed to fetch default avatar');
}
} catch (e) {
console.warn("[Amily2 CharAPI] Failed to load default avatar, using fallback 1x1 PNG.", e);
const base64Png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
const byteCharacters = atob(base64Png);
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: 'image/png' });
formData.append('avatar', blob, 'default.png');
}
const response = await fetch('/api/characters/create', {
method: 'POST',
headers: getRequestHeaders({ omitContentType: true }),
body: formData,
});
if (response.ok) {
const avatarId = await response.text();
console.log(`[Amily2 CharAPI] Created character: ${name}, Avatar ID: ${avatarId}`);
await getCharacters();
const newChid = characters.findIndex(c => c.avatar === avatarId);
if (newChid !== -1) {
return newChid;
}
return -2;
} else {
console.error(`[Amily2 CharAPI] Failed to create character: ${response.statusText}`);
return -1;
}
} catch (error) {
console.error(`[Amily2 CharAPI] Error creating character:`, error);
return -1;
}
}

View File

@@ -1,128 +1,156 @@
export class ContextManager {
constructor() {
this.keepToolOutputTurns = 5;
this.tokenLimit = 100000;
this.rules = [];
this.worldInfo = [];
this.activeWorldInfoCache = new Map();
this.cacheDuration = 3;
}
addRule(rule) {
this.rules.push({
id: rule.id || Date.now().toString(),
keyword: rule.keyword || null,
content: rule.content,
enabled: rule.enabled !== undefined ? rule.enabled : true
});
}
setWorldInfo(entries) {
this.worldInfo = entries.map(entry => {
let keys = [];
if (Array.isArray(entry.key)) {
keys = entry.key;
} else if (typeof entry.key === 'string') {
keys = entry.key.split(',').map(k => k.trim()).filter(k => k);
}
return {
id: entry.uid,
keys: keys,
content: entry.content,
enabled: entry.enabled !== false
};
});
}
getRelevantContext(contextText) {
const relevantRules = this.rules.filter(rule => {
if (!rule.enabled) return false;
if (!rule.keyword) return true;
return contextText.includes(rule.keyword);
});
const currentMatches = this.worldInfo.filter(entry => {
if (!entry.enabled) return false;
if (!entry.keys || entry.keys.length === 0) return false;
return entry.keys.some(key => contextText.includes(key));
});
for (const [uid, data] of this.activeWorldInfoCache) {
data.turnsLeft--;
if (data.turnsLeft <= 0) {
this.activeWorldInfoCache.delete(uid);
}
}
currentMatches.forEach(entry => {
this.activeWorldInfoCache.set(entry.id, { turnsLeft: this.cacheDuration });
});
const allRelevantUIDs = new Set([...currentMatches.map(e => e.id), ...this.activeWorldInfoCache.keys()]);
const relevantWorldInfo = this.worldInfo.filter(entry => allRelevantUIDs.has(entry.id));
return {
rules: relevantRules,
worldInfo: relevantWorldInfo
};
}
estimateTokens(text) {
return Math.ceil((text || '').length / 3.5);
}
buildMessages(systemPrompt, history, maxTokens) {
const limit = maxTokens || this.tokenLimit;
const systemTokens = this.estimateTokens(systemPrompt);
let availableTokens = limit - systemTokens - 1000;
if (availableTokens < 0) availableTokens = 1000;
const optimizedHistory = this.optimizeToolOutputs(history);
const finalMessages = [];
let currentTokens = 0;
for (let i = optimizedHistory.length - 1; i >= 0; i--) {
const msg = optimizedHistory[i];
const msgTokens = this.estimateTokens(msg.content);
if (currentTokens + msgTokens > availableTokens) {
finalMessages.unshift({ role: 'system', content: "[Earlier history truncated to save tokens]" });
break;
}
finalMessages.unshift(msg);
currentTokens += msgTokens;
}
return [
{ role: 'system', content: systemPrompt },
...finalMessages
];
}
optimizeToolOutputs(history) {
let toolOutputCount = 0;
const reversedHistory = [...history].reverse();
const processedReversed = reversedHistory.map((msg) => {
if (msg.role === 'user' && msg.content.startsWith('[Tool Result')) {
toolOutputCount++;
if (toolOutputCount > this.keepToolOutputTurns) {
const firstLine = msg.content.split('\n')[0];
return {
role: msg.role,
content: `${firstLine}\n[Content hidden to save tokens. The tool was executed successfully.]`
};
}
}
return msg;
});
return processedReversed.reverse();
}
}
export class ContextManager {
constructor() {
this.keepToolOutputTurns = 5;
this.tokenLimit = 100000;
this.rules = this.loadRules();
this.worldInfo = [];
this.activeWorldInfoCache = new Map();
this.cacheDuration = 3;
}
loadRules() {
try {
const savedRules = localStorage.getItem('amily2_acc_rules');
if (savedRules) {
return JSON.parse(savedRules);
}
} catch (e) {
console.error('[AutoCharCard] Failed to load rules:', e);
}
return [];
}
saveRules() {
try {
localStorage.setItem('amily2_acc_rules', JSON.stringify(this.rules));
} catch (e) {
console.error('[AutoCharCard] Failed to save rules:', e);
}
}
addRule(rule) {
this.rules.push({
id: rule.id || Date.now().toString(),
keyword: rule.keyword || null,
content: rule.content,
enabled: rule.enabled !== undefined ? rule.enabled : true
});
this.saveRules();
}
removeRule(index) {
if (index >= 0 && index < this.rules.length) {
this.rules.splice(index, 1);
this.saveRules();
}
}
setWorldInfo(entries) {
this.worldInfo = entries.map(entry => {
let keys = [];
if (Array.isArray(entry.key)) {
keys = entry.key;
} else if (typeof entry.key === 'string') {
keys = entry.key.split(',').map(k => k.trim()).filter(k => k);
}
return {
id: entry.uid,
keys: keys,
content: entry.content,
enabled: entry.enabled !== false
};
});
}
getRelevantContext(contextText) {
const relevantRules = this.rules.filter(rule => {
if (!rule.enabled) return false;
if (!rule.keyword) return true;
return contextText.includes(rule.keyword);
});
const currentMatches = this.worldInfo.filter(entry => {
if (!entry.enabled) return false;
if (!entry.keys || entry.keys.length === 0) return false;
return entry.keys.some(key => contextText.includes(key));
});
for (const [uid, data] of this.activeWorldInfoCache) {
data.turnsLeft--;
if (data.turnsLeft <= 0) {
this.activeWorldInfoCache.delete(uid);
}
}
currentMatches.forEach(entry => {
this.activeWorldInfoCache.set(entry.id, { turnsLeft: this.cacheDuration });
});
const allRelevantUIDs = new Set([...currentMatches.map(e => e.id), ...this.activeWorldInfoCache.keys()]);
const relevantWorldInfo = this.worldInfo.filter(entry => allRelevantUIDs.has(entry.id));
return {
rules: relevantRules,
worldInfo: relevantWorldInfo
};
}
estimateTokens(text) {
return Math.ceil((text || '').length / 3.5);
}
buildMessages(systemPrompt, history, maxTokens) {
const limit = maxTokens || this.tokenLimit;
const systemTokens = this.estimateTokens(systemPrompt);
let availableTokens = limit - systemTokens - 1000;
if (availableTokens < 0) availableTokens = 1000;
const optimizedHistory = this.optimizeToolOutputs(history);
const finalMessages = [];
let currentTokens = 0;
for (let i = optimizedHistory.length - 1; i >= 0; i--) {
const msg = optimizedHistory[i];
const msgTokens = this.estimateTokens(msg.content);
if (currentTokens + msgTokens > availableTokens) {
finalMessages.unshift({ role: 'system', content: "[Earlier history truncated to save tokens]" });
break;
}
finalMessages.unshift(msg);
currentTokens += msgTokens;
}
return [
{ role: 'system', content: systemPrompt },
...finalMessages
];
}
optimizeToolOutputs(history) {
let toolOutputCount = 0;
const reversedHistory = [...history].reverse();
const processedReversed = reversedHistory.map((msg) => {
if (msg.role === 'user' && msg.content.startsWith('[Tool Result')) {
toolOutputCount++;
if (toolOutputCount > this.keepToolOutputTurns) {
const firstLine = msg.content.split('\n')[0];
return {
role: msg.role,
content: `${firstLine}\n[Content hidden to save tokens. The tool was executed successfully.]`
};
}
}
return msg;
});
return processedReversed.reverse();
}
}

View File

@@ -1,91 +1,91 @@
import { callAi, getApiConfig } from "./api.js";
export class MemorySystem {
constructor() {
this.summarizePrompt = `
The current conversation context is growing large. Your task is to create a comprehensive, structured summary of the character/world generation process so far.
This summary will be used as the "Memory" for the next steps, so it must be detailed enough to prevent information loss.
Please summarize the following:
1. **Core Identity**: Name, Age, Gender, Role, etc.
2. **Personality & Traits**: Key personality keywords, behavioral quirks, speech patterns.
3. **Appearance**: Physical description, clothing, accessories.
4. **Background & Lore**: Backstory, world setting, important relationships.
5. **Current Progress**: What has been completed, what is currently being worked on, and what is left to do.
6. **User Preferences**: Any specific constraints or requests made by the user (e.g., "Make her tsundere", "Don't use modern technology").
Format your response as a structured Markdown block.
`;
}
async extractKeyFacts(history) {
const extractionPrompt = `
Analyze the recent conversation and extract "Key Facts" that should be remembered long-term.
Key Facts include:
- Specific decisions made (e.g., "Character has blue eyes", "Weapon is a sword").
- User preferences stated (e.g., "User dislikes horror").
- Completed milestones.
Do NOT include temporary conversation details or planning steps.
Return the facts as a JSON array of strings. Example: ["Eyes: Blue", "Class: Mage"].
Output ONLY valid JSON.
`;
const recentHistory = history.slice(-5);
const messages = [
{ role: 'system', content: extractionPrompt },
...recentHistory
];
try {
const response = await callAi('executor', messages, {
max_tokens: 500,
temperature: 0.3
});
const cleanResponse = response.replace(/```json/g, '').replace(/```/g, '').trim();
const facts = JSON.parse(cleanResponse);
return Array.isArray(facts) ? facts : [];
} catch (error) {
console.warn("Failed to extract key facts:", error);
return [];
}
}
async summarize(history, taskState) {
const config = getApiConfig('executor');
const newFacts = await this.extractKeyFacts(history);
if (newFacts.length > 0) {
taskState.addKeyFacts(newFacts);
}
const contextMsg = `
[System Note]: The following is the current Task State. Use this to inform your summary.
${taskState.getPromptContext()}
`;
const messages = [
{ role: 'system', content: this.summarizePrompt },
...history.slice(-10),
{ role: 'user', content: `Please summarize the session based on the history above. ${contextMsg}` }
];
try {
const response = await callAi('executor', messages, {
max_tokens: 2000,
temperature: 0.5
});
return response;
} catch (error) {
console.error("Failed to generate summary:", error);
return null;
}
}
shouldSummarize(history, tokenCount, maxTokens) {
const tokenUsageRatio = tokenCount / maxTokens;
if (tokenUsageRatio > 0.7) return true;
if (history.length > 35) return true;
return false;
}
}
import { callAi, getApiConfig } from "./api.js";
export class MemorySystem {
constructor() {
this.summarizePrompt = `
The current conversation context is growing large. Your task is to create a comprehensive, structured summary of the character/world generation process so far.
This summary will be used as the "Memory" for the next steps, so it must be detailed enough to prevent information loss.
Please summarize the following:
1. **Core Identity**: Name, Age, Gender, Role, etc.
2. **Personality & Traits**: Key personality keywords, behavioral quirks, speech patterns.
3. **Appearance**: Physical description, clothing, accessories.
4. **Background & Lore**: Backstory, world setting, important relationships.
5. **Current Progress**: What has been completed, what is currently being worked on, and what is left to do.
6. **User Preferences**: Any specific constraints or requests made by the user (e.g., "Make her tsundere", "Don't use modern technology").
Format your response as a structured Markdown block.
`;
}
async extractKeyFacts(history) {
const extractionPrompt = `
Analyze the recent conversation and extract "Key Facts" that should be remembered long-term.
Key Facts include:
- Specific decisions made (e.g., "Character has blue eyes", "Weapon is a sword").
- User preferences stated (e.g., "User dislikes horror").
- Completed milestones.
Do NOT include temporary conversation details or planning steps.
Return the facts as a JSON array of strings. Example: ["Eyes: Blue", "Class: Mage"].
Output ONLY valid JSON.
`;
const recentHistory = history.slice(-5);
const messages = [
{ role: 'system', content: extractionPrompt },
...recentHistory
];
try {
const response = await callAi('executor', messages, {
max_tokens: 500,
temperature: 0.3
});
const cleanResponse = response.replace(/```json/g, '').replace(/```/g, '').trim();
const facts = JSON.parse(cleanResponse);
return Array.isArray(facts) ? facts : [];
} catch (error) {
console.warn("Failed to extract key facts:", error);
return [];
}
}
async summarize(history, taskState) {
const config = getApiConfig('executor');
const newFacts = await this.extractKeyFacts(history);
if (newFacts.length > 0) {
taskState.addKeyFacts(newFacts);
}
const contextMsg = `
[System Note]: The following is the current Task State. Use this to inform your summary.
${taskState.getPromptContext()}
`;
const messages = [
{ role: 'system', content: this.summarizePrompt },
...history.slice(-10),
{ role: 'user', content: `Please summarize the session based on the history above. ${contextMsg}` }
];
try {
const response = await callAi('executor', messages, {
max_tokens: 2000,
temperature: 0.5
});
return response;
} catch (error) {
console.error("Failed to generate summary:", error);
return null;
}
}
shouldSummarize(history, tokenCount, maxTokens) {
const tokenUsageRatio = tokenCount / maxTokens;
if (tokenUsageRatio > 0.7) return true;
if (history.length > 35) return true;
return false;
}
}

View File

@@ -1,109 +1,109 @@
export class TaskState {
constructor() {
this.reset();
}
reset() {
this.originalRequest = "";
this.currentGoal = "";
this.completedSteps = [];
this.pendingSteps = [];
this.summary = "";
this.generatedData = {};
this.style_reference = "";
this.keyFacts = [];
this.lastSummaryTimestamp = 0;
}
init(request) {
this.reset();
this.originalRequest = request;
this.currentGoal = "Analyze request and plan steps";
this.lastSummaryTimestamp = Date.now();
}
updateSummary(newSummary) {
this.summary = newSummary;
this.lastSummaryTimestamp = Date.now();
}
addCompletedStep(step) {
this.completedSteps.push(step);
}
setPendingSteps(steps) {
this.pendingSteps = steps;
}
setCurrentGoal(goal) {
this.currentGoal = goal;
}
updateGeneratedData(key, value) {
this.generatedData[key] = value;
}
setStyle(style) {
this.style_reference = style;
}
addKeyFacts(facts) {
this.keyFacts.push(...facts);
}
getPromptContext() {
let context = `\n# Task State\n`;
context += `- **Original Request**: ${this.originalRequest}\n`;
context += `- **Current Goal**: ${this.currentGoal}\n`;
if (this.style_reference) {
context += `- **Style Reference**: ${this.style_reference}\n`;
}
if (this.completedSteps.length > 0) {
context += `- **Completed Steps**:\n${this.completedSteps.map(s => ` - ${s}`).join('\n')}\n`;
}
if (this.pendingSteps.length > 0) {
context += `- **Pending Steps**:\n${this.pendingSteps.map(s => ` - ${s}`).join('\n')}\n`;
}
if (this.keyFacts.length > 0) {
context += `\n# Key Facts (Long Term Memory)\n`;
this.keyFacts.forEach(fact => context += `- ${fact}\n`);
}
if (this.summary) {
context += `\n# Recent Context Summary\n${this.summary}\n`;
}
return context;
}
toJSON() {
return {
originalRequest: this.originalRequest,
currentGoal: this.currentGoal,
completedSteps: this.completedSteps,
pendingSteps: this.pendingSteps,
summary: this.summary,
generatedData: this.generatedData,
style_reference: this.style_reference,
keyFacts: this.keyFacts,
lastSummaryTimestamp: this.lastSummaryTimestamp
};
}
fromJSON(json) {
if (!json) return;
this.originalRequest = json.originalRequest || "";
this.currentGoal = json.currentGoal || "";
this.completedSteps = json.completedSteps || [];
this.pendingSteps = json.pendingSteps || [];
this.summary = json.summary || "";
this.generatedData = json.generatedData || {};
this.style_reference = json.style_reference || "";
this.keyFacts = json.keyFacts || [];
this.lastSummaryTimestamp = json.lastSummaryTimestamp || 0;
}
}
export class TaskState {
constructor() {
this.reset();
}
reset() {
this.originalRequest = "";
this.currentGoal = "";
this.completedSteps = [];
this.pendingSteps = [];
this.summary = "";
this.generatedData = {};
this.style_reference = "";
this.keyFacts = [];
this.lastSummaryTimestamp = 0;
}
init(request) {
this.reset();
this.originalRequest = request;
this.currentGoal = "Analyze request and plan steps";
this.lastSummaryTimestamp = Date.now();
}
updateSummary(newSummary) {
this.summary = newSummary;
this.lastSummaryTimestamp = Date.now();
}
addCompletedStep(step) {
this.completedSteps.push(step);
}
setPendingSteps(steps) {
this.pendingSteps = steps;
}
setCurrentGoal(goal) {
this.currentGoal = goal;
}
updateGeneratedData(key, value) {
this.generatedData[key] = value;
}
setStyle(style) {
this.style_reference = style;
}
addKeyFacts(facts) {
this.keyFacts.push(...facts);
}
getPromptContext() {
let context = `\n# Task State\n`;
context += `- **Original Request**: ${this.originalRequest}\n`;
context += `- **Current Goal**: ${this.currentGoal}\n`;
if (this.style_reference) {
context += `- **Style Reference**: ${this.style_reference}\n`;
}
if (this.completedSteps.length > 0) {
context += `- **Completed Steps**:\n${this.completedSteps.map(s => ` - ${s}`).join('\n')}\n`;
}
if (this.pendingSteps.length > 0) {
context += `- **Pending Steps**:\n${this.pendingSteps.map(s => ` - ${s}`).join('\n')}\n`;
}
if (this.keyFacts.length > 0) {
context += `\n# Key Facts (Long Term Memory)\n`;
this.keyFacts.forEach(fact => context += `- ${fact}\n`);
}
if (this.summary) {
context += `\n# Recent Context Summary\n${this.summary}\n`;
}
return context;
}
toJSON() {
return {
originalRequest: this.originalRequest,
currentGoal: this.currentGoal,
completedSteps: this.completedSteps,
pendingSteps: this.pendingSteps,
summary: this.summary,
generatedData: this.generatedData,
style_reference: this.style_reference,
keyFacts: this.keyFacts,
lastSummaryTimestamp: this.lastSummaryTimestamp
};
}
fromJSON(json) {
if (!json) return;
this.originalRequest = json.originalRequest || "";
this.currentGoal = json.currentGoal || "";
this.completedSteps = json.completedSteps || [];
this.pendingSteps = json.pendingSteps || [];
this.summary = json.summary || "";
this.generatedData = json.generatedData || {};
this.style_reference = json.style_reference || "";
this.keyFacts = json.keyFacts || [];
this.lastSummaryTimestamp = json.lastSummaryTimestamp || 0;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff