feat: 为快捷指令添加变量功能

#57
This commit is contained in:
Baobhan Sith
2025-05-30 09:25:46 +08:00
parent e2d6dcb937
commit 807a48a7dd
14 changed files with 595 additions and 109 deletions
+11 -1
View File
@@ -123,7 +123,6 @@ const definedMigrations: Migration[] = [
name: 'Add notes column to connections table',
check: async (db: Database): Promise<boolean> => {
const notesColumnExists = await columnExists(db, 'connections', 'notes');
// Only run if the column does NOT exist
return !notesColumnExists;
},
sql: `
@@ -297,6 +296,17 @@ const definedMigrations: Migration[] = [
return !jumpChainColumnExists || !proxyTypeColumnExists;
}
},
{
id: 10,
name: 'Add variables column to quick_commands table',
check: async (db: Database): Promise<boolean> => {
const columnAlreadyExists = await columnExists(db, 'quick_commands', 'variables');
return !columnAlreadyExists;
},
sql: `
ALTER TABLE quick_commands ADD COLUMN variables TEXT NULL;
`
}
];
/**
+1
View File
@@ -166,6 +166,7 @@ CREATE TABLE IF NOT EXISTS quick_commands (
name TEXT NULL, -- 名称可选
command TEXT NOT NULL, -- 指令必选
usage_count INTEGER NOT NULL DEFAULT 0, -- 使用频率
variables TEXT NULL,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);
@@ -6,8 +6,8 @@ import { QuickCommandSortBy } from '../services/quick-commands.service';
* 处理添加新快捷指令的请求
*/
export const addQuickCommand = async (req: Request, res: Response): Promise<void> => {
// 从请求体中解构出 name, command, 以及可选的 tagIds
const { name, command, tagIds } = req.body;
// 从请求体中解构出 name, command, 以及可选的 tagIds 和 variables
const { name, command, tagIds, variables } = req.body;
// --- 基本验证 ---
if (!command || typeof command !== 'string' || command.trim().length === 0) {
@@ -24,11 +24,16 @@ export const addQuickCommand = async (req: Request, res: Response): Promise<void
res.status(400).json({ message: 'tagIds 必须是一个数字数组' });
return;
}
// --- 结束验证 ---
// 验证 variables (如果提供的话)
if (variables !== undefined && (typeof variables !== 'object' || variables === null || Array.isArray(variables))) {
res.status(400).json({ message: 'variables 必须是一个对象' });
return;
}
try {
// 将 tagIds 传递给 Service 层
const newId = await QuickCommandsService.addQuickCommand(name, command, tagIds);
// 将 tagIds 和 variables 传递给 Service 层
const newId = await QuickCommandsService.addQuickCommand(name, command, tagIds, variables);
// 尝试获取新创建的带标签的指令信息返回
const newCommand = await QuickCommandsService.getQuickCommandById(newId);
if (newCommand) {
@@ -65,8 +70,8 @@ export const getAllQuickCommands = async (req: Request, res: Response): Promise<
*/
export const updateQuickCommand = async (req: Request, res: Response): Promise<void> => {
const id = parseInt(req.params.id, 10);
// 从请求体中解构出 name, command, 以及可选的 tagIds
const { name, command, tagIds } = req.body;
// 从请求体中解构出 name, command, 以及可选的 tagIds 和 variables
const { name, command, tagIds, variables } = req.body;
// --- 基本验证 ---
if (isNaN(id)) {
@@ -87,11 +92,16 @@ export const updateQuickCommand = async (req: Request, res: Response): Promise<v
res.status(400).json({ message: 'tagIds 必须是一个数字数组' });
return;
}
// --- 结束验证 ---
// 验证 variables (如果提供的话)
// undefined 表示不更新 variables, null 或对象表示要更新
if (variables !== undefined && variables !== null && (typeof variables !== 'object' || Array.isArray(variables))) {
res.status(400).json({ message: 'variables 必须是对象或 null' });
return;
}
try {
// 将 tagIds 传递给 Service 层
const success = await QuickCommandsService.updateQuickCommand(id, name, command, tagIds);
// 将 tagIds 和 variables 传递给 Service 层
const success = await QuickCommandsService.updateQuickCommand(id, name, command, tagIds, variables);
if (success) {
// 尝试获取更新后的带标签的指令信息返回
const updatedCommand = await QuickCommandsService.getQuickCommandById(id);
@@ -6,18 +6,21 @@ export interface QuickCommand {
name: string | null; // 名称可选
command: string;
usage_count: number;
variables?: string; // 存储 JSON 格式的变量键值对
created_at: number; // Unix 时间戳 (秒)
updated_at: number; // Unix 时间戳 (秒)
}
// 定义包含标签 ID 的接口
export interface QuickCommandWithTags extends QuickCommand {
// 定义包含标签 ID 和解析后变量的接口
export type QuickCommandWithTags = Omit<QuickCommand, 'variables'> & {
tagIds: number[];
}
variables: Record<string, string> | null; // API 层面使用对象
};
// 用于从数据库获取带 tag_ids_str 的行
interface DbQuickCommandWithTagsRow extends QuickCommand {
tag_ids_str: string | null;
// variables 字段已包含在 QuickCommand 中,这里不需要重复定义,因为 QuickCommand 将包含 variables?: string
}
@@ -25,13 +28,15 @@ interface DbQuickCommandWithTagsRow extends QuickCommand {
* 添加一条新的快捷指令
* @param name - 指令名称 (可选)
* @param command - 指令内容
* @param variables - 变量对象 (可选)
* @returns 返回插入记录的 ID
*/
export const addQuickCommand = async (name: string | null, command: string): Promise<number> => {
const sql = `INSERT INTO quick_commands (name, command, created_at, updated_at) VALUES (?, ?, strftime('%s', 'now'), strftime('%s', 'now'))`;
export const addQuickCommand = async (name: string | null, command: string, variables?: Record<string, string>): Promise<number> => {
const sql = `INSERT INTO quick_commands (name, command, variables, created_at, updated_at) VALUES (?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now'))`;
try {
const db = await getDbInstance();
const result = await runDb(db, sql, [name, command]);
const variablesJson = variables ? JSON.stringify(variables) : null;
const result = await runDb(db, sql, [name, command, variablesJson]);
if (typeof result.lastID !== 'number' || result.lastID <= 0) {
throw new Error('添加快捷指令后未能获取有效的 lastID');
}
@@ -47,13 +52,15 @@ export const addQuickCommand = async (name: string | null, command: string): Pro
* @param id - 要更新的记录 ID
* @param name - 新的指令名称 (可选)
* @param command - 新的指令内容
* @param variables - 新的变量对象 (可选)
* @returns 返回是否成功更新 (true/false)
*/
export const updateQuickCommand = async (id: number, name: string | null, command: string): Promise<boolean> => {
const sql = `UPDATE quick_commands SET name = ?, command = ?, updated_at = strftime('%s', 'now') WHERE id = ?`;
export const updateQuickCommand = async (id: number, name: string | null, command: string, variables?: Record<string, string>): Promise<boolean> => {
const sql = `UPDATE quick_commands SET name = ?, command = ?, variables = ?, updated_at = strftime('%s', 'now') WHERE id = ?`;
try {
const db = await getDbInstance();
const result = await runDb(db, sql, [name, command, id]);
const variablesJson = variables ? JSON.stringify(variables) : null;
const result = await runDb(db, sql, [name, command, variablesJson, id]);
return result.changes > 0;
} catch (err: any) {
console.error('更新快捷指令时出错:', err.message);
@@ -91,7 +98,7 @@ export const getAllQuickCommands = async (sortBy: 'name' | 'usage_count' = 'name
// 使用 LEFT JOIN 连接关联表,并使用 GROUP_CONCAT 获取标签 ID 字符串
const sql = `
SELECT
qc.id, qc.name, qc.command, qc.usage_count, qc.created_at, qc.updated_at,
qc.id, qc.name, qc.command, qc.usage_count, qc.variables, qc.created_at, qc.updated_at,
GROUP_CONCAT(qta.tag_id) as tag_ids_str
FROM quick_commands qc
LEFT JOIN quick_command_tag_associations qta ON qc.id = qta.quick_command_id
@@ -100,11 +107,24 @@ export const getAllQuickCommands = async (sortBy: 'name' | 'usage_count' = 'name
try {
const db = await getDbInstance();
const rows = await allDb<DbQuickCommandWithTagsRow>(db, sql);
// 将 tag_ids_str 解析为数字数组
return rows.map(row => ({
...row,
tagIds: row.tag_ids_str ? row.tag_ids_str.split(',').map(Number).filter(id => !isNaN(id)) : []
}));
// 将 tag_ids_str 解析为数字数组,并解析 variables
return rows.map(row => {
let parsedVariables: Record<string, string> | null = null;
if (row.variables) {
try {
parsedVariables = JSON.parse(row.variables);
} catch (e) {
console.error(`Error parsing variables for quick command ${row.id}:`, e);
//保持 parsedVariables 为 null
}
}
const { variables, ...restOfRow } = row; // 从 row 中移除原始的 string 类型的 variables
return {
...restOfRow,
variables: parsedVariables,
tagIds: row.tag_ids_str ? row.tag_ids_str.split(',').map(Number).filter(id => !isNaN(id)) : []
};
});
} catch (err: any) {
console.error('获取快捷指令(带标签)时出错:', err.message);
throw new Error('无法获取快捷指令');
@@ -137,7 +157,7 @@ export const findQuickCommandById = async (id: number): Promise<QuickCommandWith
// 使用 LEFT JOIN 连接关联表,并使用 GROUP_CONCAT 获取标签 ID 字符串
const sql = `
SELECT
qc.id, qc.name, qc.command, qc.usage_count, qc.created_at, qc.updated_at,
qc.id, qc.name, qc.command, qc.usage_count, qc.variables, qc.created_at, qc.updated_at,
GROUP_CONCAT(qta.tag_id) as tag_ids_str
FROM quick_commands qc
LEFT JOIN quick_command_tag_associations qta ON qc.id = qta.quick_command_id
@@ -147,9 +167,20 @@ export const findQuickCommandById = async (id: number): Promise<QuickCommandWith
const db = await getDbInstance();
const row = await getDbRow<DbQuickCommandWithTagsRow>(db, sql, [id]);
if (row && typeof row.id !== 'undefined') {
// 将 tag_ids_str 解析为数字数组
// 将 tag_ids_str 解析为数字数组,并解析 variables
let parsedVariables: Record<string, string> | null = null;
if (row.variables) {
try {
parsedVariables = JSON.parse(row.variables);
} catch (e) {
console.error(`Error parsing variables for quick command ${row.id}:`, e);
//保持 parsedVariables 为 null
}
}
const { variables, ...restOfRow } = row; // 从 row 中移除原始的 string 类型的 variables
return {
...row,
...restOfRow,
variables: parsedVariables,
tagIds: row.tag_ids_str ? row.tag_ids_str.split(',').map(Number).filter(id => !isNaN(id)) : []
};
} else {
@@ -10,15 +10,16 @@ export type QuickCommandSortBy = 'name' | 'usage_count';
* @param name - 指令名称 (可选)
* @param command - 指令内容
* @param tagIds - 关联的快捷指令标签 ID 数组 (可选)
* @param variables - 变量对象 (可选)
* @returns 返回添加记录的 ID
*/
export const addQuickCommand = async (name: string | null, command: string, tagIds?: number[]): Promise<number> => {
export const addQuickCommand = async (name: string | null, command: string, tagIds?: number[], variables?: Record<string, string>): Promise<number> => {
if (!command || command.trim().length === 0) {
throw new Error('指令内容不能为空');
}
// 如果 name 是空字符串,则视为 null
const finalName = name && name.trim().length > 0 ? name.trim() : null;
const commandId = await QuickCommandsRepository.addQuickCommand(finalName, command.trim());
const commandId = await QuickCommandsRepository.addQuickCommand(finalName, command.trim(), variables);
// 添加成功后,设置标签关联
if (commandId > 0 && tagIds && Array.isArray(tagIds)) {
@@ -39,14 +40,15 @@ export const addQuickCommand = async (name: string | null, command: string, tagI
* @param name - 新的指令名称 (可选)
* @param command - 新的指令内容
* @param tagIds - 新的关联标签 ID 数组 (可选, undefined 表示不更新标签)
* @param variables - 新的变量对象 (可选)
* @returns 返回是否成功更新 (更新行数 > 0)
*/
export const updateQuickCommand = async (id: number, name: string | null, command: string, tagIds?: number[]): Promise<boolean> => {
export const updateQuickCommand = async (id: number, name: string | null, command: string, tagIds?: number[], variables?: Record<string, string>): Promise<boolean> => {
if (!command || command.trim().length === 0) {
throw new Error('指令内容不能为空');
}
const finalName = name && name.trim().length > 0 ? name.trim() : null;
const commandUpdated = await QuickCommandsRepository.updateQuickCommand(id, finalName, command.trim());
const commandUpdated = await QuickCommandsRepository.updateQuickCommand(id, finalName, command.trim(), variables);
// 如果指令更新成功,并且提供了 tagIds (即使是空数组也表示要更新),则更新标签关联
if (commandUpdated && typeof tagIds !== 'undefined') {