增加小型对象数据库

This commit is contained in:
锦恢 2025-04-25 20:14:59 +08:00
parent f925da7d7d
commit ca64ce040f
18 changed files with 1501 additions and 122 deletions

View File

@ -2,7 +2,10 @@
## [main] 0.0.5 ## [main] 0.0.5
- 支持对已经打开过的文件项目进行管理 - 支持对已经打开过的文件项目进行管理
- 支持对用户对应服务器的调试工作内容进行保存MVP 100% - 支持对用户对应服务器的调试工作内容进行保存
- 支持连续工具调用和错误警告的显示
- 实现小型本地对象数据库,用于对对话产生的多媒体进行数据持久化
- 支持对于调用结果进行一键复现
## [main] 0.0.4 ## [main] 0.0.4
- 修复选择模型后点击确认跳转回 deepseek 的 bug - 修复选择模型后点击确认跳转回 deepseek 的 bug

View File

@ -51,6 +51,7 @@
| `service` | 系统配置信息云同步 | `MVP` | 0% | `P1` | | `service` | 系统配置信息云同步 | `MVP` | 0% | `P1` |
| `all` | 系统提示词管理模块 | `MVP` | 0% | `P1` | | `all` | 系统提示词管理模块 | `MVP` | 0% | `P1` |
| `service` | 工具 wise 的日志系统 | `MVP` | 0% | `P0` | | `service` | 工具 wise 的日志系统 | `MVP` | 0% | `P0` |
| `service` | 自带 OCR 进行字符识别 | `MVP` | 0% | `P1` |
## Dev ## Dev

1257
service/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -41,6 +41,8 @@
"pako": "^2.1.0", "pako": "^2.1.0",
"pino": "^9.6.0", "pino": "^9.6.0",
"pino-pretty": "^13.0.0", "pino-pretty": "^13.0.0",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7",
"tesseract.js": "^6.0.1", "tesseract.js": "^6.0.1",
"ws": "^8.18.1" "ws": "^8.18.1"
} }

View File

@ -1,140 +1,94 @@
import { PostMessageble } from '../hook/adapter'; import { PostMessageble } from '../hook/adapter';
import { connect, MCPClient, type MCPOptions } from '../hook/client'; import { lookupEnvVarService } from '../service/env-var';
import { lookupEnvVarHandler } from './env-var';
import { callTool, getPrompt, getServerVersion, listPrompts, listResources, listResourceTemplates, listTools, readResource } from './mcp-server';
import { chatCompletionHandler } from './llm';
import { panelLoadHandler, panelSaveHandler } from './panel';
import { settingLoadHandler, settingSaveHandler } from './setting';
import { ping } from './util';
import { spawnSync } from 'node:child_process'; import {
callToolService,
getPromptService,
getServerVersionService,
listPromptsService,
listResourcesService,
listResourceTemplatesService,
listToolsService,
readResourceService
} from '../service/mcp-server';
import { abortMessageService, chatCompletionService } from '../service/llm';
import { panelLoadService, panelSaveService } from '../service/panel';
import { settingLoadService, settingSaveService } from '../service/setting';
import { pingService } from '../service/util';
import { client, connectService } from '../service/connect';
// TODO: 支持更多的 client
export let client: MCPClient | undefined = undefined;
function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
try {
console.log('current command', command);
console.log('current args', args);
const result = spawnSync(command, args, {
cwd: cwd || process.cwd(),
stdio: 'pipe',
encoding: 'utf-8'
});
if (result.error) {
return result.error.message;
}
if (result.status !== 0) {
return result.stderr || `Command failed with code ${result.status}`;
}
return null;
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
async function connectHandler(option: MCPOptions, webview: PostMessageble) {
try {
console.log('ready to connect', option);
client = await connect(option);
const connectResult = {
code: 200,
msg: 'Connect to OpenMCP successfully\nWelcome back, Kirigaya'
};
webview.postMessage({ command: 'connect', data: connectResult });
} catch (error) {
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
// 比如 error: Failed to spawn: `server.py`
// Caused by: No such file or directory (os error 2)
let errorMsg = '';
if (option.command) {
errorMsg += tryGetRunCommandError(option.command, option.args, option.cwd);
}
errorMsg += (error as any).toString();
const connectResult = {
code: 500,
msg: errorMsg
};
webview.postMessage({ command: 'connect', data: connectResult });
}
}
export function messageController(command: string, data: any, webview: PostMessageble) { export function messageController(command: string, data: any, webview: PostMessageble) {
switch (command) { switch (command) {
case 'connect': case 'connect':
connectHandler(data, webview); connectService(client, data, webview);
break; break;
case 'server/version': case 'server/version':
getServerVersion(client, webview); getServerVersionService(client, data, webview);
break; break;
case 'prompts/list': case 'prompts/list':
listPrompts(client, webview); listPromptsService(client, data, webview);
break; break;
case 'prompts/get': case 'prompts/get':
getPrompt(client, data, webview); getPromptService(client, data, webview);
break; break;
case 'resources/list': case 'resources/list':
listResources(client, webview); listResourcesService(client, data, webview);
break; break;
case 'resources/templates/list': case 'resources/templates/list':
listResourceTemplates(client, webview); listResourceTemplatesService(client, data, webview);
break; break;
case 'resources/read': case 'resources/read':
readResource(client, data, webview); readResourceService(client, data, webview);
break; break;
case 'tools/list': case 'tools/list':
listTools(client, webview); listToolsService(client, data, webview);
break; break;
case 'tools/call': case 'tools/call':
callTool(client, data, webview); callToolService(client, data, webview);
break; break;
case 'ping': case 'ping':
ping(client, webview); pingService(client, data, webview);
break; break;
case 'setting/save': case 'setting/save':
settingSaveHandler(client, data, webview); settingSaveService(client, data, webview);
break; break;
case 'setting/load': case 'setting/load':
settingLoadHandler(client, webview); settingLoadService(client, data, webview);
break; break;
case 'panel/save': case 'panel/save':
panelSaveHandler(client, data, webview); panelSaveService(client, data, webview);
break; break;
case 'panel/load': case 'panel/load':
panelLoadHandler(client, webview); panelLoadService(client, data, webview);
break; break;
case 'llm/chat/completions': case 'llm/chat/completions':
chatCompletionHandler(client, data, webview); chatCompletionService(client, data, webview);
break; break;
case 'llm/chat/completions/cancel':
abortMessageService(client, data, webview);
break;
case 'lookup-env-var': case 'lookup-env-var':
lookupEnvVarHandler(client, data, webview); lookupEnvVarService(client, data, webview);
break; break;
default: default:

69
service/src/hook/db.ts Normal file
View File

@ -0,0 +1,69 @@
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import * as os from 'os';
import * as path from 'path';
interface Entity {
id: string | number;
[key: string]: any;
}
export class LocalDB<T extends Entity> {
private db: any;
constructor(private tableName: string) {
this.init();
}
private async init() {
// 默认存储在用户目录的 .openmcp 目录下
const homedir = os.homedir();
const filename = path.join(homedir, '.openmcp', 'db.sqlite');
this.db = await open({
filename,
driver: sqlite3.Database
});
await this.db.exec(`
CREATE TABLE IF NOT EXISTS ${this.tableName} (
id TEXT PRIMARY KEY,
data TEXT NOT NULL
)
`);
}
async insert(entity: T): Promise<void> {
await this.db.run(
`INSERT OR REPLACE INTO ${this.tableName} (id, data) VALUES (?, ?)`,
entity.id,
JSON.stringify(entity)
);
}
async findById(id: string | number): Promise<T | undefined> {
const row = await this.db.get(
`SELECT data FROM ${this.tableName} WHERE id = ?`,
id
);
return row ? JSON.parse(row.data) : undefined;
}
async findAll(): Promise<T[]> {
const rows = await this.db.all(
`SELECT data FROM ${this.tableName}`
);
return rows.map((row: any) => JSON.parse(row.data));
}
async delete(id: string | number): Promise<void> {
await this.db.run(
`DELETE FROM ${this.tableName} WHERE id = ?`,
id
);
}
async close(): Promise<void> {
await this.db.close();
}
}

View File

@ -2,4 +2,4 @@ export { messageController } from './controller';
export { VSCodeWebViewLike } from './hook/adapter'; export { VSCodeWebViewLike } from './hook/adapter';
export { setVscodeWorkspace } from './hook/setting'; export { setVscodeWorkspace } from './hook/setting';
// TODO: 更加规范 // TODO: 更加规范
export { client } from './controller'; export { client } from './service/connect';

View File

@ -2,7 +2,7 @@ import WebSocket from 'ws';
import pino from 'pino'; import pino from 'pino';
import { messageController } from './controller'; import { messageController } from './controller';
import { VSCodeWebViewLike } from './adapter'; import { VSCodeWebViewLike } from './hook/adapter';
export interface VSCodeMessage { export interface VSCodeMessage {
command: string; command: string;

View File

@ -0,0 +1,66 @@
import { PostMessageble } from '../hook/adapter';
import { connect, MCPClient, type MCPOptions } from '../hook/client';
import { spawnSync } from 'node:child_process';
// TODO: 支持更多的 client
export let client: MCPClient | undefined = undefined;
function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
try {
console.log('current command', command);
console.log('current args', args);
const result = spawnSync(command, args, {
cwd: cwd || process.cwd(),
stdio: 'pipe',
encoding: 'utf-8'
});
if (result.error) {
return result.error.message;
}
if (result.status !== 0) {
return result.stderr || `Command failed with code ${result.status}`;
}
return null;
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
export async function connectService(
client: MCPClient | undefined,
option: MCPOptions,
webview: PostMessageble
) {
try {
console.log('ready to connect', option);
client = await connect(option);
const connectResult = {
code: 200,
msg: 'Connect to OpenMCP successfully\nWelcome back, Kirigaya'
};
webview.postMessage({ command: 'connect', data: connectResult });
} catch (error) {
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
// 比如 error: Failed to spawn: `server.py`
// Caused by: No such file or directory (os error 2)
let errorMsg = '';
if (option.command) {
errorMsg += tryGetRunCommandError(option.command, option.args, option.cwd);
}
errorMsg += (error as any).toString();
const connectResult = {
code: 500,
msg: errorMsg
};
webview.postMessage({ command: 'connect', data: connectResult });
}
}

View File

@ -2,7 +2,7 @@ import { PostMessageble } from "../hook/adapter";
import { MCPClient } from "../hook/client"; import { MCPClient } from "../hook/client";
export async function lookupEnvVarHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) { export async function lookupEnvVarService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
try { try {
const { keys } = data; const { keys } = data;

View File

@ -4,7 +4,7 @@ import { PostMessageble } from '../hook/adapter';
let currentStream: AsyncIterable<any> | null = null; let currentStream: AsyncIterable<any> | null = null;
export async function chatCompletionHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) { export async function chatCompletionService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
if (!client) { if (!client) {
const connectResult = { const connectResult = {
code: 501, code: 501,
@ -100,7 +100,7 @@ export async function chatCompletionHandler(client: MCPClient | undefined, data:
} }
// 处理中止消息的函数 // 处理中止消息的函数
export function handleAbortMessage(webview: PostMessageble) { export function abortMessageService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
if (currentStream) { if (currentStream) {
// 标记流已中止 // 标记流已中止
currentStream = null; currentStream = null;

View File

@ -21,8 +21,9 @@ export interface CallToolOption {
/** /**
* @description prompts * @description prompts
*/ */
export async function listPrompts( export async function listPromptsService(
client: MCPClient | undefined, client: MCPClient | undefined,
data: any,
webview: PostMessageble webview: PostMessageble
) { ) {
if (!client) { if (!client) {
@ -53,7 +54,7 @@ export async function listPrompts(
/** /**
* @description prompt * @description prompt
*/ */
export async function getPrompt( export async function getPromptService(
client: MCPClient | undefined, client: MCPClient | undefined,
option: GetPromptOption, option: GetPromptOption,
webview: PostMessageble webview: PostMessageble
@ -86,8 +87,9 @@ export async function getPrompt(
/** /**
* @description resources * @description resources
*/ */
export async function listResources( export async function listResourcesService(
client: MCPClient | undefined, client: MCPClient | undefined,
data: any,
webview: PostMessageble webview: PostMessageble
) { ) {
if (!client) { if (!client) {
@ -119,8 +121,9 @@ export async function listResources(
/** /**
* @description resources * @description resources
*/ */
export async function listResourceTemplates( export async function listResourceTemplatesService(
client: MCPClient | undefined, client: MCPClient | undefined,
data: any,
webview: PostMessageble webview: PostMessageble
) { ) {
if (!client) { if (!client) {
@ -152,7 +155,7 @@ export async function listResourceTemplates(
/** /**
* @description resource * @description resource
*/ */
export async function readResource( export async function readResourceService(
client: MCPClient | undefined, client: MCPClient | undefined,
option: ReadResourceOption, option: ReadResourceOption,
webview: PostMessageble webview: PostMessageble
@ -185,8 +188,9 @@ export async function readResource(
/** /**
* @description * @description
*/ */
export async function listTools( export async function listToolsService(
client: MCPClient | undefined, client: MCPClient | undefined,
data: any,
webview: PostMessageble webview: PostMessageble
) { ) {
if (!client) { if (!client) {
@ -220,7 +224,7 @@ export async function listTools(
/** /**
* @description * @description
*/ */
export async function callTool( export async function callToolService(
client: MCPClient | undefined, client: MCPClient | undefined,
option: CallToolOption, option: CallToolOption,
webview: PostMessageble webview: PostMessageble
@ -254,8 +258,9 @@ export async function callTool(
} }
} }
export async function getServerVersion( export async function getServerVersionService(
client: MCPClient | undefined, client: MCPClient | undefined,
data: any,
webview: PostMessageble webview: PostMessageble
) { ) {
if (!client) { if (!client) {

View File

@ -0,0 +1,18 @@
import { PostMessageble } from "../hook/adapter";
import { MCPClient } from "../hook/client";
export function ocrService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
webview.postMessage({
command: 'ping',
data: {
code: 200,
msg: {}
}
});
}

View File

@ -2,7 +2,7 @@ import { PostMessageble } from '../hook/adapter';
import { loadConfig, loadTabSaveConfig, saveConfig, saveTabSaveConfig } from '../hook/setting'; import { loadConfig, loadTabSaveConfig, saveConfig, saveTabSaveConfig } from '../hook/setting';
import { MCPClient } from '../hook/client'; import { MCPClient } from '../hook/client';
export async function panelSaveHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) { export async function panelSaveService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
try { try {
// 保存配置 // 保存配置
const serverInfo = client?.getServerVersion(); const serverInfo = client?.getServerVersion();
@ -26,7 +26,11 @@ export async function panelSaveHandler(client: MCPClient | undefined, data: any,
} }
} }
export async function panelLoadHandler(client: MCPClient | undefined, webview: PostMessageble) { export async function panelLoadService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
try { try {
// 加载配置 // 加载配置
const serverInfo = client?.getServerVersion(); const serverInfo = client?.getServerVersion();

View File

@ -2,7 +2,11 @@ import { PostMessageble } from '../hook/adapter';
import { loadConfig, saveConfig } from '../hook/setting'; import { loadConfig, saveConfig } from '../hook/setting';
import { MCPClient } from '../hook/client'; import { MCPClient } from '../hook/client';
export async function settingSaveHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) { export async function settingSaveService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
try { try {
// 保存配置 // 保存配置
saveConfig(data); saveConfig(data);
@ -28,7 +32,11 @@ export async function settingSaveHandler(client: MCPClient | undefined, data: an
} }
} }
export async function settingLoadHandler(client: MCPClient | undefined, webview: PostMessageble) { export async function settingLoadService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
try { try {
// 加载配置 // 加载配置
const config = loadConfig(); const config = loadConfig();

View File

@ -1,7 +1,11 @@
import { PostMessageble } from "../hook/adapter"; import { PostMessageble } from "../hook/adapter";
import { MCPClient } from "../hook/client"; import { MCPClient } from "../hook/client";
export function ping(client: MCPClient | undefined, webview: PostMessageble) { export function pingService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
if (!client) { if (!client) {
const connectResult = { const connectResult = {
code: 501, code: 501,
@ -12,7 +16,8 @@ export function ping(client: MCPClient | undefined, webview: PostMessageble) {
} }
webview.postMessage({ webview.postMessage({
command: 'ping', data: { command: 'ping',
data: {
code: 200, code: 200,
msg: {} msg: {}
} }

View File

@ -10,6 +10,11 @@
"declaration": true, // "declaration": true, //
"declarationMap": true // "declarationMap": true //
}, },
"paths": {
"@/*": [
"src/*"
]
},
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "src/main.ts"] // main.ts "exclude": ["node_modules", "src/main.ts"] // main.ts
} }