diff --git a/package.json b/package.json index ddf1a439e4..e5edcdc87d 100644 --- a/package.json +++ b/package.json @@ -73,15 +73,12 @@ "@libsql/client": "0.14.0", "@libsql/win32-x64-msvc": "^0.4.7", "@strongtz/win32-arm64-msvc": "^0.4.7", - "express": "^5.1.0", "graceful-fs": "^4.2.11", "jsdom": "26.1.0", "node-stream-zip": "^1.15.0", "officeparser": "^4.2.0", "os-proxy-config": "^1.1.2", "selection-hook": "^1.0.8", - "swagger-jsdoc": "^6.2.8", - "swagger-ui-express": "^5.0.1", "turndown": "7.2.0" }, "devDependencies": { @@ -145,7 +142,6 @@ "@types/content-type": "^1.1.9", "@types/cors": "^2.8.19", "@types/diff": "^7", - "@types/express": "^5", "@types/fs-extra": "^11", "@types/lodash": "^4.17.5", "@types/markdown-it": "^14", @@ -156,8 +152,6 @@ "@types/react-dom": "^19.0.4", "@types/react-infinite-scroll-component": "^5.0.0", "@types/react-window": "^1", - "@types/swagger-jsdoc": "^6", - "@types/swagger-ui-express": "^4.1.8", "@types/tinycolor2": "^1", "@types/word-extractor": "^1", "@uiw/codemirror-extensions-langs": "^4.23.14", diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index daf6643d78..42ff62db2a 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -273,11 +273,5 @@ export enum IpcChannel { TRACE_SET_TITLE = 'trace:setTitle', TRACE_ADD_END_MESSAGE = 'trace:addEndMessage', TRACE_CLEAN_LOCAL_DATA = 'trace:cleanLocalData', - TRACE_ADD_STREAM_MESSAGE = 'trace:addStreamMessage', - // API Server - ApiServer_Start = 'api-server:start', - ApiServer_Stop = 'api-server:stop', - ApiServer_Restart = 'api-server:restart', - ApiServer_GetStatus = 'api-server:get-status', - ApiServer_GetConfig = 'api-server:get-config' + TRACE_ADD_STREAM_MESSAGE = 'trace:addStreamMessage' } diff --git a/src/main/apiServer/app.ts b/src/main/apiServer/app.ts deleted file mode 100644 index 46da10f876..0000000000 --- a/src/main/apiServer/app.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { loggerService } from '@main/services/LoggerService' -import cors from 'cors' -import express from 'express' -import { v4 as uuidv4 } from 'uuid' - -import { authMiddleware } from './middleware/auth' -import { errorHandler } from './middleware/error' -import { setupOpenAPIDocumentation } from './middleware/openapi' -import { chatRoutes } from './routes/chat' -import { mcpRoutes } from './routes/mcp' -import { modelsRoutes } from './routes/models' - -const logger = loggerService.withContext('ApiServer') - -const app = express() - -// Global middleware -app.use((req, res, next) => { - const start = Date.now() - res.on('finish', () => { - const duration = Date.now() - start - logger.info(`${req.method} ${req.path} - ${res.statusCode} - ${duration}ms`) - }) - next() -}) - -app.use((_req, res, next) => { - res.setHeader('X-Request-ID', uuidv4()) - next() -}) - -app.use( - cors({ - origin: '*', - allowedHeaders: ['Content-Type', 'Authorization'], - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] - }) -) - -/** - * @swagger - * /health: - * get: - * summary: Health check endpoint - * description: Check server status (no authentication required) - * tags: [Health] - * security: [] - * responses: - * 200: - * description: Server is healthy - * content: - * application/json: - * schema: - * type: object - * properties: - * status: - * type: string - * example: ok - * timestamp: - * type: string - * format: date-time - * version: - * type: string - * example: 1.0.0 - */ -app.get('/health', (_req, res) => { - res.json({ - status: 'ok', - timestamp: new Date().toISOString(), - version: process.env.npm_package_version || '1.0.0' - }) -}) - -/** - * @swagger - * /: - * get: - * summary: API information - * description: Get basic API information and available endpoints - * tags: [General] - * security: [] - * responses: - * 200: - * description: API information - * content: - * application/json: - * schema: - * type: object - * properties: - * name: - * type: string - * example: Cherry Studio API - * version: - * type: string - * example: 1.0.0 - * endpoints: - * type: object - */ -app.get('/', (_req, res) => { - res.json({ - name: 'Cherry Studio API', - version: '1.0.0', - endpoints: { - health: 'GET /health', - models: 'GET /v1/models', - chat: 'POST /v1/chat/completions', - mcp: 'GET /v1/mcps' - } - }) -}) - -// API v1 routes with auth -const apiRouter = express.Router() -apiRouter.use(authMiddleware) -apiRouter.use(express.json()) -// Mount routes -apiRouter.use('/chat', chatRoutes) -apiRouter.use('/mcps', mcpRoutes) -apiRouter.use('/models', modelsRoutes) -app.use('/v1', apiRouter) - -// Setup OpenAPI documentation -setupOpenAPIDocumentation(app) - -// Error handling (must be last) -app.use(errorHandler) - -export { app } diff --git a/src/main/apiServer/config.ts b/src/main/apiServer/config.ts deleted file mode 100644 index 15d7a244a3..0000000000 --- a/src/main/apiServer/config.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ApiServerConfig } from '@types' -import { v4 as uuidv4 } from 'uuid' - -import { loggerService } from '../services/LoggerService' -import { reduxService } from '../services/ReduxService' - -const logger = loggerService.withContext('ApiServerConfig') - -class ConfigManager { - private _config: ApiServerConfig | null = null - - async load(): Promise { - try { - const settings = await reduxService.select('state.settings') - - // Auto-generate API key if not set - if (!settings?.apiServer?.apiKey) { - const generatedKey = `cs-sk-${uuidv4()}` - await reduxService.dispatch({ - type: 'settings/setApiServerApiKey', - payload: generatedKey - }) - - this._config = { - enabled: settings?.apiServer?.enabled ?? false, - port: settings?.apiServer?.port ?? 23333, - host: 'localhost', - apiKey: generatedKey - } - } else { - this._config = { - enabled: settings?.apiServer?.enabled ?? false, - port: settings?.apiServer?.port ?? 23333, - host: 'localhost', - apiKey: settings.apiServer.apiKey - } - } - - return this._config - } catch (error: any) { - logger.warn('Failed to load config from Redux, using defaults:', error) - this._config = { - enabled: false, - port: 23333, - host: 'localhost', - apiKey: `cs-sk-${uuidv4()}` - } - return this._config - } - } - - async get(): Promise { - if (!this._config) { - await this.load() - } - if (!this._config) { - throw new Error('Failed to load API server configuration') - } - return this._config - } - - async reload(): Promise { - return await this.load() - } -} - -export const config = new ConfigManager() diff --git a/src/main/apiServer/index.ts b/src/main/apiServer/index.ts deleted file mode 100644 index 765ca05fba..0000000000 --- a/src/main/apiServer/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { config } from './config' -export { apiServer } from './server' diff --git a/src/main/apiServer/middleware/auth.ts b/src/main/apiServer/middleware/auth.ts deleted file mode 100644 index 416bd297ab..0000000000 --- a/src/main/apiServer/middleware/auth.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { NextFunction, Request, Response } from 'express' - -import { config } from '../config' - -export const authMiddleware = async (req: Request, res: Response, next: NextFunction) => { - const auth = req.header('Authorization') - - if (!auth || !auth.startsWith('Bearer ')) { - return res.status(401).json({ error: 'Unauthorized' }) - } - - const token = auth.slice(7) // Remove 'Bearer ' prefix - - if (!token) { - return res.status(401).json({ error: 'Unauthorized, Bearer token is empty' }) - } - - const { apiKey } = await config.get() - - if (token !== apiKey) { - return res.status(403).json({ error: 'Forbidden' }) - } - - return next() -} diff --git a/src/main/apiServer/middleware/error.ts b/src/main/apiServer/middleware/error.ts deleted file mode 100644 index 65eef5e43d..0000000000 --- a/src/main/apiServer/middleware/error.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NextFunction, Request, Response } from 'express' - -import { loggerService } from '../../services/LoggerService' - -const logger = loggerService.withContext('ApiServerErrorHandler') - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const errorHandler = (err: Error, _req: Request, res: Response, _next: NextFunction) => { - logger.error('API Server Error:', err) - - // Don't expose internal errors in production - const isDev = process.env.NODE_ENV === 'development' - - res.status(500).json({ - error: { - message: isDev ? err.message : 'Internal server error', - type: 'server_error', - ...(isDev && { stack: err.stack }) - } - }) -} diff --git a/src/main/apiServer/middleware/openapi.ts b/src/main/apiServer/middleware/openapi.ts deleted file mode 100644 index 691bd8ec96..0000000000 --- a/src/main/apiServer/middleware/openapi.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { Express } from 'express' -import swaggerJSDoc from 'swagger-jsdoc' -import swaggerUi from 'swagger-ui-express' - -import { loggerService } from '../../services/LoggerService' - -const logger = loggerService.withContext('OpenAPIMiddleware') - -const swaggerOptions: swaggerJSDoc.Options = { - definition: { - openapi: '3.0.0', - info: { - title: 'Cherry Studio API', - version: '1.0.0', - description: 'OpenAI-compatible API for Cherry Studio with additional Cherry-specific endpoints', - contact: { - name: 'Cherry Studio', - url: 'https://github.com/CherryHQ/cherry-studio' - } - }, - servers: [ - { - url: 'http://localhost:23333', - description: 'Local development server' - } - ], - components: { - securitySchemes: { - BearerAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT', - description: 'Use the API key from Cherry Studio settings' - } - }, - schemas: { - Error: { - type: 'object', - properties: { - error: { - type: 'object', - properties: { - message: { type: 'string' }, - type: { type: 'string' }, - code: { type: 'string' } - } - } - } - }, - ChatMessage: { - type: 'object', - properties: { - role: { - type: 'string', - enum: ['system', 'user', 'assistant', 'tool'] - }, - content: { - oneOf: [ - { type: 'string' }, - { - type: 'array', - items: { - type: 'object', - properties: { - type: { type: 'string' }, - text: { type: 'string' }, - image_url: { - type: 'object', - properties: { - url: { type: 'string' } - } - } - } - } - } - ] - }, - name: { type: 'string' }, - tool_calls: { - type: 'array', - items: { - type: 'object', - properties: { - id: { type: 'string' }, - type: { type: 'string' }, - function: { - type: 'object', - properties: { - name: { type: 'string' }, - arguments: { type: 'string' } - } - } - } - } - } - } - }, - ChatCompletionRequest: { - type: 'object', - required: ['model', 'messages'], - properties: { - model: { - type: 'string', - description: 'The model to use for completion, in format provider:model-id' - }, - messages: { - type: 'array', - items: { $ref: '#/components/schemas/ChatMessage' } - }, - temperature: { - type: 'number', - minimum: 0, - maximum: 2, - default: 1 - }, - max_tokens: { - type: 'integer', - minimum: 1 - }, - stream: { - type: 'boolean', - default: false - }, - tools: { - type: 'array', - items: { - type: 'object', - properties: { - type: { type: 'string' }, - function: { - type: 'object', - properties: { - name: { type: 'string' }, - description: { type: 'string' }, - parameters: { type: 'object' } - } - } - } - } - } - } - }, - Model: { - type: 'object', - properties: { - id: { type: 'string' }, - object: { type: 'string', enum: ['model'] }, - created: { type: 'integer' }, - owned_by: { type: 'string' } - } - }, - MCPServer: { - type: 'object', - properties: { - id: { type: 'string' }, - name: { type: 'string' }, - command: { type: 'string' }, - args: { - type: 'array', - items: { type: 'string' } - }, - env: { type: 'object' }, - disabled: { type: 'boolean' } - } - } - } - }, - security: [ - { - BearerAuth: [] - } - ] - }, - apis: ['./src/main/apiServer/routes/*.ts', './src/main/apiServer/app.ts'] -} - -export function setupOpenAPIDocumentation(app: Express) { - try { - const specs = swaggerJSDoc(swaggerOptions) - - // Serve OpenAPI JSON - app.get('/api-docs.json', (_req, res) => { - res.setHeader('Content-Type', 'application/json') - res.send(specs) - }) - - // Serve Swagger UI - app.use( - '/api-docs', - swaggerUi.serve, - swaggerUi.setup(specs, { - customCss: ` - .swagger-ui .topbar { display: none; } - .swagger-ui .info .title { color: #1890ff; } - `, - customSiteTitle: 'Cherry Studio API Documentation' - }) - ) - - logger.info('OpenAPI documentation setup complete') - logger.info('Documentation available at /api-docs') - logger.info('OpenAPI spec available at /api-docs.json') - } catch (error) { - logger.error('Failed to setup OpenAPI documentation:', error as Error) - } -} diff --git a/src/main/apiServer/routes/chat.ts b/src/main/apiServer/routes/chat.ts deleted file mode 100644 index 70529f5f35..0000000000 --- a/src/main/apiServer/routes/chat.ts +++ /dev/null @@ -1,225 +0,0 @@ -import express, { Request, Response } from 'express' -import OpenAI from 'openai' -import { ChatCompletionCreateParams } from 'openai/resources' - -import { loggerService } from '../../services/LoggerService' -import { chatCompletionService } from '../services/chat-completion' -import { getProviderByModel, getRealProviderModel } from '../utils' - -const logger = loggerService.withContext('ApiServerChatRoutes') - -const router = express.Router() - -/** - * @swagger - * /v1/chat/completions: - * post: - * summary: Create chat completion - * description: Create a chat completion response, compatible with OpenAI API - * tags: [Chat] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/ChatCompletionRequest' - * responses: - * 200: - * description: Chat completion response - * content: - * application/json: - * schema: - * type: object - * properties: - * id: - * type: string - * object: - * type: string - * example: chat.completion - * created: - * type: integer - * model: - * type: string - * choices: - * type: array - * items: - * type: object - * properties: - * index: - * type: integer - * message: - * $ref: '#/components/schemas/ChatMessage' - * finish_reason: - * type: string - * usage: - * type: object - * properties: - * prompt_tokens: - * type: integer - * completion_tokens: - * type: integer - * total_tokens: - * type: integer - * text/plain: - * schema: - * type: string - * description: Server-sent events stream (when stream=true) - * 400: - * description: Bad request - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/Error' - * 401: - * description: Unauthorized - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/Error' - * 429: - * description: Rate limit exceeded - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/Error' - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/Error' - */ -router.post('/completions', async (req: Request, res: Response) => { - try { - const request: ChatCompletionCreateParams = req.body - - if (!request) { - return res.status(400).json({ - error: { - message: 'Request body is required', - type: 'invalid_request_error', - code: 'missing_body' - } - }) - } - - logger.info('Chat completion request:', { - model: request.model, - messageCount: request.messages?.length || 0, - stream: request.stream - }) - - // Validate request - const validation = chatCompletionService.validateRequest(request) - if (!validation.isValid) { - return res.status(400).json({ - error: { - message: validation.errors.join('; '), - type: 'invalid_request_error', - code: 'validation_failed' - } - }) - } - - // Get provider - const provider = await getProviderByModel(request.model) - if (!provider) { - return res.status(400).json({ - error: { - message: `Model "${request.model}" not found`, - type: 'invalid_request_error', - code: 'model_not_found' - } - }) - } - - // Validate model availability - const modelId = getRealProviderModel(request.model) - const model = provider.models?.find((m) => m.id === modelId) - if (!model) { - return res.status(400).json({ - error: { - message: `Model "${modelId}" not available in provider "${provider.id}"`, - type: 'invalid_request_error', - code: 'model_not_available' - } - }) - } - - // Create OpenAI client - const client = new OpenAI({ - baseURL: provider.apiHost, - apiKey: provider.apiKey - }) - request.model = modelId - - // Handle streaming - if (request.stream) { - const streamResponse = await client.chat.completions.create(request) - - res.setHeader('Content-Type', 'text/plain; charset=utf-8') - res.setHeader('Cache-Control', 'no-cache') - res.setHeader('Connection', 'keep-alive') - - try { - for await (const chunk of streamResponse as any) { - res.write(`data: ${JSON.stringify(chunk)}\n\n`) - } - res.write('data: [DONE]\n\n') - res.end() - } catch (streamError: any) { - logger.error('Stream error:', streamError) - res.write( - `data: ${JSON.stringify({ - error: { - message: 'Stream processing error', - type: 'server_error', - code: 'stream_error' - } - })}\n\n` - ) - res.end() - } - return - } - - // Handle non-streaming - const response = await client.chat.completions.create(request) - return res.json(response) - } catch (error: any) { - logger.error('Chat completion error:', error) - - let statusCode = 500 - let errorType = 'server_error' - let errorCode = 'internal_error' - let errorMessage = 'Internal server error' - - if (error instanceof Error) { - errorMessage = error.message - - if (error.message.includes('API key') || error.message.includes('authentication')) { - statusCode = 401 - errorType = 'authentication_error' - errorCode = 'invalid_api_key' - } else if (error.message.includes('rate limit') || error.message.includes('quota')) { - statusCode = 429 - errorType = 'rate_limit_error' - errorCode = 'rate_limit_exceeded' - } else if (error.message.includes('timeout') || error.message.includes('connection')) { - statusCode = 502 - errorType = 'server_error' - errorCode = 'upstream_error' - } - } - - return res.status(statusCode).json({ - error: { - message: errorMessage, - type: errorType, - code: errorCode - } - }) - } -}) - -export { router as chatRoutes } diff --git a/src/main/apiServer/routes/mcp.ts b/src/main/apiServer/routes/mcp.ts deleted file mode 100644 index 1e154ee583..0000000000 --- a/src/main/apiServer/routes/mcp.ts +++ /dev/null @@ -1,153 +0,0 @@ -import express, { Request, Response } from 'express' - -import { loggerService } from '../../services/LoggerService' -import { mcpApiService } from '../services/mcp' - -const logger = loggerService.withContext('ApiServerMCPRoutes') - -const router = express.Router() - -/** - * @swagger - * /v1/mcps: - * get: - * summary: List MCP servers - * description: Get a list of all configured Model Context Protocol servers - * tags: [MCP] - * responses: - * 200: - * description: List of MCP servers - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * type: array - * items: - * $ref: '#/components/schemas/MCPServer' - * 503: - * description: Service unavailable - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: false - * error: - * $ref: '#/components/schemas/Error' - */ -router.get('/', async (req: Request, res: Response) => { - try { - logger.info('Get all MCP servers request received') - const servers = await mcpApiService.getAllServers(req) - return res.json({ - success: true, - data: servers - }) - } catch (error: any) { - logger.error('Error fetching MCP servers:', error) - return res.status(503).json({ - success: false, - error: { - message: `Failed to retrieve MCP servers: ${error.message}`, - type: 'service_unavailable', - code: 'servers_unavailable' - } - }) - } -}) - -/** - * @swagger - * /v1/mcps/{server_id}: - * get: - * summary: Get MCP server info - * description: Get detailed information about a specific MCP server - * tags: [MCP] - * parameters: - * - in: path - * name: server_id - * required: true - * schema: - * type: string - * description: MCP server ID - * responses: - * 200: - * description: MCP server information - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * data: - * $ref: '#/components/schemas/MCPServer' - * 404: - * description: MCP server not found - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * example: false - * error: - * $ref: '#/components/schemas/Error' - */ -router.get('/:server_id', async (req: Request, res: Response) => { - try { - logger.info('Get MCP server info request received') - const server = await mcpApiService.getServerInfo(req.params.server_id) - if (!server) { - logger.warn('MCP server not found') - return res.status(404).json({ - success: false, - error: { - message: 'MCP server not found', - type: 'not_found', - code: 'server_not_found' - } - }) - } - return res.json({ - success: true, - data: server - }) - } catch (error: any) { - logger.error('Error fetching MCP server info:', error) - return res.status(503).json({ - success: false, - error: { - message: `Failed to retrieve MCP server info: ${error.message}`, - type: 'service_unavailable', - code: 'server_info_unavailable' - } - }) - } -}) - -// Connect to MCP server -router.all('/:server_id/mcp', async (req: Request, res: Response) => { - const server = await mcpApiService.getServerById(req.params.server_id) - if (!server) { - logger.warn('MCP server not found') - return res.status(404).json({ - success: false, - error: { - message: 'MCP server not found', - type: 'not_found', - code: 'server_not_found' - } - }) - } - return await mcpApiService.handleRequest(req, res, server) -}) - -export { router as mcpRoutes } diff --git a/src/main/apiServer/routes/models.ts b/src/main/apiServer/routes/models.ts deleted file mode 100644 index 74d37cc1b7..0000000000 --- a/src/main/apiServer/routes/models.ts +++ /dev/null @@ -1,66 +0,0 @@ -import express, { Request, Response } from 'express' - -import { loggerService } from '../../services/LoggerService' -import { chatCompletionService } from '../services/chat-completion' - -const logger = loggerService.withContext('ApiServerModelsRoutes') - -const router = express.Router() - -/** - * @swagger - * /v1/models: - * get: - * summary: List available models - * description: Returns a list of available AI models from all configured providers - * tags: [Models] - * responses: - * 200: - * description: List of available models - * content: - * application/json: - * schema: - * type: object - * properties: - * object: - * type: string - * example: list - * data: - * type: array - * items: - * $ref: '#/components/schemas/Model' - * 503: - * description: Service unavailable - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/Error' - */ -router.get('/', async (_req: Request, res: Response) => { - try { - logger.info('Models list request received') - - const models = await chatCompletionService.getModels() - - if (models.length === 0) { - logger.warn('No models available from providers') - } - - logger.info(`Returning ${models.length} models`) - return res.json({ - object: 'list', - data: models - }) - } catch (error: any) { - logger.error('Error fetching models:', error) - return res.status(503).json({ - error: { - message: 'Failed to retrieve models', - type: 'service_unavailable', - code: 'models_unavailable' - } - }) - } -}) - -export { router as modelsRoutes } diff --git a/src/main/apiServer/server.ts b/src/main/apiServer/server.ts deleted file mode 100644 index 2555fa8c2e..0000000000 --- a/src/main/apiServer/server.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { createServer } from 'node:http' - -import { loggerService } from '../services/LoggerService' -import { app } from './app' -import { config } from './config' - -const logger = loggerService.withContext('ApiServer') - -export class ApiServer { - private server: ReturnType | null = null - - async start(): Promise { - if (this.server) { - logger.warn('Server already running') - return - } - - // Load config - const { port, host, apiKey } = await config.load() - - // Create server with Express app - this.server = createServer(app) - - // Start server - return new Promise((resolve, reject) => { - this.server!.listen(port, host, () => { - logger.info(`API Server started at http://${host}:${port}`) - logger.info(`API Key: ${apiKey}`) - resolve() - }) - - this.server!.on('error', reject) - }) - } - - async stop(): Promise { - if (!this.server) return - - return new Promise((resolve) => { - this.server!.close(() => { - logger.info('API Server stopped') - this.server = null - resolve() - }) - }) - } - - async restart(): Promise { - await this.stop() - await config.reload() - await this.start() - } - - isRunning(): boolean { - const hasServer = this.server !== null - const isListening = this.server?.listening || false - const result = hasServer && isListening - - logger.debug('isRunning check:', { hasServer, isListening, result }) - - return result - } -} - -export const apiServer = new ApiServer() diff --git a/src/main/apiServer/services/chat-completion.ts b/src/main/apiServer/services/chat-completion.ts deleted file mode 100644 index fe503a2d34..0000000000 --- a/src/main/apiServer/services/chat-completion.ts +++ /dev/null @@ -1,222 +0,0 @@ -import OpenAI from 'openai' -import { ChatCompletionCreateParams } from 'openai/resources' - -import { loggerService } from '../../services/LoggerService' -import { - getProviderByModel, - getRealProviderModel, - listAllAvailableModels, - OpenAICompatibleModel, - transformModelToOpenAI, - validateProvider -} from '../utils' - -const logger = loggerService.withContext('ChatCompletionService') - -export interface ModelData extends OpenAICompatibleModel { - provider_id: string - model_id: string - name: string -} - -export interface ValidationResult { - isValid: boolean - errors: string[] -} - -export class ChatCompletionService { - async getModels(): Promise { - try { - logger.info('Getting available models from providers') - - const models = await listAllAvailableModels() - - const modelData: ModelData[] = models.map((model) => { - const openAIModel = transformModelToOpenAI(model) - return { - ...openAIModel, - provider_id: model.provider, - model_id: model.id, - name: model.name - } - }) - - logger.info(`Successfully retrieved ${modelData.length} models`) - return modelData - } catch (error: any) { - logger.error('Error getting models:', error) - return [] - } - } - - validateRequest(request: ChatCompletionCreateParams): ValidationResult { - const errors: string[] = [] - - // Validate model - if (!request.model) { - errors.push('Model is required') - } else if (typeof request.model !== 'string') { - errors.push('Model must be a string') - } else if (!request.model.includes(':')) { - errors.push('Model must be in format "provider:model_id"') - } - - // Validate messages - if (!request.messages) { - errors.push('Messages array is required') - } else if (!Array.isArray(request.messages)) { - errors.push('Messages must be an array') - } else if (request.messages.length === 0) { - errors.push('Messages array cannot be empty') - } else { - // Validate each message - request.messages.forEach((message, index) => { - if (!message.role) { - errors.push(`Message ${index}: role is required`) - } - if (!message.content) { - errors.push(`Message ${index}: content is required`) - } - }) - } - - // Validate optional parameters - if (request.temperature !== undefined) { - if (typeof request.temperature !== 'number' || request.temperature < 0 || request.temperature > 2) { - errors.push('Temperature must be a number between 0 and 2') - } - } - - if (request.max_tokens !== undefined) { - if (typeof request.max_tokens !== 'number' || request.max_tokens < 1) { - errors.push('max_tokens must be a positive number') - } - } - - return { - isValid: errors.length === 0, - errors - } - } - - async processCompletion(request: ChatCompletionCreateParams): Promise { - try { - logger.info('Processing chat completion request:', { - model: request.model, - messageCount: request.messages.length, - stream: request.stream - }) - - // Validate request - const validation = this.validateRequest(request) - if (!validation.isValid) { - throw new Error(`Request validation failed: ${validation.errors.join(', ')}`) - } - - // Get provider for the model - const provider = await getProviderByModel(request.model!) - if (!provider) { - throw new Error(`Provider not found for model: ${request.model}`) - } - - // Validate provider - if (!validateProvider(provider)) { - throw new Error(`Provider validation failed for: ${provider.id}`) - } - - // Extract model ID from the full model string - const modelId = getRealProviderModel(request.model) - - // Create OpenAI client for the provider - const client = new OpenAI({ - baseURL: provider.apiHost, - apiKey: provider.apiKey - }) - - // Prepare request with the actual model ID - const providerRequest = { - ...request, - model: modelId, - stream: false - } - - logger.debug('Sending request to provider:', { - provider: provider.id, - model: modelId, - apiHost: provider.apiHost - }) - - const response = (await client.chat.completions.create(providerRequest)) as OpenAI.Chat.Completions.ChatCompletion - - logger.info('Successfully processed chat completion') - return response - } catch (error: any) { - logger.error('Error processing chat completion:', error) - throw error - } - } - - async *processStreamingCompletion( - request: ChatCompletionCreateParams - ): AsyncIterable { - try { - logger.info('Processing streaming chat completion request:', { - model: request.model, - messageCount: request.messages.length - }) - - // Validate request - const validation = this.validateRequest(request) - if (!validation.isValid) { - throw new Error(`Request validation failed: ${validation.errors.join(', ')}`) - } - - // Get provider for the model - const provider = await getProviderByModel(request.model!) - if (!provider) { - throw new Error(`Provider not found for model: ${request.model}`) - } - - // Validate provider - if (!validateProvider(provider)) { - throw new Error(`Provider validation failed for: ${provider.id}`) - } - - // Extract model ID from the full model string - const modelId = getRealProviderModel(request.model) - - // Create OpenAI client for the provider - const client = new OpenAI({ - baseURL: provider.apiHost, - apiKey: provider.apiKey - }) - - // Prepare streaming request - const streamingRequest = { - ...request, - model: modelId, - stream: true as const - } - - logger.debug('Sending streaming request to provider:', { - provider: provider.id, - model: modelId, - apiHost: provider.apiHost - }) - - const stream = await client.chat.completions.create(streamingRequest) - - for await (const chunk of stream) { - yield chunk - } - - logger.info('Successfully completed streaming chat completion') - } catch (error: any) { - logger.error('Error processing streaming chat completion:', error) - throw error - } - } -} - -// Export singleton instance -export const chatCompletionService = new ChatCompletionService() diff --git a/src/main/apiServer/services/mcp.ts b/src/main/apiServer/services/mcp.ts deleted file mode 100644 index 99f1732114..0000000000 --- a/src/main/apiServer/services/mcp.ts +++ /dev/null @@ -1,251 +0,0 @@ -import mcpService from '@main/services/MCPService' -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp' -import { - isJSONRPCRequest, - JSONRPCMessage, - JSONRPCMessageSchema, - MessageExtraInfo -} from '@modelcontextprotocol/sdk/types' -import { MCPServer } from '@types' -import { randomUUID } from 'crypto' -import { EventEmitter } from 'events' -import { Request, Response } from 'express' -import { IncomingMessage, ServerResponse } from 'http' - -import { loggerService } from '../../services/LoggerService' -import { reduxService } from '../../services/ReduxService' -import { getMcpServerById } from '../utils/mcp' - -const logger = loggerService.withContext('MCPApiService') -const transports: Record = {} - -interface McpServerDTO { - id: MCPServer['id'] - name: MCPServer['name'] - type: MCPServer['type'] - description: MCPServer['description'] - url: string -} - -interface McpServersResp { - servers: Record -} - -/** - * MCPApiService - API layer for MCP server management - * - * This service provides a REST API interface for MCP servers while integrating - * with the existing application architecture: - * - * 1. Uses ReduxService to access the renderer's Redux store directly - * 2. Syncs changes back to the renderer via Redux actions - * 3. Leverages existing MCPService for actual server connections - * 4. Provides session management for API clients - */ -class MCPApiService extends EventEmitter { - private transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID() - }) - - constructor() { - super() - this.initMcpServer() - logger.silly('MCPApiService initialized') - } - - private initMcpServer() { - this.transport.onmessage = this.onMessage - } - - /** - * Get servers directly from Redux store - */ - private async getServersFromRedux(): Promise { - try { - logger.silly('Getting servers from Redux store') - - // Try to get from cache first (faster) - const cachedServers = reduxService.selectSync('state.mcp.servers') - if (cachedServers && Array.isArray(cachedServers)) { - logger.silly(`Found ${cachedServers.length} servers in Redux cache`) - return cachedServers - } - - // If cache is not available, get fresh data - const servers = await reduxService.select('state.mcp.servers') - logger.silly(`Fetched ${servers?.length || 0} servers from Redux store`) - return servers || [] - } catch (error: any) { - logger.error('Failed to get servers from Redux:', error) - return [] - } - } - - // get all activated servers - async getAllServers(req: Request): Promise { - try { - const servers = await this.getServersFromRedux() - logger.silly(`Returning ${servers.length} servers`) - const resp: McpServersResp = { - servers: {} - } - for (const server of servers) { - if (server.isActive) { - resp.servers[server.id] = { - id: server.id, - name: server.name, - type: 'streamableHttp', - description: server.description, - url: `${req.protocol}://${req.host}/v1/mcps/${server.id}/mcp` - } - } - } - return resp - } catch (error: any) { - logger.error('Failed to get all servers:', error) - throw new Error('Failed to retrieve servers') - } - } - - // get server by id - async getServerById(id: string): Promise { - try { - logger.silly(`getServerById called with id: ${id}`) - const servers = await this.getServersFromRedux() - const server = servers.find((s) => s.id === id) - if (!server) { - logger.warn(`Server with id ${id} not found`) - return null - } - logger.silly(`Returning server with id ${id}`) - return server - } catch (error: any) { - logger.error(`Failed to get server with id ${id}:`, error) - throw new Error('Failed to retrieve server') - } - } - - async getServerInfo(id: string): Promise { - try { - logger.silly(`getServerInfo called with id: ${id}`) - const server = await this.getServerById(id) - if (!server) { - logger.warn(`Server with id ${id} not found`) - return null - } - logger.silly(`Returning server info for id ${id}`) - - const client = await mcpService.initClient(server) - const tools = await client.listTools() - - logger.info(`Server with id ${id} info:`, { tools: JSON.stringify(tools) }) - - // const [version, tools, prompts, resources] = await Promise.all([ - // () => { - // try { - // return client.getServerVersion() - // } catch (error) { - // logger.error(`Failed to get server version for id ${id}:`, { error: error }) - // return '1.0.0' - // } - // }, - // (() => { - // try { - // return client.listTools() - // } catch (error) { - // logger.error(`Failed to list tools for id ${id}:`, { error: error }) - // return [] - // } - // })(), - // (() => { - // try { - // return client.listPrompts() - // } catch (error) { - // logger.error(`Failed to list prompts for id ${id}:`, { error: error }) - // return [] - // } - // })(), - // (() => { - // try { - // return client.listResources() - // } catch (error) { - // logger.error(`Failed to list resources for id ${id}:`, { error: error }) - // return [] - // } - // })() - // ]) - - return { - id: server.id, - name: server.name, - type: server.type, - description: server.description, - tools - } - } catch (error: any) { - logger.error(`Failed to get server info with id ${id}:`, error) - throw new Error('Failed to retrieve server info') - } - } - - async handleRequest(req: Request, res: Response, server: MCPServer) { - const sessionId = req.headers['mcp-session-id'] as string | undefined - logger.silly(`Handling request for server with sessionId ${sessionId}`) - let transport: StreamableHTTPServerTransport - if (sessionId && transports[sessionId]) { - transport = transports[sessionId] - } else { - transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - onsessioninitialized: (sessionId) => { - transports[sessionId] = transport - } - }) - - transport.onclose = () => { - logger.info(`Transport for sessionId ${sessionId} closed`) - if (transport.sessionId) { - delete transports[transport.sessionId] - } - } - const mcpServer = await getMcpServerById(server.id) - if (mcpServer) { - await mcpServer.connect(transport) - } - } - const jsonpayload = req.body - const messages: JSONRPCMessage[] = [] - - if (Array.isArray(jsonpayload)) { - for (const payload of jsonpayload) { - const message = JSONRPCMessageSchema.parse(payload) - messages.push(message) - } - } else { - const message = JSONRPCMessageSchema.parse(jsonpayload) - messages.push(message) - } - - for (const message of messages) { - if (isJSONRPCRequest(message)) { - if (!message.params) { - message.params = {} - } - if (!message.params._meta) { - message.params._meta = {} - } - message.params._meta.serverId = server.id - } - } - - logger.info(`Request body`, { rawBody: req.body, messages: JSON.stringify(messages) }) - await transport.handleRequest(req as IncomingMessage, res as ServerResponse, messages) - } - - private onMessage(message: JSONRPCMessage, extra?: MessageExtraInfo) { - logger.info(`Received message: ${JSON.stringify(message)}`, extra) - // Handle message here - } -} - -export const mcpApiService = new MCPApiService() diff --git a/src/main/apiServer/utils/index.ts b/src/main/apiServer/utils/index.ts deleted file mode 100644 index e53dc95e7b..0000000000 --- a/src/main/apiServer/utils/index.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { loggerService } from '@main/services/LoggerService' -import { reduxService } from '@main/services/ReduxService' -import { Model, Provider } from '@types' - -const logger = loggerService.withContext('ApiServerUtils') - -// OpenAI compatible model format -export interface OpenAICompatibleModel { - id: string - object: 'model' - created: number - owned_by: string -} - -export async function getAvailableProviders(): Promise { - try { - // Wait for store to be ready before accessing providers - const providers = await reduxService.select('state.llm.providers') - if (!providers || !Array.isArray(providers)) { - logger.warn('No providers found in Redux store, returning empty array') - return [] - } - return providers.filter((p: Provider) => p.enabled) - } catch (error: any) { - logger.error('Failed to get providers from Redux store:', error) - return [] - } -} - -export async function listAllAvailableModels(): Promise { - try { - const providers = await getAvailableProviders() - return providers.map((p: Provider) => p.models || []).flat() as Model[] - } catch (error: any) { - logger.error('Failed to list available models:', error) - return [] - } -} - -export async function getProviderByModel(model: string): Promise { - try { - if (!model || typeof model !== 'string') { - logger.warn(`Invalid model parameter: ${model}`) - return undefined - } - - const providers = await getAvailableProviders() - const modelInfo = model.split(':') - - if (modelInfo.length < 2) { - logger.warn(`Invalid model format, expected "provider:model": ${model}`) - return undefined - } - - const providerId = modelInfo[0] - const provider = providers.find((p: Provider) => p.id === providerId) - - if (!provider) { - logger.warn(`Provider not found for model: ${model}`) - return undefined - } - - return provider - } catch (error: any) { - logger.error('Failed to get provider by model:', error) - return undefined - } -} - -export function getRealProviderModel(modelStr: string): string { - return modelStr.split(':').slice(1).join(':') -} - -export function transformModelToOpenAI(model: Model): OpenAICompatibleModel { - return { - id: `${model.provider}:${model.id}`, - object: 'model', - created: Math.floor(Date.now() / 1000), - owned_by: model.owned_by || model.provider - } -} - -export function validateProvider(provider: Provider): boolean { - try { - if (!provider) { - return false - } - - // Check required fields - if (!provider.id || !provider.type || !provider.apiKey || !provider.apiHost) { - logger.warn('Provider missing required fields:', { - id: !!provider.id, - type: !!provider.type, - apiKey: !!provider.apiKey, - apiHost: !!provider.apiHost - }) - return false - } - - // Check if provider is enabled - if (!provider.enabled) { - logger.debug(`Provider is disabled: ${provider.id}`) - return false - } - - return true - } catch (error: any) { - logger.error('Error validating provider:', error) - return false - } -} diff --git a/src/main/apiServer/utils/mcp.ts b/src/main/apiServer/utils/mcp.ts deleted file mode 100644 index 77d7009f40..0000000000 --- a/src/main/apiServer/utils/mcp.ts +++ /dev/null @@ -1,76 +0,0 @@ -import mcpService from '@main/services/MCPService' -import { Server } from '@modelcontextprotocol/sdk/server/index.js' -import { CallToolRequestSchema, ListToolsRequestSchema, ListToolsResult } from '@modelcontextprotocol/sdk/types.js' -import { MCPServer } from '@types' - -import { loggerService } from '../../services/LoggerService' -import { reduxService } from '../../services/ReduxService' - -const logger = loggerService.withContext('MCPApiService') - -const cachedServers: Record = {} - -async function handleListToolsRequest(request: any, extra: any): Promise { - logger.debug('Handling list tools request', { request: request, extra: extra }) - const serverId: string = request.params._meta.serverId - const serverConfig = await getMcpServerConfigById(serverId) - if (!serverConfig) { - throw new Error(`Server not found: ${serverId}`) - } - const client = await mcpService.initClient(serverConfig) - return await client.listTools() -} - -async function handleCallToolRequest(request: any, extra: any): Promise { - logger.debug('Handling call tool request', { request: request, extra: extra }) - const serverId: string = request.params._meta.serverId - const serverConfig = await getMcpServerConfigById(serverId) - if (!serverConfig) { - throw new Error(`Server not found: ${serverId}`) - } - const client = await mcpService.initClient(serverConfig) - return client.callTool(request.params) -} - -async function getMcpServerConfigById(id: string): Promise { - const servers = await getServersFromRedux() - return servers.find((s) => s.id === id || s.name === id) -} - -/** - * Get servers directly from Redux store - */ -async function getServersFromRedux(): Promise { - try { - const servers = await reduxService.select('state.mcp.servers') - logger.silly(`Fetched ${servers?.length || 0} servers from Redux store`) - return servers || [] - } catch (error: any) { - logger.error('Failed to get servers from Redux:', error) - return [] - } -} - -export async function getMcpServerById(id: string): Promise { - const server = cachedServers[id] - if (!server) { - const servers = await getServersFromRedux() - const mcpServer = servers.find((s) => s.id === id || s.name === id) - if (!mcpServer) { - throw new Error(`Server not found: ${id}`) - } - - const createMcpServer = (name: string, version: string): Server => { - const server = new Server({ name: name, version }, { capabilities: { tools: {} } }) - server.setRequestHandler(ListToolsRequestSchema, handleListToolsRequest) - server.setRequestHandler(CallToolRequestSchema, handleCallToolRequest) - return server - } - - const newServer = createMcpServer(mcpServer.name, '0.1.0') - cachedServers[id] = newServer - return newServer - } - logger.silly('getMcpServer ', { server: server }) - return server -} diff --git a/src/main/index.ts b/src/main/index.ts index d724efdbec..5f933bee3d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -27,7 +27,6 @@ import { registerShortcuts } from './services/ShortcutService' import { TrayService } from './services/TrayService' import { windowService } from './services/WindowService' import process from 'node:process' -import { apiServerService } from './services/ApiServerService' const logger = loggerService.withContext('MainEntry') @@ -140,17 +139,6 @@ if (!app.requestSingleInstanceLock()) { //start selection assistant service initSelectionService() - - // Start API server if enabled - try { - const config = await apiServerService.getCurrentConfig() - logger.info('API server config:', config) - if (config.enabled) { - await apiServerService.start() - } - } catch (error: any) { - logger.error('Failed to check/start API server:', error) - } }) registerProtocolClient(app) @@ -196,7 +184,6 @@ if (!app.requestSingleInstanceLock()) { // 简单的资源清理,不阻塞退出流程 try { await mcpService.cleanup() - await apiServerService.stop() } catch (error) { logger.warn('Error cleaning up MCP service:', error as Error) } diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 78bcb5cb84..39f677a6b8 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -13,7 +13,6 @@ import { FileMetadata, Provider, Shortcut, ThemeMode } from '@types' import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron' import { Notification } from 'src/renderer/src/types/notification' -import { apiServerService } from './services/ApiServerService' import appService from './services/AppService' import AppUpdater from './services/AppUpdater' import BackupManager from './services/BackupManager' @@ -696,7 +695,4 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { (_, spanId: string, modelName: string, context: string, msg: any) => addStreamMessage(spanId, modelName, context, msg) ) - - // API Server - apiServerService.registerIpcHandlers() } diff --git a/src/main/services/ApiServerService.ts b/src/main/services/ApiServerService.ts deleted file mode 100644 index 5c0ec63b83..0000000000 --- a/src/main/services/ApiServerService.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { IpcChannel } from '@shared/IpcChannel' -import { ApiServerConfig } from '@types' -import { ipcMain } from 'electron' - -import { apiServer } from '../apiServer' -import { config } from '../apiServer/config' -import { loggerService } from './LoggerService' -const logger = loggerService.withContext('ApiServerService') - -export class ApiServerService { - constructor() { - // Use the new clean implementation - } - - async start(): Promise { - try { - await apiServer.start() - logger.info('API Server started successfully') - } catch (error: any) { - logger.error('Failed to start API Server:', error) - throw error - } - } - - async stop(): Promise { - try { - await apiServer.stop() - logger.info('API Server stopped successfully') - } catch (error: any) { - logger.error('Failed to stop API Server:', error) - throw error - } - } - - async restart(): Promise { - try { - await apiServer.restart() - logger.info('API Server restarted successfully') - } catch (error: any) { - logger.error('Failed to restart API Server:', error) - throw error - } - } - - isRunning(): boolean { - return apiServer.isRunning() - } - - async getCurrentConfig(): Promise { - return await config.get() - } - - registerIpcHandlers(): void { - // API Server - ipcMain.handle(IpcChannel.ApiServer_Start, async () => { - try { - await this.start() - return { success: true } - } catch (error: any) { - return { success: false, error: error instanceof Error ? error.message : 'Unknown error' } - } - }) - - ipcMain.handle(IpcChannel.ApiServer_Stop, async () => { - try { - await this.stop() - return { success: true } - } catch (error: any) { - return { success: false, error: error instanceof Error ? error.message : 'Unknown error' } - } - }) - - ipcMain.handle(IpcChannel.ApiServer_Restart, async () => { - try { - await this.restart() - return { success: true } - } catch (error: any) { - return { success: false, error: error instanceof Error ? error.message : 'Unknown error' } - } - }) - - ipcMain.handle(IpcChannel.ApiServer_GetStatus, async () => { - try { - const config = await this.getCurrentConfig() - return { - running: this.isRunning(), - config - } - } catch (error: any) { - return { - running: this.isRunning(), - config: null - } - } - }) - - ipcMain.handle(IpcChannel.ApiServer_GetConfig, async () => { - try { - return await this.getCurrentConfig() - } catch (error: any) { - return null - } - }) - } -} - -// Export singleton instance -export const apiServerService = new ApiServerService() diff --git a/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/ApiServerSettings.tsx b/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/ApiServerSettings.tsx deleted file mode 100644 index 086c873525..0000000000 --- a/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/ApiServerSettings.tsx +++ /dev/null @@ -1,423 +0,0 @@ -import { useTheme } from '@renderer/context/ThemeProvider' -import { loggerService } from '@renderer/services/LoggerService' -import { RootState, useAppDispatch } from '@renderer/store' -import { setApiServerApiKey, setApiServerEnabled, setApiServerPort } from '@renderer/store/settings' -import { IpcChannel } from '@shared/IpcChannel' -import { Button, Input, InputNumber, Tooltip, Typography } from 'antd' -import { Copy, ExternalLink, Play, RotateCcw, Square } from 'lucide-react' -import { FC, useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import styled from 'styled-components' -import { v4 as uuidv4 } from 'uuid' - -import { SettingContainer } from '../..' - -const logger = loggerService.withContext('ApiServerSettings') -const { Text, Title } = Typography - -const ApiServerSettings: FC = () => { - const { theme } = useTheme() - const dispatch = useAppDispatch() - const { t } = useTranslation() - - // API Server state with proper defaults - const apiServerConfig = useSelector((state: RootState) => state.settings.apiServer) - - const [apiServerRunning, setApiServerRunning] = useState(false) - const [apiServerLoading, setApiServerLoading] = useState(false) - - // API Server functions - const checkApiServerStatus = async () => { - try { - const status = await window.electron.ipcRenderer.invoke(IpcChannel.ApiServer_GetStatus) - setApiServerRunning(status.running) - } catch (error: any) { - logger.error('Failed to check API server status:', error) - } - } - - useEffect(() => { - checkApiServerStatus() - }, []) - - const handleApiServerToggle = async (enabled: boolean) => { - setApiServerLoading(true) - try { - if (enabled) { - const result = await window.electron.ipcRenderer.invoke(IpcChannel.ApiServer_Start) - if (result.success) { - setApiServerRunning(true) - window.message.success(t('apiServer.messages.startSuccess')) - } else { - window.message.error(t('apiServer.messages.startError') + result.error) - } - } else { - const result = await window.electron.ipcRenderer.invoke(IpcChannel.ApiServer_Stop) - if (result.success) { - setApiServerRunning(false) - window.message.success(t('apiServer.messages.stopSuccess')) - } else { - window.message.error(t('apiServer.messages.stopError') + result.error) - } - } - } catch (error) { - window.message.error(t('apiServer.messages.operationFailed') + (error as Error).message) - } finally { - dispatch(setApiServerEnabled(enabled)) - setApiServerLoading(false) - } - } - - const handleApiServerRestart = async () => { - setApiServerLoading(true) - try { - const result = await window.electron.ipcRenderer.invoke(IpcChannel.ApiServer_Restart) - if (result.success) { - await checkApiServerStatus() - window.message.success(t('apiServer.messages.restartSuccess')) - } else { - window.message.error(t('apiServer.messages.restartError') + result.error) - } - } catch (error) { - window.message.error(t('apiServer.messages.restartFailed') + (error as Error).message) - } finally { - setApiServerLoading(false) - } - } - - const copyApiKey = () => { - navigator.clipboard.writeText(apiServerConfig.apiKey) - window.message.success(t('apiServer.messages.apiKeyCopied')) - } - - const regenerateApiKey = () => { - const newApiKey = `cs-sk-${uuidv4()}` - dispatch(setApiServerApiKey(newApiKey)) - window.message.success(t('apiServer.messages.apiKeyRegenerated')) - } - - const handlePortChange = (value: string) => { - const port = parseInt(value) || 23333 - if (port >= 1000 && port <= 65535) { - dispatch(setApiServerPort(port)) - } - } - - const openApiDocs = () => { - if (apiServerRunning) { - window.open(`http://localhost:${apiServerConfig.port}/api-docs`, '_blank') - } - } - - return ( - - {/* Header Section */} - - - - {t('apiServer.title')} - - {t('apiServer.description')} - - {apiServerRunning && ( - - )} - - - {/* Server Control Panel with integrated configuration */} - - - - - - {apiServerRunning ? t('apiServer.status.running') : t('apiServer.status.stopped')} - - - {apiServerRunning ? `http://localhost:${apiServerConfig.port}` : t('apiServer.fields.port.description')} - - - - - - {apiServerRunning && ( - - - - {t('apiServer.actions.restart.button')} - - - )} - - {/* Port input when server is stopped */} - {!apiServerRunning && ( - handlePortChange(String(value || 23333))} - min={1000} - max={65535} - disabled={apiServerRunning} - placeholder="23333" - size="middle" - /> - )} - - - {apiServerRunning ? ( - handleApiServerToggle(false)}> - - - ) : ( - handleApiServerToggle(true)}> - - - )} - - - - - {/* API Key Configuration */} - - {t('apiServer.fields.apiKey.label')} - {t('apiServer.fields.apiKey.description')} - - - {!apiServerRunning && ( - - {t('apiServer.actions.regenerate')} - - )} - - } onClick={copyApiKey} disabled={!apiServerConfig.apiKey} /> - - - } - /> - - {/* Authorization header info */} - - {t('apiServer.authHeader.title')} - - - - - ) -} - -// Styled Components -const Container = styled(SettingContainer)` - display: flex; - flex-direction: column; - height: calc(100vh - var(--navbar-height)); -` - -const HeaderSection = styled.div` - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - margin-bottom: 24px; -` - -const HeaderContent = styled.div` - flex: 1; -` - -const ServerControlPanel = styled.div<{ $status: boolean }>` - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 20px; - border-radius: 8px; - background: var(--color-background); - border: 1px solid ${(props) => (props.$status ? 'var(--color-status-success)' : 'var(--color-border)')}; - transition: all 0.3s ease; - margin-bottom: 16px; -` - -const StatusSection = styled.div` - display: flex; - align-items: center; - gap: 10px; -` - -const StatusIndicator = styled.div<{ $status: boolean }>` - position: relative; - width: 10px; - height: 10px; - border-radius: 50%; - background: ${(props) => (props.$status ? 'var(--color-status-success)' : 'var(--color-status-error)')}; - - &::before { - content: ''; - position: absolute; - inset: -3px; - border-radius: 50%; - background: ${(props) => (props.$status ? 'var(--color-status-success)' : 'var(--color-status-error)')}; - opacity: 0.2; - animation: ${(props) => (props.$status ? 'pulse 2s infinite' : 'none')}; - } - - @keyframes pulse { - 0%, - 100% { - transform: scale(1); - opacity: 0.2; - } - 50% { - transform: scale(1.5); - opacity: 0.1; - } - } -` - -const StatusContent = styled.div` - display: flex; - flex-direction: column; - gap: 2px; -` - -const StatusText = styled.div<{ $status: boolean }>` - font-weight: 600; - font-size: 14px; - color: ${(props) => (props.$status ? 'var(--color-status-success)' : 'var(--color-text-1)')}; - margin: 0; -` - -const StatusSubtext = styled.div` - font-size: 12px; - color: var(--color-text-3); - margin: 0; -` - -const ControlSection = styled.div` - display: flex; - align-items: center; - gap: 12px; -` - -const RestartButton = styled.div<{ $loading: boolean }>` - display: flex; - align-items: center; - gap: 4px; - color: var(--color-text-2); - cursor: ${(props) => (props.$loading ? 'not-allowed' : 'pointer')}; - opacity: ${(props) => (props.$loading ? 0.5 : 1)}; - font-size: 12px; - transition: all 0.2s ease; - - &:hover { - color: ${(props) => (props.$loading ? 'var(--color-text-2)' : 'var(--color-primary)')}; - } -` - -const StyledInputNumber = styled(InputNumber)` - width: 80px; - border-radius: 6px; - border: 1.5px solid var(--color-border); - margin-right: 5px; -` - -const StartButton = styled.div<{ $loading: boolean }>` - display: inline-flex; - align-items: center; - justify-content: center; - cursor: ${(props) => (props.$loading ? 'not-allowed' : 'pointer')}; - opacity: ${(props) => (props.$loading ? 0.5 : 1)}; - transition: all 0.2s ease; - - &:hover { - transform: ${(props) => (props.$loading ? 'scale(1)' : 'scale(1.1)')}; - } -` - -const StopButton = styled.div<{ $loading: boolean }>` - display: inline-flex; - align-items: center; - justify-content: center; - cursor: ${(props) => (props.$loading ? 'not-allowed' : 'pointer')}; - opacity: ${(props) => (props.$loading ? 0.5 : 1)}; - transition: all 0.2s ease; - - &:hover { - transform: ${(props) => (props.$loading ? 'scale(1)' : 'scale(1.1)')}; - } -` - -const ConfigurationField = styled.div` - display: flex; - flex-direction: column; - gap: 8px; - padding: 16px; - background: var(--color-background); - border-radius: 8px; - border: 1px solid var(--color-border); -` - -const FieldLabel = styled.div` - font-size: 14px; - font-weight: 500; - color: var(--color-text-1); - margin: 0; -` - -const FieldDescription = styled.div` - font-size: 12px; - color: var(--color-text-3); - margin: 0; -` - -const StyledInput = styled(Input)` - width: 100%; - border-radius: 6px; - border: 1.5px solid var(--color-border); -` - -const InputButtonContainer = styled.div` - display: flex; - align-items: center; - gap: 4px; -` - -const InputButton = styled(Button)` - border: none; - padding: 0 4px; - background: transparent; -` - -const RegenerateButton = styled(Button)` - padding: 0 4px; - font-size: 12px; - height: auto; - line-height: 1; - border: none; - background: transparent; -` - -const AuthHeaderSection = styled.div` - margin-top: 12px; - display: flex; - flex-direction: column; - gap: 8px; -` - -export default ApiServerSettings diff --git a/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/index.tsx b/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/index.tsx deleted file mode 100644 index fbe9ebe611..0000000000 --- a/src/renderer/src/pages/settings/ToolSettings/ApiServerSettings/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default as ApiServerSettings } from './ApiServerSettings' diff --git a/src/renderer/src/pages/settings/ToolSettings/index.tsx b/src/renderer/src/pages/settings/ToolSettings/index.tsx index 7cc75c2c02..1c42c52309 100644 --- a/src/renderer/src/pages/settings/ToolSettings/index.tsx +++ b/src/renderer/src/pages/settings/ToolSettings/index.tsx @@ -1,12 +1,11 @@ import { GlobalOutlined } from '@ant-design/icons' import { HStack } from '@renderer/components/Layout' import ListItem from '@renderer/components/ListItem' -import { FileCode, Server } from 'lucide-react' +import { FileCode } from 'lucide-react' import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import ApiServerSettings from './ApiServerSettings/ApiServerSettings' import PreprocessSettings from './PreprocessSettings' import WebSearchSettings from './WebSearchSettings' @@ -17,8 +16,7 @@ const ToolSettings: FC = () => { const [menu, setMenu] = useState(_menu) const menuItems = [ { key: 'web-search', title: 'settings.tool.websearch.title', icon: }, - { key: 'preprocess', title: 'settings.tool.preprocess.title', icon: }, - { key: 'api-server', title: 'apiServer.title', icon: } + { key: 'preprocess', title: 'settings.tool.preprocess.title', icon: } ] _menu = menu @@ -39,7 +37,6 @@ const ToolSettings: FC = () => { {menu == 'web-search' && } {menu == 'preprocess' && } - {menu == 'api-server' && } ) } diff --git a/yarn.lock b/yarn.lock index 9bb58e0422..e913ad807b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -244,48 +244,6 @@ __metadata: languageName: node linkType: hard -"@apidevtools/json-schema-ref-parser@npm:^9.0.6": - version: 9.1.2 - resolution: "@apidevtools/json-schema-ref-parser@npm:9.1.2" - dependencies: - "@jsdevtools/ono": "npm:^7.1.3" - "@types/json-schema": "npm:^7.0.6" - call-me-maybe: "npm:^1.0.1" - js-yaml: "npm:^4.1.0" - checksum: 10c0/ebf952eb2e00bf0919f024e72897e047fd5012f0a9e47ac361873f6de0a733b9334513cdbc73205a6b43ac4a652b8c87f55e489c39b2d60bd0bc1cb2b411e218 - languageName: node - linkType: hard - -"@apidevtools/openapi-schemas@npm:^2.0.4": - version: 2.1.0 - resolution: "@apidevtools/openapi-schemas@npm:2.1.0" - checksum: 10c0/f4aa0f9df32e474d166c84ef91bceb18fa1c4f44b5593879529154ef340846811ea57dc2921560f157f692262827d28d988dd6e19fb21f00320e9961964176b4 - languageName: node - linkType: hard - -"@apidevtools/swagger-methods@npm:^3.0.2": - version: 3.0.2 - resolution: "@apidevtools/swagger-methods@npm:3.0.2" - checksum: 10c0/8c390e8e50c0be7787ba0ba4c3758488bde7c66c2d995209b4b48c1f8bc988faf393cbb24a4bd1cd2d42ce5167c26538e8adea5c85eb922761b927e4dab9fa1c - languageName: node - linkType: hard - -"@apidevtools/swagger-parser@npm:10.0.3": - version: 10.0.3 - resolution: "@apidevtools/swagger-parser@npm:10.0.3" - dependencies: - "@apidevtools/json-schema-ref-parser": "npm:^9.0.6" - "@apidevtools/openapi-schemas": "npm:^2.0.4" - "@apidevtools/swagger-methods": "npm:^3.0.2" - "@jsdevtools/ono": "npm:^7.1.3" - call-me-maybe: "npm:^1.0.1" - z-schema: "npm:^5.0.1" - peerDependencies: - openapi-types: ">=7" - checksum: 10c0/3b43f719c2d647ac8dcf30f132834d413ce21cbf7a8d9c3b35ec91149dd25d608c8fd892358fcd61a8edd8c5140a7fb13676f948e2d87067d081a47b8c7107e9 - languageName: node - linkType: hard - "@asamuzakjp/css-color@npm:^3.1.1": version: 3.1.2 resolution: "@asamuzakjp/css-color@npm:3.1.2" @@ -3140,13 +3098,6 @@ __metadata: languageName: node linkType: hard -"@jsdevtools/ono@npm:^7.1.3": - version: 7.1.3 - resolution: "@jsdevtools/ono@npm:7.1.3" - checksum: 10c0/a9f7e3e8e3bc315a34959934a5e2f874c423cf4eae64377d3fc9de0400ed9f36cb5fd5ebce3300d2e8f4085f557c4a8b591427a583729a87841fda46e6c216b9 - languageName: node - linkType: hard - "@kangfenmao/keyv-storage@npm:^0.1.0": version: 0.1.0 resolution: "@kangfenmao/keyv-storage@npm:0.1.0" @@ -4962,13 +4913,6 @@ __metadata: languageName: node linkType: hard -"@scarf/scarf@npm:=1.4.0": - version: 1.4.0 - resolution: "@scarf/scarf@npm:1.4.0" - checksum: 10c0/332118bb488e7a70eaad068fb1a33f016d30442fb0498b37a80cb425c1e741853a5de1a04dce03526ed6265481ecf744aa6e13f072178d19e6b94b19f623ae1c - languageName: node - linkType: hard - "@selderee/plugin-htmlparser2@npm:^0.11.0": version: 0.11.0 resolution: "@selderee/plugin-htmlparser2@npm:0.11.0" @@ -6123,16 +6067,6 @@ __metadata: languageName: node linkType: hard -"@types/body-parser@npm:*": - version: 1.19.6 - resolution: "@types/body-parser@npm:1.19.6" - dependencies: - "@types/connect": "npm:*" - "@types/node": "npm:*" - checksum: 10c0/542da05c924dce58ee23f50a8b981fee36921850c82222e384931fda3e106f750f7880c47be665217d72dbe445129049db6eb1f44e7a06b09d62af8f3cca8ea7 - languageName: node - linkType: hard - "@types/cacheable-request@npm:^6.0.1": version: 6.0.3 resolution: "@types/cacheable-request@npm:6.0.3" @@ -6163,15 +6097,6 @@ __metadata: languageName: node linkType: hard -"@types/connect@npm:*": - version: 3.4.38 - resolution: "@types/connect@npm:3.4.38" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c - languageName: node - linkType: hard - "@types/content-type@npm:^1.1.9": version: 1.1.9 resolution: "@types/content-type@npm:1.1.9" @@ -6499,29 +6424,6 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:^5.0.0": - version: 5.0.7 - resolution: "@types/express-serve-static-core@npm:5.0.7" - dependencies: - "@types/node": "npm:*" - "@types/qs": "npm:*" - "@types/range-parser": "npm:*" - "@types/send": "npm:*" - checksum: 10c0/28666f6a0743b8678be920a6eed075bc8afc96fc7d8ef59c3c049bd6b51533da3b24daf3b437d061e053fba1475e4f3175cb4972f5e8db41608e817997526430 - languageName: node - linkType: hard - -"@types/express@npm:*, @types/express@npm:^5": - version: 5.0.3 - resolution: "@types/express@npm:5.0.3" - dependencies: - "@types/body-parser": "npm:*" - "@types/express-serve-static-core": "npm:^5.0.0" - "@types/serve-static": "npm:*" - checksum: 10c0/f0fbc8daa7f40070b103cf4d020ff1dd08503477d866d1134b87c0390bba71d5d7949cb8b4e719a81ccba89294d8e1573414e6dcbb5bb1d097a7b820928ebdef - languageName: node - linkType: hard - "@types/fs-extra@npm:9.0.13, @types/fs-extra@npm:^9.0.11": version: 9.0.13 resolution: "@types/fs-extra@npm:9.0.13" @@ -6574,14 +6476,7 @@ __metadata: languageName: node linkType: hard -"@types/http-errors@npm:*": - version: 2.0.5 - resolution: "@types/http-errors@npm:2.0.5" - checksum: 10c0/00f8140fbc504f47356512bd88e1910c2f07e04233d99c88c854b3600ce0523c8cd0ba7d1897667243282eb44c59abb9245959e2428b9de004f93937f52f7c15 - languageName: node - linkType: hard - -"@types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.6": +"@types/json-schema@npm:^7.0.15": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db @@ -6676,13 +6571,6 @@ __metadata: languageName: node linkType: hard -"@types/mime@npm:^1": - version: 1.3.5 - resolution: "@types/mime@npm:1.3.5" - checksum: 10c0/c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc - languageName: node - linkType: hard - "@types/ms@npm:*": version: 2.1.0 resolution: "@types/ms@npm:2.1.0" @@ -6735,20 +6623,6 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*": - version: 6.14.0 - resolution: "@types/qs@npm:6.14.0" - checksum: 10c0/5b3036df6e507483869cdb3858201b2e0b64b4793dc4974f188caa5b5732f2333ab9db45c08157975054d3b070788b35088b4bc60257ae263885016ee2131310 - languageName: node - linkType: hard - -"@types/range-parser@npm:*": - version: 1.2.7 - resolution: "@types/range-parser@npm:1.2.7" - checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c - languageName: node - linkType: hard - "@types/react-dom@npm:^19.0.4": version: 19.1.2 resolution: "@types/react-dom@npm:19.1.2" @@ -6801,27 +6675,6 @@ __metadata: languageName: node linkType: hard -"@types/send@npm:*": - version: 0.17.5 - resolution: "@types/send@npm:0.17.5" - dependencies: - "@types/mime": "npm:^1" - "@types/node": "npm:*" - checksum: 10c0/a86c9b89bb0976ff58c1cdd56360ea98528f4dbb18a5c2287bb8af04815513a576a42b4e0e1e7c4d14f7d6ea54733f6ef935ebff8c65e86d9c222881a71e1f15 - languageName: node - linkType: hard - -"@types/serve-static@npm:*": - version: 1.15.8 - resolution: "@types/serve-static@npm:1.15.8" - dependencies: - "@types/http-errors": "npm:*" - "@types/node": "npm:*" - "@types/send": "npm:*" - checksum: 10c0/8ad86a25b87da5276cb1008c43c74667ff7583904d46d5fcaf0355887869d859d453d7dc4f890788ae04705c23720e9b6b6f3215e2d1d2a4278bbd090a9268dd - languageName: node - linkType: hard - "@types/stylis@npm:4.2.5": version: 4.2.5 resolution: "@types/stylis@npm:4.2.5" @@ -6829,23 +6682,6 @@ __metadata: languageName: node linkType: hard -"@types/swagger-jsdoc@npm:^6": - version: 6.0.4 - resolution: "@types/swagger-jsdoc@npm:6.0.4" - checksum: 10c0/fbe17d91a12e1e60a255b02e6def6877c81b356c75ffcd0e5167fbaf1476e2d6600cd7eea79e6b3e0ff7929dec33ade345147509ed3b98026f63c782b74514f6 - languageName: node - linkType: hard - -"@types/swagger-ui-express@npm:^4.1.8": - version: 4.1.8 - resolution: "@types/swagger-ui-express@npm:4.1.8" - dependencies: - "@types/express": "npm:*" - "@types/serve-static": "npm:*" - checksum: 10c0/9c9e8327c40376b98b6fbd5dd2d722b7b5473e5c168af809431f16b34c948d2d3d44ce2157d2066355e9926ba86416481a30cd1cbcdbb064dd2fedb28442a85a - languageName: node - linkType: hard - "@types/tinycolor2@npm:^1": version: 1.4.6 resolution: "@types/tinycolor2@npm:1.4.6" @@ -7871,7 +7707,6 @@ __metadata: "@types/content-type": "npm:^1.1.9" "@types/cors": "npm:^2.8.19" "@types/diff": "npm:^7" - "@types/express": "npm:^5" "@types/fs-extra": "npm:^11" "@types/lodash": "npm:^4.17.5" "@types/markdown-it": "npm:^14" @@ -7882,8 +7717,6 @@ __metadata: "@types/react-dom": "npm:^19.0.4" "@types/react-infinite-scroll-component": "npm:^5.0.0" "@types/react-window": "npm:^1" - "@types/swagger-jsdoc": "npm:^6" - "@types/swagger-ui-express": "npm:^4.1.8" "@types/tinycolor2": "npm:^1" "@types/word-extractor": "npm:^1" "@uiw/codemirror-extensions-langs": "npm:^4.23.14" @@ -7927,7 +7760,6 @@ __metadata: eslint-plugin-react-hooks: "npm:^5.2.0" eslint-plugin-simple-import-sort: "npm:^12.1.1" eslint-plugin-unused-imports: "npm:^4.1.4" - express: "npm:^5.1.0" fast-diff: "npm:^1.3.0" fast-xml-parser: "npm:^5.2.0" fetch-socks: "npm:1.3.2" @@ -7994,8 +7826,6 @@ __metadata: strict-url-sanitise: "npm:^0.0.1" string-width: "npm:^7.2.0" styled-components: "npm:^6.1.11" - swagger-jsdoc: "npm:^6.2.8" - swagger-ui-express: "npm:^5.0.1" tar: "npm:^7.4.3" tiny-pinyin: "npm:^1.3.2" tokenx: "npm:^1.1.0" @@ -9052,13 +8882,6 @@ __metadata: languageName: node linkType: hard -"call-me-maybe@npm:^1.0.1": - version: 1.0.2 - resolution: "call-me-maybe@npm:1.0.2" - checksum: 10c0/8eff5dbb61141ebb236ed71b4e9549e488bcb5451c48c11e5667d5c75b0532303788a1101e6978cafa2d0c8c1a727805599c2741e3e0982855c9f1d78cd06c9f - languageName: node - linkType: hard - "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -9586,13 +9409,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:6.2.0": - version: 6.2.0 - resolution: "commander@npm:6.2.0" - checksum: 10c0/1b701c6726fc2b6c6a7d9ab017be9465153546a05767cdd0e15e9f9a11c07f88f64d47684b90b07e5fb103d173efb6afdf4a21f6d6c4c25f7376bd027d21062c - languageName: node - linkType: hard - "commander@npm:7": version: 7.2.0 resolution: "commander@npm:7.2.0" @@ -9621,13 +9437,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^9.4.1": - version: 9.5.0 - resolution: "commander@npm:9.5.0" - checksum: 10c0/5f7784fbda2aaec39e89eb46f06a999e00224b3763dc65976e05929ec486e174fe9aac2655f03ba6a5e83875bd173be5283dc19309b7c65954701c02025b3c1d - languageName: node - linkType: hard - "compare-version@npm:^0.1.2": version: 0.1.2 resolution: "compare-version@npm:0.1.2" @@ -10763,15 +10572,6 @@ __metadata: languageName: node linkType: hard -"doctrine@npm:3.0.0": - version: 3.0.0 - resolution: "doctrine@npm:3.0.0" - dependencies: - esutils: "npm:^2.0.2" - checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 - languageName: node - linkType: hard - "docx@npm:^9.0.2": version: 9.3.0 resolution: "docx@npm:9.3.0" @@ -11912,7 +11712,7 @@ __metadata: languageName: node linkType: hard -"express@npm:^5.0.1, express@npm:^5.1.0": +"express@npm:^5.0.1": version: 5.1.0 resolution: "express@npm:5.1.0" dependencies: @@ -12775,20 +12575,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:7.1.6": - version: 7.1.6 - resolution: "glob@npm:7.1.6" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.0.4" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10c0/2575cce9306ac534388db751f0aa3e78afedb6af8f3b529ac6b2354f66765545145dba8530abf7bff49fb399a047d3f9b6901c38ee4c9503f592960d9af67763 - languageName: node - linkType: hard - "glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.12, glob@npm:^10.3.7, glob@npm:^10.4.1": version: 10.4.5 resolution: "glob@npm:10.4.5" @@ -14727,13 +14513,6 @@ __metadata: languageName: node linkType: hard -"lodash.get@npm:^4.4.2": - version: 4.4.2 - resolution: "lodash.get@npm:4.4.2" - checksum: 10c0/48f40d471a1654397ed41685495acb31498d5ed696185ac8973daef424a749ca0c7871bf7b665d5c14f5cc479394479e0307e781f61d5573831769593411be6e - languageName: node - linkType: hard - "lodash.isequal@npm:^4.5.0": version: 4.5.0 resolution: "lodash.isequal@npm:4.5.0" @@ -14748,13 +14527,6 @@ __metadata: languageName: node linkType: hard -"lodash.mergewith@npm:^4.6.2": - version: 4.6.2 - resolution: "lodash.mergewith@npm:4.6.2" - checksum: 10c0/4adbed65ff96fd65b0b3861f6899f98304f90fd71e7f1eb36c1270e05d500ee7f5ec44c02ef979b5ddbf75c0a0b9b99c35f0ad58f4011934c4d4e99e5200b3b5 - languageName: node - linkType: hard - "lodash@npm:^4.17.15, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -20212,51 +19984,6 @@ __metadata: languageName: node linkType: hard -"swagger-jsdoc@npm:^6.2.8": - version: 6.2.8 - resolution: "swagger-jsdoc@npm:6.2.8" - dependencies: - commander: "npm:6.2.0" - doctrine: "npm:3.0.0" - glob: "npm:7.1.6" - lodash.mergewith: "npm:^4.6.2" - swagger-parser: "npm:^10.0.3" - yaml: "npm:2.0.0-1" - bin: - swagger-jsdoc: bin/swagger-jsdoc.js - checksum: 10c0/7e20f08e8d90cc1e787cd82c096291cf12533359f89c70fbe4295a01f7c4734f2e82a03ba94027127bcd3da04b817abfe979f00d00ef0cd8283e449250a66215 - languageName: node - linkType: hard - -"swagger-parser@npm:^10.0.3": - version: 10.0.3 - resolution: "swagger-parser@npm:10.0.3" - dependencies: - "@apidevtools/swagger-parser": "npm:10.0.3" - checksum: 10c0/d1a5c05f651f21a23508a36416071630b83e91dfffd52a6d44b06ca2cd1b86304c0dd2f4c04526c999b70062fa89bde3f5d54a1436626f4350590b6c6265a098 - languageName: node - linkType: hard - -"swagger-ui-dist@npm:>=5.0.0": - version: 5.27.0 - resolution: "swagger-ui-dist@npm:5.27.0" - dependencies: - "@scarf/scarf": "npm:=1.4.0" - checksum: 10c0/8f50e67b9b92a6953f28954cda137af82550ab236813c984a6f71ec314e14121a960187e62fd5db42fc1a5763d7e687ad990f4f921c7e96d3e02b06bf631d8ae - languageName: node - linkType: hard - -"swagger-ui-express@npm:^5.0.1": - version: 5.0.1 - resolution: "swagger-ui-express@npm:5.0.1" - dependencies: - swagger-ui-dist: "npm:>=5.0.0" - peerDependencies: - express: ">=4.0.0 || >=5.0.0-beta" - checksum: 10c0/dbe9830caef7fe455241e44e74958bac62642997e4341c1b0f38a3d684d19a4a81b431217c656792d99f046a1b5f261abf7783ede0afe41098cd4450401f6fd1 - languageName: node - linkType: hard - "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4" @@ -21258,13 +20985,6 @@ __metadata: languageName: node linkType: hard -"validator@npm:^13.7.0": - version: 13.15.15 - resolution: "validator@npm:13.15.15" - checksum: 10c0/f5349d1fbb9cc36f9f6c5dab1880764ddad1d0d2b084e2a71e5964f7de1635d20e406611559df9a3db24828ce775cbee5e3b6dd52f0d555a61939ed7ea5990bd - languageName: node - linkType: hard - "vary@npm:^1, vary@npm:^1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -21981,13 +21701,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:2.0.0-1": - version: 2.0.0-1 - resolution: "yaml@npm:2.0.0-1" - checksum: 10c0/e76eba2fbae37cd3e5bff057184be7cdca849895149d2f5660386871a501d76d2e1ec5906c48269a9fe798f214df31d342675b37bcd9d09af7c12eb6fb46a740 - languageName: node - linkType: hard - "yaml@npm:^2.2.1, yaml@npm:^2.7.0": version: 2.7.1 resolution: "yaml@npm:2.7.1" @@ -22036,23 +21749,6 @@ __metadata: languageName: node linkType: hard -"z-schema@npm:^5.0.1": - version: 5.0.5 - resolution: "z-schema@npm:5.0.5" - dependencies: - commander: "npm:^9.4.1" - lodash.get: "npm:^4.4.2" - lodash.isequal: "npm:^4.5.0" - validator: "npm:^13.7.0" - dependenciesMeta: - commander: - optional: true - bin: - z-schema: bin/z-schema - checksum: 10c0/e4c812cfe6468c19b2a21d07d4ff8fb70359062d33400b45f89017eaa3efe9d51e85963f2b115eaaa99a16b451782249bf9b1fa8b31d35cc473e7becb3e44264 - languageName: node - linkType: hard - "zip-stream@npm:^6.0.1": version: 6.0.1 resolution: "zip-stream@npm:6.0.1"