lint: fix code format

This commit is contained in:
kangfenmao 2025-04-19 18:58:26 +08:00
parent 4663794ba6
commit 55aac1cb9b
3 changed files with 328 additions and 277 deletions

View File

@ -1,9 +1,9 @@
import { getConfigDir } from '@main/utils/file'
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js'
import { Mutex } from 'async-mutex' // 引入 Mutex
import { promises as fs } from 'fs'
import path from 'path'
import { Mutex } from 'async-mutex' // 引入 Mutex
// Define memory file path
const defaultMemoryPath = path.join(getConfigDir(), 'memory.json')
@ -62,7 +62,10 @@ class KnowledgeGraphManager {
} catch (error) {
console.error('Failed to ensure memory path exists:', error)
// Propagate the error or handle it more gracefully depending on requirements
throw new McpError(ErrorCode.InternalError, `Failed to ensure memory path: ${error instanceof Error ? error.message : String(error)}`)
throw new McpError(
ErrorCode.InternalError,
`Failed to ensure memory path: ${error instanceof Error ? error.message : String(error)}`
)
}
}
@ -81,8 +84,8 @@ class KnowledgeGraphManager {
const graph: KnowledgeGraph = JSON.parse(data)
this.entities.clear()
this.relations.clear()
graph.entities.forEach(entity => this.entities.set(entity.name, entity))
graph.relations.forEach(relation => this.relations.add(this._serializeRelation(relation)))
graph.entities.forEach((entity) => this.entities.set(entity.name, entity))
graph.relations.forEach((relation) => this.relations.add(this._serializeRelation(relation)))
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') {
// File doesn't exist (should have been created by _ensureMemoryPathExists, but handle defensively)
@ -97,7 +100,10 @@ class KnowledgeGraphManager {
await this._persistGraph()
} else {
console.error('Failed to load knowledge graph from disk:', error)
throw new McpError(ErrorCode.InternalError, `Failed to load graph: ${error instanceof Error ? error.message : String(error)}`)
throw new McpError(
ErrorCode.InternalError,
`Failed to load graph: ${error instanceof Error ? error.message : String(error)}`
)
}
}
}
@ -108,13 +114,16 @@ class KnowledgeGraphManager {
try {
const graphData: KnowledgeGraph = {
entities: Array.from(this.entities.values()),
relations: Array.from(this.relations).map(rStr => this._deserializeRelation(rStr))
relations: Array.from(this.relations).map((rStr) => this._deserializeRelation(rStr))
}
await fs.writeFile(this.memoryPath, JSON.stringify(graphData, null, 2))
} catch (error) {
console.error('Failed to save knowledge graph:', error)
// Decide how to handle write errors - potentially retry or notify
throw new McpError(ErrorCode.InternalError, `Failed to save graph: ${error instanceof Error ? error.message : String(error)}`)
throw new McpError(
ErrorCode.InternalError,
`Failed to save graph: ${error instanceof Error ? error.message : String(error)}`
)
} finally {
release()
}
@ -133,10 +142,10 @@ class KnowledgeGraphManager {
async createEntities(entities: Entity[]): Promise<Entity[]> {
const newEntities: Entity[] = []
entities.forEach(entity => {
entities.forEach((entity) => {
if (!this.entities.has(entity.name)) {
// Ensure observations is always an array
const newEntity = { ...entity, observations: Array.isArray(entity.observations) ? entity.observations : [] };
const newEntity = { ...entity, observations: Array.isArray(entity.observations) ? entity.observations : [] }
this.entities.set(entity.name, newEntity)
newEntities.push(newEntity)
}
@ -149,11 +158,11 @@ class KnowledgeGraphManager {
async createRelations(relations: Relation[]): Promise<Relation[]> {
const newRelations: Relation[] = []
relations.forEach(relation => {
relations.forEach((relation) => {
// Ensure related entities exist before creating a relation
if (!this.entities.has(relation.from) || !this.entities.has(relation.to)) {
console.warn(`Skipping relation creation: Entity not found for relation ${relation.from} -> ${relation.to}`)
return; // Skip this relation
return // Skip this relation
}
const relationStr = this._serializeRelation(relation)
if (!this.relations.has(relationStr)) {
@ -172,7 +181,7 @@ class KnowledgeGraphManager {
): Promise<{ entityName: string; addedObservations: string[] }[]> {
const results: { entityName: string; addedObservations: string[] }[] = []
let changed = false
observations.forEach(o => {
observations.forEach((o) => {
const entity = this.entities.get(o.entityName)
if (!entity) {
// Option 1: Throw error
@ -183,9 +192,9 @@ class KnowledgeGraphManager {
}
// Ensure observations array exists
if (!Array.isArray(entity.observations)) {
entity.observations = [];
entity.observations = []
}
const newObservations = o.contents.filter(content => !entity.observations.includes(content))
const newObservations = o.contents.filter((content) => !entity.observations.includes(content))
if (newObservations.length > 0) {
entity.observations.push(...newObservations)
results.push({ entityName: o.entityName, addedObservations: newObservations })
@ -206,7 +215,7 @@ class KnowledgeGraphManager {
const namesToDelete = new Set(entityNames)
// Delete entities
namesToDelete.forEach(name => {
namesToDelete.forEach((name) => {
if (this.entities.delete(name)) {
changed = true
}
@ -214,14 +223,14 @@ class KnowledgeGraphManager {
// Delete relations involving deleted entities
const relationsToDelete = new Set<string>()
this.relations.forEach(relStr => {
this.relations.forEach((relStr) => {
const rel = this._deserializeRelation(relStr)
if (namesToDelete.has(rel.from) || namesToDelete.has(rel.to)) {
relationsToDelete.add(relStr)
}
})
relationsToDelete.forEach(relStr => {
relationsToDelete.forEach((relStr) => {
if (this.relations.delete(relStr)) {
changed = true
}
@ -234,12 +243,12 @@ class KnowledgeGraphManager {
async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> {
let changed = false
deletions.forEach(d => {
deletions.forEach((d) => {
const entity = this.entities.get(d.entityName)
if (entity && Array.isArray(entity.observations)) {
const initialLength = entity.observations.length
const observationsToDelete = new Set(d.observations)
entity.observations = entity.observations.filter(o => !observationsToDelete.has(o))
entity.observations = entity.observations.filter((o) => !observationsToDelete.has(o))
if (entity.observations.length !== initialLength) {
changed = true
}
@ -252,7 +261,7 @@ class KnowledgeGraphManager {
async deleteRelations(relations: Relation[]): Promise<void> {
let changed = false
relations.forEach(rel => {
relations.forEach((rel) => {
const relStr = this._serializeRelation(rel)
if (this.relations.delete(relStr)) {
changed = true
@ -266,27 +275,29 @@ class KnowledgeGraphManager {
// Read the current state from memory
async readGraph(): Promise<KnowledgeGraph> {
// Return a deep copy to prevent external modification of the internal state
return JSON.parse(JSON.stringify({
return JSON.parse(
JSON.stringify({
entities: Array.from(this.entities.values()),
relations: Array.from(this.relations).map(rStr => this._deserializeRelation(rStr))
}));
relations: Array.from(this.relations).map((rStr) => this._deserializeRelation(rStr))
})
)
}
// Search operates on the in-memory graph
async searchNodes(query: string): Promise<KnowledgeGraph> {
const lowerCaseQuery = query.toLowerCase()
const filteredEntities = Array.from(this.entities.values()).filter(
e =>
(e) =>
e.name.toLowerCase().includes(lowerCaseQuery) ||
e.entityType.toLowerCase().includes(lowerCaseQuery) ||
(Array.isArray(e.observations) && e.observations.some(o => o.toLowerCase().includes(lowerCaseQuery)))
(Array.isArray(e.observations) && e.observations.some((o) => o.toLowerCase().includes(lowerCaseQuery)))
)
const filteredEntityNames = new Set(filteredEntities.map(e => e.name))
const filteredEntityNames = new Set(filteredEntities.map((e) => e.name))
const filteredRelations = Array.from(this.relations)
.map(rStr => this._deserializeRelation(rStr))
.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to))
.map((rStr) => this._deserializeRelation(rStr))
.filter((r) => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to))
return {
entities: filteredEntities,
@ -296,26 +307,26 @@ class KnowledgeGraphManager {
// Open operates on the in-memory graph
async openNodes(names: string[]): Promise<KnowledgeGraph> {
const nameSet = new Set(names);
const filteredEntities = Array.from(this.entities.values()).filter(e => nameSet.has(e.name));
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
const nameSet = new Set(names)
const filteredEntities = Array.from(this.entities.values()).filter((e) => nameSet.has(e.name))
const filteredEntityNames = new Set(filteredEntities.map((e) => e.name))
const filteredRelations = Array.from(this.relations)
.map(rStr => this._deserializeRelation(rStr))
.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to));
.map((rStr) => this._deserializeRelation(rStr))
.filter((r) => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to))
return {
entities: filteredEntities,
relations: filteredRelations
};
}
}
}
class MemoryServer {
public server: Server
// Hold the manager instance, initialized asynchronously
private knowledgeGraphManager: KnowledgeGraphManager | null = null;
private initializationPromise: Promise<void>; // To track initialization
private knowledgeGraphManager: KnowledgeGraphManager | null = null
private initializationPromise: Promise<void> // To track initialization
constructor(envPath: string = '') {
const memoryPath = envPath
@ -336,33 +347,32 @@ class MemoryServer {
}
)
// Start initialization, but don't block constructor
this.initializationPromise = this._initializeManager(memoryPath);
this.setupRequestHandlers(); // Setup handlers immediately
this.initializationPromise = this._initializeManager(memoryPath)
this.setupRequestHandlers() // Setup handlers immediately
}
// Private async method to handle manager initialization
private async _initializeManager(memoryPath: string): Promise<void> {
try {
this.knowledgeGraphManager = await KnowledgeGraphManager.create(memoryPath);
console.log("KnowledgeGraphManager initialized successfully.");
this.knowledgeGraphManager = await KnowledgeGraphManager.create(memoryPath)
console.log('KnowledgeGraphManager initialized successfully.')
} catch (error) {
console.error("Failed to initialize KnowledgeGraphManager:", error);
console.error('Failed to initialize KnowledgeGraphManager:', error)
// Server might be unusable, consider how to handle this state
// Maybe set a flag and return errors for all tool calls?
this.knowledgeGraphManager = null; // Ensure it's null if init fails
this.knowledgeGraphManager = null // Ensure it's null if init fails
}
}
// Ensures the manager is initialized before handling tool calls
private async _getManager(): Promise<KnowledgeGraphManager> {
await this.initializationPromise; // Wait for initialization to complete
await this.initializationPromise // Wait for initialization to complete
if (!this.knowledgeGraphManager) {
throw new McpError(ErrorCode.InternalError, "Memory server failed to initialize. Cannot process requests.");
throw new McpError(ErrorCode.InternalError, 'Memory server failed to initialize. Cannot process requests.')
}
return this.knowledgeGraphManager;
return this.knowledgeGraphManager
}
// Setup handlers (can be called from constructor)
setupRequestHandlers() {
// ListTools remains largely the same, descriptions might be updated if needed
@ -371,11 +381,11 @@ class MemoryServer {
// Although ListTools itself doesn't *call* the manager, it implies the
// manager is ready to handle calls for those tools.
try {
await this._getManager(); // Wait for initialization before confirming tools are available
await this._getManager() // Wait for initialization before confirming tools are available
} catch (error) {
// If manager failed to init, maybe return an empty tool list or throw?
console.error("Cannot list tools, manager initialization failed:", error);
return { tools: [] }; // Return empty list if server is not ready
console.error('Cannot list tools, manager initialization failed:', error)
return { tools: [] } // Return empty list if server is not ready
}
return {
@ -409,7 +419,8 @@ class MemoryServer {
},
{
name: 'create_relations',
description: 'Create multiple new relations between EXISTING entities. Skips existing relations or relations with non-existent entities.',
description:
'Create multiple new relations between EXISTING entities. Skips existing relations or relations with non-existent entities.',
inputSchema: {
type: 'object',
properties: {
@ -560,7 +571,7 @@ class MemoryServer {
// CallTool handler needs to await the manager and the async methods
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const manager = await this._getManager(); // Ensure manager is ready
const manager = await this._getManager() // Ensure manager is ready
const { name, arguments: args } = request.params
if (!args) {
@ -573,40 +584,74 @@ class MemoryServer {
case 'create_entities':
// Validate args structure if necessary, though SDK might do basic validation
if (!args.entities || !Array.isArray(args.entities)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'entities' array is required.`);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'entities' array is required.`
)
}
return {
content: [{ type: 'text', text: JSON.stringify(await manager.createEntities(args.entities as Entity[]), null, 2) }]
content: [
{ type: 'text', text: JSON.stringify(await manager.createEntities(args.entities as Entity[]), null, 2) }
]
}
case 'create_relations':
if (!args.relations || !Array.isArray(args.relations)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'relations' array is required.`);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'relations' array is required.`
)
}
return {
content: [{ type: 'text', text: JSON.stringify(await manager.createRelations(args.relations as Relation[]), null, 2) }]
content: [
{
type: 'text',
text: JSON.stringify(await manager.createRelations(args.relations as Relation[]), null, 2)
}
]
}
case 'add_observations':
if (!args.observations || !Array.isArray(args.observations)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'observations' array is required.`);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'observations' array is required.`
)
}
return {
content: [{ type: 'text', text: JSON.stringify(await manager.addObservations(args.observations as { entityName: string; contents: string[] }[]), null, 2) }]
content: [
{
type: 'text',
text: JSON.stringify(
await manager.addObservations(args.observations as { entityName: string; contents: string[] }[]),
null,
2
)
}
]
}
case 'delete_entities':
if (!args.entityNames || !Array.isArray(args.entityNames)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'entityNames' array is required.`);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'entityNames' array is required.`
)
}
await manager.deleteEntities(args.entityNames as string[])
return { content: [{ type: 'text', text: 'Entities deleted successfully' }] }
case 'delete_observations':
if (!args.deletions || !Array.isArray(args.deletions)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'deletions' array is required.`);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'deletions' array is required.`
)
}
await manager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[])
return { content: [{ type: 'text', text: 'Observations deleted successfully' }] }
case 'delete_relations':
if (!args.relations || !Array.isArray(args.relations)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'relations' array is required.`);
throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'relations' array is required.`
)
}
await manager.deleteRelations(args.relations as Relation[])
return { content: [{ type: 'text', text: 'Relations deleted successfully' }] }
@ -617,17 +662,21 @@ class MemoryServer {
}
case 'search_nodes':
if (typeof args.query !== 'string') {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'query' string is required.`);
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'query' string is required.`)
}
return {
content: [{ type: 'text', text: JSON.stringify(await manager.searchNodes(args.query as string), null, 2) }]
content: [
{ type: 'text', text: JSON.stringify(await manager.searchNodes(args.query as string), null, 2) }
]
}
case 'open_nodes':
if (!args.names || !Array.isArray(args.names)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'names' array is required.`);
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'names' array is required.`)
}
return {
content: [{ type: 'text', text: JSON.stringify(await manager.openNodes(args.names as string[]), null, 2) }]
content: [
{ type: 'text', text: JSON.stringify(await manager.openNodes(args.names as string[]), null, 2) }
]
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`)
@ -635,11 +684,14 @@ class MemoryServer {
} catch (error) {
// Catch errors from manager methods (like entity not found) or other issues
if (error instanceof McpError) {
throw error; // Re-throw McpErrors directly
throw error // Re-throw McpErrors directly
}
console.error(`Error executing tool ${name}:`, error);
console.error(`Error executing tool ${name}:`, error)
// Throw a generic internal error for unexpected issues
throw new McpError(ErrorCode.InternalError, `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}`);
throw new McpError(
ErrorCode.InternalError,
`Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}`
)
}
})
}

View File

@ -324,7 +324,6 @@ const McpSettings: React.FC<Props> = ({ server }) => {
}
const onToggleActive = async (active: boolean) => {
if (isFormChanged && active) {
await onSave()
return

View File

@ -2,8 +2,8 @@ import '@renderer/databases'
import { useSettings } from '@renderer/hooks/useSettings'
import store, { persistor, useAppDispatch } from '@renderer/store'
import { message } from 'antd'
import { setCustomCss } from '@renderer/store/settings'
import { message } from 'antd'
import { useEffect, useState } from 'react'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'