Skip to main content
Glama
helpers.ts7.73 kB
/** * 辅助工具方法 * 提供额外的便捷功能,增强 LLM 的可用性 */ import type { SiyuanClient } from '../api/client.js'; import type { Block, SearchResultResponse } from '../types/index.js'; import type { EnhancedBlock, OperationResult } from '../types/enhanced.js'; import { extractTitle, truncateContent } from './format.js'; export class SiyuanHelpers { constructor(private client: SiyuanClient) {} /** * 根据 ID 获取完整的块信息(增强版) * @param blockId 块 ID * @returns 增强的块信息 */ async getEnhancedBlockInfo(blockId: string): Promise<EnhancedBlock> { // 通过 SQL 获取完整块信息 const response = await this.client.request<Block[]>('/api/query/sql', { stmt: `SELECT * FROM blocks WHERE id='${blockId}'`, }); if (!response.data || response.data.length === 0) { throw new Error(`Block not found: ${blockId}`); } const block = response.data[0] as EnhancedBlock; // 获取父块信息 if (block.parent_id && block.parent_id !== block.id) { const parentResponse = await this.client.request<Block[]>('/api/query/sql', { stmt: `SELECT id, content, type FROM blocks WHERE id='${block.parent_id}'`, }); if (parentResponse.data && parentResponse.data.length > 0) { const parent = parentResponse.data[0]; block.parentInfo = { id: parent.id, content: parent.content, type: parent.type, }; } } // 获取子块数量 const childrenResponse = await this.client.request<Array<{ count: number }>>( '/api/query/sql', { stmt: `SELECT COUNT(*) as count FROM blocks WHERE parent_id='${blockId}' AND id!='${blockId}'`, } ); if (childrenResponse.data && childrenResponse.data.length > 0) { block.childrenCount = childrenResponse.data[0].count; } // 获取笔记本名称 const notebookResponse = await this.client.request<{ notebooks: Array<{ id: string; name: string }> }>( '/api/notebook/lsNotebooks' ); if (notebookResponse.data && notebookResponse.data.notebooks) { const notebook = notebookResponse.data.notebooks.find((nb) => nb.id === block.box); if (notebook) { block.notebookName = notebook.name; } } return block; } /** * 获取块的完整上下文(包括前后兄弟块) * @param blockId 块 ID * @param context 前后各获取几个块,默认 2 */ async getBlockContext( blockId: string, context: number = 2 ): Promise<{ target: Block; previous: Block[]; next: Block[]; }> { // 获取目标块 const targetResponse = await this.client.request<Block[]>('/api/query/sql', { stmt: `SELECT * FROM blocks WHERE id='${blockId}'`, }); if (!targetResponse.data || targetResponse.data.length === 0) { throw new Error(`Block not found: ${blockId}`); } const target = targetResponse.data[0]; // 获取同一父块下的兄弟块 const siblingsResponse = await this.client.request<Block[]>('/api/query/sql', { stmt: `SELECT * FROM blocks WHERE parent_id='${target.parent_id}' ORDER BY sort`, }); const siblings = siblingsResponse.data || []; const targetIndex = siblings.findIndex((b) => b.id === blockId); const previous = targetIndex > 0 ? siblings.slice(Math.max(0, targetIndex - context), targetIndex) : []; const next = targetIndex < siblings.length - 1 ? siblings.slice(targetIndex + 1, targetIndex + 1 + context) : []; return { target, previous, next }; } /** * 获取文档的大纲结构 * @param docId 文档 ID */ async getDocumentOutline(docId: string): Promise<Array<{ id: string; content: string; type: string; level: number; children: Array<any>; }>> { // 获取所有标题块 const response = await this.client.request<Block[]>('/api/query/sql', { stmt: `SELECT * FROM blocks WHERE root_id='${docId}' AND type='h' ORDER BY sort`, }); if (!response.data) { return []; } // 解析标题层级 const outline = response.data.map((block) => { // 从 subtype 中提取层级 (h1, h2, h3...) const level = parseInt(block.subtype.replace('h', '')) || 1; return { id: block.id, content: block.content, type: block.type, level, children: [], }; }); return outline; } /** * 获取最近更新的文档 * @param limit 返回数量 * @param notebookId 限制在特定笔记本(可选) */ async getRecentlyUpdatedDocuments(limit: number = 10, notebookId?: string): Promise<SearchResultResponse[]> { let stmt = `SELECT * FROM blocks WHERE type='d'`; if (notebookId) { stmt += ` AND box='${notebookId}'`; } stmt += ` ORDER BY updated DESC LIMIT ${limit}`; const response = await this.client.request<Block[]>('/api/query/sql', { stmt }); const blocks = response.data || []; return this.toSearchResultResponse(blocks); } /** * 将Block数组转换为搜索结果响应 */ private toSearchResultResponse(blocks: Block[]): SearchResultResponse[] { return blocks.map(block => ({ id: block.id, name: block.name || extractTitle(block.content), path: block.hpath || block.path, content: truncateContent(block.content, 100), // 简短摘要 type: block.type, updated: block.updated })); } /** * 创建操作结果对象 */ createOperationResult( success: boolean, id: string, operation: OperationResult['operation'], resourceType: OperationResult['resourceType'], metadata?: OperationResult['metadata'] ): OperationResult { return { success, id, operation, resourceType, metadata: { ...metadata, timestamp: new Date().toISOString(), }, }; } /** * 格式化块的引用链接 * @param blockId 块 ID * @param anchor 锚文本(可选) */ formatBlockRef(blockId: string, anchor?: string): string { return `((${blockId} "${anchor || blockId}"))`; } /** * 解析思源笔记的时间戳格式 * @param timestamp 思源笔记时间戳(如 20230404163414) */ parseTimestamp(timestamp: string): Date { // 格式: YYYYMMDDHHmmss const year = parseInt(timestamp.substring(0, 4)); const month = parseInt(timestamp.substring(4, 6)) - 1; const day = parseInt(timestamp.substring(6, 8)); const hour = parseInt(timestamp.substring(8, 10)); const minute = parseInt(timestamp.substring(10, 12)); const second = parseInt(timestamp.substring(12, 14)); return new Date(year, month, day, hour, minute, second); } /** * 格式化思源笔记时间戳 * @param date 日期对象 */ formatTimestamp(date: Date = new Date()): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hour = String(date.getHours()).padStart(2, '0'); const minute = String(date.getMinutes()).padStart(2, '0'); const second = String(date.getSeconds()).padStart(2, '0'); return `${year}${month}${day}${hour}${minute}${second}`; } /** * 获取块的面包屑路径 * @param blockId 块 ID */ async getBreadcrumb(blockId: string): Promise<string[]> { const response = await this.client.request<{ hPath: string }>('/api/filetree/getHPathByID', { id: blockId, }); if (response.code !== 0 || !response.data || !response.data.hPath) { return []; } return response.data.hPath.split('/').filter((p) => p); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/porkll/siyuan-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server