mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 15:05:51 +00:00
Add files via upload
This commit is contained in:
@@ -36,18 +36,25 @@ export class AgentManager {
|
|||||||
this.approvalRequired = required;
|
this.approvalRequired = required;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePendingToolArgs(newArgs) {
|
||||||
|
if (this.pendingToolCall) {
|
||||||
|
this.pendingToolCall.arguments = { ...this.pendingToolCall.arguments, ...newArgs };
|
||||||
|
console.log("[AgentManager] Pending tool args updated:", this.pendingToolCall.arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.status = 'idle';
|
this.status = 'idle';
|
||||||
}
|
}
|
||||||
|
|
||||||
async resumeWithApproval(approved, feedback, onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate) {
|
async resumeWithApproval(approved, feedback, onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate, onPromptGenerated) {
|
||||||
if (this.status !== 'paused' || !this.pendingToolCall) return;
|
if (this.status !== 'paused' || !this.pendingToolCall) return;
|
||||||
|
|
||||||
if (approved) {
|
if (approved) {
|
||||||
this.status = 'running';
|
this.status = 'running';
|
||||||
await this.executePendingTool(onStreamUpdate, onPreviewUpdate, onContextUpdate);
|
await this.executePendingTool(onStreamUpdate, onPreviewUpdate, onContextUpdate);
|
||||||
this.pendingToolCall = null;
|
this.pendingToolCall = null;
|
||||||
await this.runTaskLoop(onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate);
|
await this.runTaskLoop(onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate, onPromptGenerated);
|
||||||
} else {
|
} else {
|
||||||
this.status = 'running';
|
this.status = 'running';
|
||||||
this.pendingToolCall = null;
|
this.pendingToolCall = null;
|
||||||
@@ -55,7 +62,7 @@ export class AgentManager {
|
|||||||
role: 'user',
|
role: 'user',
|
||||||
content: `[工具执行被拒绝] 用户反馈: ${feedback || "未提供原因。"}`
|
content: `[工具执行被拒绝] 用户反馈: ${feedback || "未提供原因。"}`
|
||||||
});
|
});
|
||||||
await this.runTaskLoop(onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate);
|
await this.runTaskLoop(onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate, onPromptGenerated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,12 +127,18 @@ ${this.taskState.getPromptContext()}
|
|||||||
if (this.currentChid !== undefined && this.currentChid !== 'new') {
|
if (this.currentChid !== undefined && this.currentChid !== 'new') {
|
||||||
try {
|
try {
|
||||||
const charData = await tools.read_character_card({ chid: this.currentChid });
|
const charData = await tools.read_character_card({ chid: this.currentChid });
|
||||||
const char = JSON.parse(charData);
|
const response = JSON.parse(charData);
|
||||||
envDetails += `# Current Character\n`;
|
|
||||||
envDetails += `Name: ${char.name}\n`;
|
if (response.status === 'success' && response.data) {
|
||||||
envDetails += `Description Length: ${char.description?.length || 0}\n`;
|
const char = response.data;
|
||||||
envDetails += `First Message Length: ${char.first_mes?.length || 0}\n`;
|
envDetails += `# Current Character\n`;
|
||||||
envDetails += `Description Snippet: ${char.description?.substring(0, 200).replace(/\n/g, ' ')}...\n\n`;
|
envDetails += `Name: ${char.name}\n`;
|
||||||
|
envDetails += `Description Length: ${char.description?.length || 0}\n`;
|
||||||
|
envDetails += `First Message Length: ${char.first_mes?.length || 0}\n`;
|
||||||
|
envDetails += `Description Snippet: ${char.description?.substring(0, 200).replace(/\n/g, ' ')}...\n\n`;
|
||||||
|
} else {
|
||||||
|
envDetails += `# Current Character\nError reading character: ${response.message || 'Unknown error'}\n\n`;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
envDetails += `# Current Character\nError reading character: ${e.message}\n\n`;
|
envDetails += `# Current Character\nError reading character: ${e.message}\n\n`;
|
||||||
}
|
}
|
||||||
@@ -192,6 +205,11 @@ Example:
|
|||||||
- **Detailed Writing**: When writing content (Description, First Message, World Info), be creative and detailed.
|
- **Detailed Writing**: When writing content (Description, First Message, World Info), be creative and detailed.
|
||||||
- World Info entries: > 300 words.
|
- World Info entries: > 300 words.
|
||||||
- First Message: > 1500 words, including environment, psychology, and action.
|
- First Message: > 1500 words, including environment, psychology, and action.
|
||||||
|
- **Tool Selection**:
|
||||||
|
- **Use \`edit_character_text\`** for small modifications to existing large text fields (Description, First Message, etc.). This is more precise and saves tokens.
|
||||||
|
- **Use \`edit_world_info_entry\`** for small modifications to existing World Info entries.
|
||||||
|
- **Use \`update_character_card\`** only when populating empty fields or rewriting the entire content of a field.
|
||||||
|
- **Use \`write_world_info_entry\`** only when creating new entries or rewriting the entire content of an entry.
|
||||||
- **Do not ask for more information than necessary**: Use the tools provided to accomplish the user's request efficiently and effectively.
|
- **Do not ask for more information than necessary**: Use the tools provided to accomplish the user's request efficiently and effectively.
|
||||||
- **Completion**: When the task is done, provide a final summary to the user.
|
- **Completion**: When the task is done, provide a final summary to the user.
|
||||||
`;
|
`;
|
||||||
@@ -207,17 +225,17 @@ Example:
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleUserMessage(message, onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate) {
|
async handleUserMessage(message, onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate, onPromptGenerated) {
|
||||||
if (this.history.length === 0) {
|
if (this.history.length === 0) {
|
||||||
this.taskState.init(message);
|
this.taskState.init(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.history.push({ role: 'user', content: message });
|
this.history.push({ role: 'user', content: message });
|
||||||
this.status = 'running';
|
this.status = 'running';
|
||||||
await this.runTaskLoop(onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate);
|
await this.runTaskLoop(onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate, onPromptGenerated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async runTaskLoop(onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate) {
|
async runTaskLoop(onStreamUpdate, onPreviewUpdate, onApprovalRequest, onContextUpdate, onPromptGenerated) {
|
||||||
let maxTurns = 20;
|
let maxTurns = 20;
|
||||||
let currentTurn = 0;
|
let currentTurn = 0;
|
||||||
|
|
||||||
@@ -250,6 +268,10 @@ Example:
|
|||||||
config.maxTokens
|
config.maxTokens
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (onPromptGenerated) {
|
||||||
|
onPromptGenerated(messages);
|
||||||
|
}
|
||||||
|
|
||||||
let responseContent;
|
let responseContent;
|
||||||
let fullStreamedContent = "";
|
let fullStreamedContent = "";
|
||||||
try {
|
try {
|
||||||
@@ -326,7 +348,7 @@ Example:
|
|||||||
toolCall.arguments.chid = parseInt(this.currentChid);
|
toolCall.arguments.chid = parseInt(this.currentChid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toolCall.name === 'write_world_info_entry' || toolCall.name === 'read_world_info') {
|
if (toolCall.name === 'write_world_info_entry' || toolCall.name === 'read_world_info' || toolCall.name === 'edit_world_info_entry') {
|
||||||
if (!toolCall.arguments.book_name && this.currentBookName) {
|
if (!toolCall.arguments.book_name && this.currentBookName) {
|
||||||
toolCall.arguments.book_name = this.currentBookName;
|
toolCall.arguments.book_name = this.currentBookName;
|
||||||
}
|
}
|
||||||
@@ -419,7 +441,7 @@ Example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (onPreviewUpdate && !isError) {
|
if (onPreviewUpdate && !isError) {
|
||||||
onPreviewUpdate(toolCall.name, toolCall.arguments);
|
onPreviewUpdate(toolCall.name, toolCall.arguments, false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export async function callAi(role, messages, options = {}, onChunk = null) {
|
|||||||
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
buffer += decoder.decode(value, { stream: true });
|
||||||
const lines = buffer.split('\n');
|
const lines = buffer.split('\n');
|
||||||
buffer = lines.pop(); // Keep incomplete line in buffer
|
buffer = lines.pop();
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmedLine = line.trim();
|
const trimmedLine = line.trim();
|
||||||
@@ -89,7 +89,7 @@ export async function callAi(role, messages, options = {}, onChunk = null) {
|
|||||||
onChunk(delta);
|
onChunk(delta);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore parse errors for partial chunks
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,6 +107,14 @@ export async function callAi(role, messages, options = {}, onChunk = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const content = responseData.choices[0].message?.content;
|
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}`);
|
console.log(`[自动构建器] AI (${roleName}) 响应接收成功。长度: ${content?.length}`);
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@@ -117,12 +125,17 @@ export async function callAi(role, messages, options = {}, onChunk = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function testConnection(role) {
|
export async function testConnection(role, config = {}) {
|
||||||
try {
|
try {
|
||||||
const response = await callAi(role, [
|
const response = await callAi(role, [
|
||||||
{ role: 'user', content: 'Hi' }
|
{ role: 'user', content: 'Say hello' }
|
||||||
], { maxTokens: 10 });
|
], { maxTokens: 50, ...config });
|
||||||
return { success: !!response };
|
|
||||||
|
if (!response) {
|
||||||
|
return { success: false, error: "API 返回了空内容 (可能是被安全过滤或模型无响应)" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[自动构建器] ${role} 连接测试失败:`, error);
|
console.error(`[自动构建器] ${role} 连接测试失败:`, error);
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
|
|||||||
@@ -1,24 +1,91 @@
|
|||||||
import { characters, saveCharacterDebounced, this_chid, getCharacters, getRequestHeaders } from "/script.js";
|
import { characters, saveCharacterDebounced, this_chid, getCharacters, getRequestHeaders } from "/script.js";
|
||||||
|
import { getContext } from "/scripts/extensions.js";
|
||||||
import { extensionName } from "../../utils/settings.js";
|
import { extensionName } from "../../utils/settings.js";
|
||||||
|
|
||||||
async function saveCharacterById(chid) {
|
async function saveCharacterById(chid) {
|
||||||
const char = characters[chid];
|
let currentChid = undefined;
|
||||||
if (!char) return;
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
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', {
|
const response = await fetch('/api/characters/edit', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getRequestHeaders(),
|
headers: getRequestHeaders({ omitContentType: true }),
|
||||||
body: JSON.stringify(char)
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error(`[Amily2 CharAPI] Failed to save character ${chid}:`, response.statusText);
|
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 {
|
} else {
|
||||||
console.log(`[Amily2 CharAPI] Successfully saved character ${chid}`);
|
console.log(`[Amily2 CharAPI] Successfully saved character ${chid} (Background)`);
|
||||||
|
return { success: true };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[Amily2 CharAPI] Error saving character ${chid}:`, e);
|
console.error(`[Amily2 CharAPI] Error saving character ${chid}:`, e);
|
||||||
|
return { success: false, message: `Save error: ${e.message}` };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +97,7 @@ export function getCharacter(chid = this_chid) {
|
|||||||
return characters[chid];
|
return characters[chid];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateCharacter(chid, updates) {
|
export async function updateCharacter(chid, updates) {
|
||||||
const char = getCharacter(chid);
|
const char = getCharacter(chid);
|
||||||
if (!char) return false;
|
if (!char) return false;
|
||||||
|
|
||||||
@@ -45,9 +112,12 @@ export function updateCharacter(chid, updates) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
saveCharacterById(chid);
|
const success = await saveCharacterById(chid);
|
||||||
console.log(`[Amily2 CharAPI] Updated character ${chid}:`, Object.keys(updates));
|
if (success) {
|
||||||
return true;
|
console.log(`[Amily2 CharAPI] Updated character ${chid}:`, Object.keys(updates));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -63,7 +133,7 @@ export function getFirstMessages(chid) {
|
|||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addFirstMessage(chid, message) {
|
export async function addFirstMessage(chid, message) {
|
||||||
const char = getCharacter(chid);
|
const char = getCharacter(chid);
|
||||||
if (!char) return false;
|
if (!char) return false;
|
||||||
|
|
||||||
@@ -73,12 +143,15 @@ export function addFirstMessage(chid, message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char.data.alternate_greetings.push(message);
|
char.data.alternate_greetings.push(message);
|
||||||
saveCharacterById(chid);
|
const success = await saveCharacterById(chid);
|
||||||
console.log(`[Amily2 CharAPI] Added alternate greeting to character ${chid}`);
|
if (success) {
|
||||||
return true;
|
console.log(`[Amily2 CharAPI] Added alternate greeting to character ${chid}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateFirstMessage(chid, index, message) {
|
export async function updateFirstMessage(chid, index, message) {
|
||||||
const char = getCharacter(chid);
|
const char = getCharacter(chid);
|
||||||
if (!char) return false;
|
if (!char) return false;
|
||||||
|
|
||||||
@@ -93,12 +166,15 @@ export function updateFirstMessage(chid, index, message) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveCharacterById(chid);
|
const success = await saveCharacterById(chid);
|
||||||
console.log(`[Amily2 CharAPI] Updated greeting ${index} for character ${chid}`);
|
if (success) {
|
||||||
return true;
|
console.log(`[Amily2 CharAPI] Updated greeting ${index} for character ${chid}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeFirstMessage(chid, index) {
|
export async function removeFirstMessage(chid, index) {
|
||||||
const char = getCharacter(chid);
|
const char = getCharacter(chid);
|
||||||
if (!char) return false;
|
if (!char) return false;
|
||||||
|
|
||||||
@@ -114,9 +190,12 @@ export function removeFirstMessage(chid, index) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveCharacterById(chid);
|
const success = await saveCharacterById(chid);
|
||||||
console.log(`[Amily2 CharAPI] Removed greeting ${index} for character ${chid}`);
|
if (success) {
|
||||||
return true;
|
console.log(`[Amily2 CharAPI] Removed greeting ${index} for character ${chid}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createNewCharacter(name) {
|
export async function createNewCharacter(name) {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ ${taskState.getPromptContext()}
|
|||||||
shouldSummarize(history, tokenCount, maxTokens) {
|
shouldSummarize(history, tokenCount, maxTokens) {
|
||||||
const tokenUsageRatio = tokenCount / maxTokens;
|
const tokenUsageRatio = tokenCount / maxTokens;
|
||||||
if (tokenUsageRatio > 0.7) return true;
|
if (tokenUsageRatio > 0.7) return true;
|
||||||
if (history.length > 15) return true;
|
if (history.length > 35) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export const tools = {
|
|||||||
const { chid, ...updates } = args;
|
const { chid, ...updates } = args;
|
||||||
const finalUpdates = args.updates || updates;
|
const finalUpdates = args.updates || updates;
|
||||||
|
|
||||||
const success = charApi.updateCharacter(chid, finalUpdates);
|
const success = await charApi.updateCharacter(chid, finalUpdates);
|
||||||
if (success) {
|
if (success) {
|
||||||
const updatedFields = Object.keys(finalUpdates).join(', ');
|
const updatedFields = Object.keys(finalUpdates).join(', ');
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
@@ -171,7 +171,7 @@ export const tools = {
|
|||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
status: "error",
|
status: "error",
|
||||||
code: "UPDATE_FAILED",
|
code: "UPDATE_FAILED",
|
||||||
message: "更新角色卡失败。"
|
message: "更新角色卡失败。请确保您正在编辑当前选中的角色(暂不支持后台编辑其他角色)。"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -196,30 +196,52 @@ export const tools = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let content = char[field] || '';
|
let content = char[field] || '';
|
||||||
const changes = diff.split('------- SEARCH');
|
|
||||||
|
|
||||||
|
const normalizedDiff = diff
|
||||||
|
.replace(/-------\s*SEARCH/g, '------- SEARCH')
|
||||||
|
.replace(/=======\s*/g, '=======')
|
||||||
|
.replace(/\+\+\+\+\+\+\+\s*REPLACE/g, '+++++++ REPLACE');
|
||||||
|
|
||||||
|
const changes = normalizedDiff.split('------- SEARCH');
|
||||||
|
|
||||||
if (changes[0].trim() === '') changes.shift();
|
if (changes[0].trim() === '') changes.shift();
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
const parts = change.split('=======');
|
const parts = change.split('=======');
|
||||||
if (parts.length !== 2) continue;
|
if (parts.length < 2) continue;
|
||||||
|
|
||||||
const searchBlock = parts[0].trim();
|
const searchBlock = parts[0].trim();
|
||||||
const replaceBlock = parts[1].split('+++++++ REPLACE')[0].trim();
|
const replaceBlock = parts[1].split('+++++++ REPLACE')[0].trim();
|
||||||
|
|
||||||
if (!content.includes(searchBlock)) {
|
|
||||||
return JSON.stringify({
|
if (content.includes(searchBlock)) {
|
||||||
status: "error",
|
content = content.replace(searchBlock, replaceBlock);
|
||||||
code: "SEARCH_NOT_FOUND",
|
continue;
|
||||||
message: `在字段 '${field}' 中未找到搜索块。`,
|
|
||||||
suggestion: "请确保 SEARCH 块与现有内容完全匹配(包括空格)。"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content = content.replace(searchBlock, replaceBlock);
|
|
||||||
|
const normalizedSearch = searchBlock.replace(/\r\n/g, '\n');
|
||||||
|
const lines = normalizedSearch.split('\n');
|
||||||
|
const regexPattern = lines.map(line => line.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\r?\\n');
|
||||||
|
|
||||||
|
const regex = new RegExp(regexPattern);
|
||||||
|
const match = content.match(regex);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
content = content.replace(match[0], replaceBlock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
status: "error",
|
||||||
|
code: "SEARCH_NOT_FOUND",
|
||||||
|
message: `在字段 '${field}' 中未找到搜索块。`,
|
||||||
|
suggestion: "请确保 SEARCH 块与现有内容完全匹配(包括空格)。"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = charApi.updateCharacter(chid, { [field]: content });
|
const success = await charApi.updateCharacter(chid, { [field]: content });
|
||||||
if (success) {
|
if (success) {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
status: "success",
|
status: "success",
|
||||||
@@ -229,7 +251,81 @@ export const tools = {
|
|||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
status: "error",
|
status: "error",
|
||||||
code: "UPDATE_FAILED",
|
code: "UPDATE_FAILED",
|
||||||
message: `更新字段 '${field}' 失败。`
|
message: `更新字段 '${field}' 失败。请确保您正在编辑当前选中的角色(暂不支持后台编辑其他角色)。`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
edit_world_info_entry: async ({ book_name, uid, diff }) => {
|
||||||
|
const entries = await amilyHelper.getLorebookEntries(book_name);
|
||||||
|
const entry = entries.find(e => String(e.uid) === String(uid));
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return JSON.stringify({
|
||||||
|
status: "error",
|
||||||
|
code: "ENTRY_NOT_FOUND",
|
||||||
|
message: `在世界书 "${book_name}" 中未找到 UID 为 ${uid} 的条目。`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = entry.content || '';
|
||||||
|
|
||||||
|
|
||||||
|
const normalizedDiff = diff
|
||||||
|
.replace(/-------\s*SEARCH/g, '------- SEARCH')
|
||||||
|
.replace(/=======\s*/g, '=======')
|
||||||
|
.replace(/\+\+\+\+\+\+\+\s*REPLACE/g, '+++++++ REPLACE');
|
||||||
|
|
||||||
|
const changes = normalizedDiff.split('------- SEARCH');
|
||||||
|
|
||||||
|
if (changes[0].trim() === '') changes.shift();
|
||||||
|
|
||||||
|
for (const change of changes) {
|
||||||
|
const parts = change.split('=======');
|
||||||
|
if (parts.length < 2) continue;
|
||||||
|
|
||||||
|
const searchBlock = parts[0].trim();
|
||||||
|
const replaceBlock = parts[1].split('+++++++ REPLACE')[0].trim();
|
||||||
|
|
||||||
|
|
||||||
|
if (content.includes(searchBlock)) {
|
||||||
|
content = content.replace(searchBlock, replaceBlock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const normalizedSearch = searchBlock.replace(/\r\n/g, '\n');
|
||||||
|
const lines = normalizedSearch.split('\n');
|
||||||
|
const regexPattern = lines.map(line => line.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\r?\\n');
|
||||||
|
|
||||||
|
const regex = new RegExp(regexPattern);
|
||||||
|
const match = content.match(regex);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
content = content.replace(match[0], replaceBlock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
status: "error",
|
||||||
|
code: "SEARCH_NOT_FOUND",
|
||||||
|
message: `在条目内容中未找到搜索块。`,
|
||||||
|
suggestion: "请确保 SEARCH 块与现有内容完全匹配(包括空格)。"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = await amilyHelper.setLorebookEntries(book_name, [{ uid: entry.uid, content: content }]);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
return JSON.stringify({
|
||||||
|
status: "success",
|
||||||
|
message: `条目 [${uid}] 更新成功。`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return JSON.stringify({
|
||||||
|
status: "error",
|
||||||
|
code: "UPDATE_FAILED",
|
||||||
|
message: `更新条目 [${uid}] 失败。`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -238,13 +334,13 @@ export const tools = {
|
|||||||
let success = false;
|
let success = false;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
success = charApi.addFirstMessage(chid, message);
|
success = await charApi.addFirstMessage(chid, message);
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
success = charApi.updateFirstMessage(chid, index, message);
|
success = await charApi.updateFirstMessage(chid, index, message);
|
||||||
break;
|
break;
|
||||||
case 'remove':
|
case 'remove':
|
||||||
success = charApi.removeFirstMessage(chid, index);
|
success = await charApi.removeFirstMessage(chid, index);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
@@ -494,6 +590,22 @@ export function getToolDefinitions() {
|
|||||||
required: ["chid", "field", "diff"]
|
required: ["chid", "field", "diff"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "edit_world_info_entry",
|
||||||
|
description: "使用 搜索/替换 块编辑世界书条目的内容。",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
book_name: { type: "string", description: "世界书名称。" },
|
||||||
|
uid: { type: "number", description: "条目 UID。" },
|
||||||
|
diff: {
|
||||||
|
type: "string",
|
||||||
|
description: "一个或多个遵循此确切格式的 搜索/替换 块:\n------- SEARCH\n[exact content to find]\n=======\n[new content to replace with]\n+++++++ REPLACE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["book_name", "uid", "diff"]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "manage_first_message",
|
name: "manage_first_message",
|
||||||
description: "添加、更新或删除候补开场白。",
|
description: "添加、更新或删除候补开场白。",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user