mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 05:45:51 +00:00
### 新功能 - **Function Call 填表模式**:在填表设置中新增独立开关,启用后支持通过 OpenAI 兼容接口(DeepSeek / OpenRouter / 各类中转等)直接返回结构化操作列表,绕过 `<Amily2Edit>` 文本解析路径,填表更稳定 - 遇到不支持 `tool_choice` 的接口时自动降级重试 - 对思考模型注入强制调用指令,防止绕过工具直接输出文本 - 全部走 ST 后端代理,修复 CSP 拦截直连外部 URL 的问题 - **主界面新增提示词链编辑器入口**,同时调换了记忆管理与角色世界书的按钮位置 - **规则中心**新增"自动排除用户楼层"选项 ### 修复 - 提示词链按钮点击无响应(改为事件委托方式绑定) - 拖拽组件微抖误触发(加 5px 移动阈值过滤) - 填表检查窗若干问题修复;翰林院(批量回填)修复;防抖逻辑落地 - 角色世界书入口添加使用警告弹窗(强制 10 秒倒计时),提示该功能长期未维护 - ApiProfile `fakeStream` 字段保存丢失问题 - 正文优化默认改为关闭状态 - NGMS / NCCS API 配置槽位标签修正(NGMS→总结,NCCS→填表) - API Profile 面板选择逻辑统一重构,修复多处旧字段覆盖新配置的问题 - 世界书控制参数兼容性修复(排除递归、插入位置、扫描深度等,适配 ST 1.17.0+)
92 lines
3.7 KiB
JavaScript
92 lines
3.7 KiB
JavaScript
/**
|
||
* @file formatters/tool-call.js — Function Call 填表格式器
|
||
*
|
||
* 职责:
|
||
* - 导出 TABLE_FILL_TOOL:发给模型的 tools 定义(单工具 + operations 数组)
|
||
* - 导出 parseToolCallArgs:把 tool_calls[0].function.arguments 解析为 Operation[]
|
||
*
|
||
* 与 executor.js(legacy formatter)并列;下游 applyOperations 不感知来源。
|
||
*
|
||
* @typedef {import('../dto/Operation.js').Operation} Operation
|
||
*/
|
||
|
||
/**
|
||
* 填表工具 schema。使用 operations 数组而非多工具并发,兼容所有支持 function calling 的提供商。
|
||
*
|
||
* data 的 key 为列索引字符串("0"、"1"...),与 executor.js legacy 格式保持一致,
|
||
* 提示词中会给出列索引与列名的对应关系。
|
||
*/
|
||
export const TABLE_FILL_TOOL = {
|
||
type: 'function',
|
||
function: {
|
||
name: 'apply_table_edits',
|
||
description: '将一批表格编辑操作应用到记忆表格中。',
|
||
parameters: {
|
||
type: 'object',
|
||
properties: {
|
||
operations: {
|
||
type: 'array',
|
||
description: '按顺序执行的操作列表。',
|
||
items: {
|
||
type: 'object',
|
||
properties: {
|
||
op: {
|
||
type: 'string',
|
||
enum: ['insertRow', 'updateRow', 'deleteRow'],
|
||
description: 'insertRow=新增行,updateRow=更新已有行,deleteRow=删除行'
|
||
},
|
||
tableIndex: {
|
||
type: 'integer',
|
||
description: '目标表格的 0-based 索引'
|
||
},
|
||
rowIndex: {
|
||
type: 'integer',
|
||
description: 'updateRow / deleteRow 时必填,目标行的 0-based 索引'
|
||
},
|
||
data: {
|
||
type: 'object',
|
||
description: 'insertRow / updateRow 时必填,key 为列索引字符串("0"/"1"...),value 为单元格内容',
|
||
additionalProperties: { type: 'string' }
|
||
}
|
||
},
|
||
required: ['op', 'tableIndex']
|
||
}
|
||
}
|
||
},
|
||
required: ['operations']
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 解析 tool_calls[0].function.arguments 字符串为 Operation[]。
|
||
* 结构校验失败的单条操作会被静默跳过,不中断整体解析。
|
||
*
|
||
* @param {string} argsString - JSON 字符串
|
||
* @returns {Operation[]}
|
||
*/
|
||
export function parseToolCallArgs(argsString) {
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(argsString);
|
||
} catch {
|
||
return [];
|
||
}
|
||
|
||
const rawOps = parsed?.operations;
|
||
if (!Array.isArray(rawOps)) return [];
|
||
|
||
/** @type {Operation[]} */
|
||
const ops = [];
|
||
for (const raw of rawOps) {
|
||
if (raw.op === 'insertRow' && Number.isInteger(raw.tableIndex) && raw.data && typeof raw.data === 'object') {
|
||
ops.push({ op: 'insertRow', tableIndex: raw.tableIndex, data: raw.data });
|
||
} else if (raw.op === 'updateRow' && Number.isInteger(raw.tableIndex) && Number.isInteger(raw.rowIndex) && raw.data && typeof raw.data === 'object') {
|
||
ops.push({ op: 'updateRow', tableIndex: raw.tableIndex, rowIndex: raw.rowIndex, data: raw.data });
|
||
} else if (raw.op === 'deleteRow' && Number.isInteger(raw.tableIndex) && Number.isInteger(raw.rowIndex)) {
|
||
ops.push({ op: 'deleteRow', tableIndex: raw.tableIndex, rowIndex: raw.rowIndex });
|
||
}
|
||
}
|
||
return ops;
|
||
}
|