mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 04:35:51 +00:00
240 lines
9.1 KiB
JavaScript
240 lines
9.1 KiB
JavaScript
/**
|
||
* @file Markdown/CSV 渲染 —— 把 TableState 渲染为 prompt 可用的字符串。
|
||
*
|
||
* 纯函数:吃 state、吐字符串。不读 store、不写盘、不发事件。
|
||
*
|
||
* 历史来源:从 manager.js 抽出
|
||
* - convertTablesToCsvString → tablesToCsv
|
||
* - convertSelectedTablesToCsvString → tablesToCsvWithSelection
|
||
* - convertTablesToCsvStringForContentOnly → tablesToCsvContentOnly
|
||
* - checkTableRules (内部) → _checkTableRules (内部)
|
||
*
|
||
* manager.js 保留同名 export 作 wrapper(自动注入 getState()),所有外部调用点零改动。
|
||
*
|
||
* @typedef {import('./dto/Table.js').Table} Table
|
||
* @typedef {import('./dto/Table.js').TableState} TableState
|
||
*/
|
||
|
||
/**
|
||
* 检查表格规则违规,返回聚合警告字符串(多行)。
|
||
* 行数超限 + 多列字符限制超限。
|
||
* @param {Table} table
|
||
* @returns {string}
|
||
*/
|
||
function _checkTableRules(table) {
|
||
const warnings = [];
|
||
|
||
// 行数限制
|
||
if (table.rowLimitRule && table.rowLimitRule > 0 && table.rows.length > table.rowLimitRule) {
|
||
warnings.push(`【当前(${table.name})超出规定(${table.rowLimitRule})行,请结合剧情缩减至(${table.rowLimitRule})行以下,但切莫完全删除。】`);
|
||
}
|
||
|
||
// 多列字符限制
|
||
const charLimitRules = table.charLimitRules || {};
|
||
for (const colIndexStr in charLimitRules) {
|
||
const colIndex = parseInt(colIndexStr, 10);
|
||
const limit = charLimitRules[colIndex];
|
||
if (limit > 0 && colIndex >= 0 && colIndex < table.headers.length) {
|
||
const colName = table.headers[colIndex];
|
||
const offendingRows = [];
|
||
table.rows.forEach((row, rowIndex) => {
|
||
if (table.rowStatuses && table.rowStatuses[rowIndex] === 'pending-deletion') return;
|
||
const cellContent = row[colIndex] || '';
|
||
if (cellContent.length > limit) offendingRows.push(rowIndex);
|
||
});
|
||
if (offendingRows.length > 0) {
|
||
warnings.push(`【当前(${table.name})第(${offendingRows.join('、')})行(${colName})列,字符超出规定(${limit})字限制,请进行缩减。】`);
|
||
}
|
||
}
|
||
}
|
||
|
||
return warnings.join('\n');
|
||
}
|
||
|
||
/**
|
||
* 把单个 table 的"内容主体"(含 simplify 处理 + warnings)写入到 fullString 末尾。
|
||
* 提取自三个渲染函数中重复的内层逻辑。
|
||
*
|
||
* @param {Table} table
|
||
* @param {string} tagName
|
||
* @returns {string}
|
||
*/
|
||
function _renderTableBody(table, tagName) {
|
||
let out = '';
|
||
const activeRows = table.rows.filter((row, i) => !table.rowStatuses || table.rowStatuses[i] !== 'pending-deletion');
|
||
|
||
if (activeRows.length === 0) {
|
||
out += '(该表当前内容为空)\n';
|
||
} else {
|
||
const simplifyThreshold = table.simplifyRowThreshold || 0;
|
||
let simplifiedCount = 0;
|
||
|
||
table.rows.forEach((row, rowIndex) => {
|
||
if (table.rowStatuses && table.rowStatuses[rowIndex] === 'pending-deletion') return;
|
||
|
||
// 历史内容简化:前 N 行用 ---已锁定--- 占位
|
||
if (simplifyThreshold > 0 && rowIndex < simplifyThreshold) {
|
||
if (simplifiedCount === 0) {
|
||
const placeholderCells = row.map(() => '---已锁定---');
|
||
out += `| ${rowIndex} | ${placeholderCells.join(' | ')} |\n`;
|
||
out += `| ... | ${row.map(() => '...').join(' | ')} |\n`;
|
||
}
|
||
if (rowIndex === simplifyThreshold - 1) {
|
||
const placeholderCells = row.map(() => '---已锁定---');
|
||
out += `| ${rowIndex} | ${placeholderCells.join(' | ')} |\n`;
|
||
}
|
||
simplifiedCount++;
|
||
return;
|
||
}
|
||
|
||
if (Array.isArray(row)) {
|
||
const rowCells = row.map(cell => {
|
||
const cellContent = (cell === null || cell === undefined || cell === '') ? '未知' : String(cell);
|
||
return cellContent.replace(/\|/g, '|');
|
||
});
|
||
out += `| ${rowIndex} | ${rowCells.join(' | ')} |\n`;
|
||
}
|
||
});
|
||
|
||
if (simplifiedCount > 0) {
|
||
out += `\n【系统提示】:表格前 ${simplifiedCount} 行(索引 0 到 ${simplifiedCount - 1})的历史内容已简化并锁定,无需读取或修改。请专注于后续行的内容。\n`;
|
||
}
|
||
}
|
||
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* 完整渲染:所有表格内容 + 规则 + 警告,注入到主流程 prompt。
|
||
* 对应 manager.js#convertTablesToCsvString。
|
||
*
|
||
* @param {TableState | null} state
|
||
* @returns {string}
|
||
*/
|
||
export function tablesToCsv(state) {
|
||
if (!state || state.length === 0) return '';
|
||
|
||
let fullString = '';
|
||
state.forEach((table, tableIndex) => {
|
||
// 标题
|
||
fullString += `\n* ${tableIndex}:${table.name}\n`;
|
||
|
||
// 说明
|
||
fullString += `【说明】:\n${table.note || '无'}\n`;
|
||
|
||
// 内容(Markdown 表)
|
||
const tagName = table.name.replace(/\s/g, '') + '内容';
|
||
fullString += `<${tagName}>\n`;
|
||
const headerWithIndex = ['rowIndex', ...table.headers.map((h, i) => `${i}:${h}`)];
|
||
fullString += `| ${headerWithIndex.join(' | ')} |\n`;
|
||
fullString += `|${headerWithIndex.map(() => '---').join('|')}|\n`;
|
||
fullString += _renderTableBody(table, tagName);
|
||
|
||
// 警告
|
||
const warnings = _checkTableRules(table);
|
||
if (warnings) fullString += `${warnings}\n`;
|
||
fullString += `</${tagName}>\n`;
|
||
|
||
// 规则
|
||
fullString += `【增加】: ${table.rule_add || '允许'}\n`;
|
||
fullString += `【删除】: ${table.rule_delete || '允许'}\n`;
|
||
fullString += `【修改】: ${table.rule_update || '允许'}\n`;
|
||
|
||
if (tableIndex < state.length - 1) fullString += '\n---\n';
|
||
});
|
||
|
||
return fullString;
|
||
}
|
||
|
||
/**
|
||
* 选中态渲染:未选中的表格只展示表头作为索引参考;选中的展示完整内容。
|
||
* 对应 manager.js#convertSelectedTablesToCsvString。
|
||
*
|
||
* @param {TableState | null} state
|
||
* @param {number[]} selectedIndices
|
||
* @returns {string}
|
||
*/
|
||
export function tablesToCsvWithSelection(state, selectedIndices) {
|
||
if (!state || state.length === 0) return '';
|
||
const selected = Array.isArray(selectedIndices) ? selectedIndices : [];
|
||
|
||
let fullString = '';
|
||
state.forEach((table, tableIndex) => {
|
||
const isSelected = selected.includes(tableIndex);
|
||
|
||
// 标题
|
||
fullString += `\n* ${tableIndex}:${table.name}`;
|
||
if (!isSelected) fullString += ' (本表格无需重新整理,仅供参考)';
|
||
fullString += '\n';
|
||
|
||
// 说明
|
||
fullString += `【说明】:\n${table.note || '无'}\n`;
|
||
|
||
const tagName = table.name.replace(/\s/g, '') + '内容';
|
||
fullString += `<${tagName}>\n`;
|
||
const headerWithIndex = ['rowIndex', ...table.headers.map((h, i) => `${i}:${h}`)];
|
||
fullString += `| ${headerWithIndex.join(' | ')} |\n`;
|
||
fullString += `|${headerWithIndex.map(() => '---').join('|')}|\n`;
|
||
|
||
if (isSelected) {
|
||
fullString += _renderTableBody(table, tagName);
|
||
const warnings = _checkTableRules(table);
|
||
if (warnings) fullString += `${warnings}\n`;
|
||
} else {
|
||
fullString += '(此处省略未选中的表格内容,仅提供表头供索引参考)\n';
|
||
}
|
||
fullString += `</${tagName}>\n`;
|
||
|
||
// 规则
|
||
if (isSelected) {
|
||
fullString += `【增加】: ${table.rule_add || '允许'}\n`;
|
||
fullString += `【删除】: ${table.rule_delete || '允许'}\n`;
|
||
fullString += `【修改】: ${table.rule_update || '允许'}\n`;
|
||
} else {
|
||
fullString += `【操作权限】: 禁止修改此表格\n`;
|
||
}
|
||
|
||
if (tableIndex < state.length - 1) fullString += '\n---\n';
|
||
});
|
||
|
||
return fullString;
|
||
}
|
||
|
||
/**
|
||
* 仅内容渲染:不带规则、不带 rowIndex 列、不带说明。
|
||
* 用于"分步填表"和"优化中填表"模式下的 prompt 注入(只展示数据本身)。
|
||
* 对应 manager.js#convertTablesToCsvStringForContentOnly。
|
||
*
|
||
* @param {TableState | null} state
|
||
* @returns {string}
|
||
*/
|
||
export function tablesToCsvContentOnly(state) {
|
||
if (!state || state.length === 0) return '';
|
||
|
||
let outputString = '';
|
||
state.forEach(table => {
|
||
outputString += `\n<${table.name}>\n`;
|
||
|
||
// Markdown 表头
|
||
outputString += `| ${table.headers.join(' | ')} |\n`;
|
||
outputString += `|${table.headers.map(() => '---').join('|')}|\n`;
|
||
|
||
// 数据
|
||
const activeRows = table.rows.filter((row, i) => !table.rowStatuses || table.rowStatuses[i] !== 'pending-deletion');
|
||
if (activeRows.length > 0) {
|
||
activeRows.forEach(row => {
|
||
if (Array.isArray(row)) {
|
||
const rowContent = row.map(cell => (cell === null || cell === undefined || cell === '') ? ' ' : cell.toString());
|
||
outputString += `| ${rowContent.join(' | ')} |\n`;
|
||
}
|
||
});
|
||
} else {
|
||
outputString += '(该表当前内容为空)\n';
|
||
}
|
||
|
||
outputString += `</${table.name}>\n`;
|
||
});
|
||
|
||
return outputString.trim();
|
||
}
|