diff --git a/dist-api/ai-GLAEBMJM.js b/dist-api/ai-GLAEBMJM.js deleted file mode 100644 index b1cb65c..0000000 --- a/dist-api/ai-GLAEBMJM.js +++ /dev/null @@ -1,12 +0,0 @@ -import { - ai_default, - createAiModule, - createAiRoutes -} from "./chunk-TSQNCXAS.js"; -import "./chunk-ER4KPD22.js"; -import "./chunk-74TMTGBG.js"; -export { - createAiModule, - createAiRoutes, - ai_default as default -}; diff --git a/dist-api/chunk-47DJ6YUB.js b/dist-api/chunk-47DJ6YUB.js deleted file mode 100644 index ad0a3c9..0000000 --- a/dist-api/chunk-47DJ6YUB.js +++ /dev/null @@ -1,15 +0,0 @@ -// api/utils/logger.ts -var createLogger = () => { - const isProd = process.env.NODE_ENV === "production"; - const debug = isProd ? () => { - } : console.debug.bind(console); - const info = console.info.bind(console); - const warn = console.warn.bind(console); - const error = console.error.bind(console); - return { debug, info, warn, error }; -}; -var logger = createLogger(); - -export { - logger -}; diff --git a/dist-api/chunk-5EGA6GHY.js b/dist-api/chunk-5EGA6GHY.js deleted file mode 100644 index 8a27427..0000000 --- a/dist-api/chunk-5EGA6GHY.js +++ /dev/null @@ -1,39 +0,0 @@ -import { - ValidationError -} from "./chunk-ER4KPD22.js"; - -// api/middlewares/validate.ts -import { ZodError } from "zod"; -var validateBody = (schema) => { - return (req, _res, next) => { - try { - req.body = schema.parse(req.body); - next(); - } catch (error) { - if (error instanceof ZodError) { - next(new ValidationError("Request validation failed", { issues: error.issues })); - } else { - next(error); - } - } - }; -}; -var validateQuery = (schema) => { - return (req, _res, next) => { - try { - req.query = schema.parse(req.query); - next(); - } catch (error) { - if (error instanceof ZodError) { - next(new ValidationError("Query validation failed", { issues: error.issues })); - } else { - next(error); - } - } - }; -}; - -export { - validateBody, - validateQuery -}; diff --git a/dist-api/chunk-74TMTGBG.js b/dist-api/chunk-74TMTGBG.js deleted file mode 100644 index d06f109..0000000 --- a/dist-api/chunk-74TMTGBG.js +++ /dev/null @@ -1,110 +0,0 @@ -var __glob = (map) => (path2) => { - var fn = map[path2]; - if (fn) return fn(); - throw new Error("Module not found in bundle: " + path2); -}; - -// api/infra/createModule.ts -function createApiModule(config2, options) { - const metadata = { - id: config2.id, - name: config2.name, - version: config2.version, - basePath: config2.basePath, - order: config2.order, - dependencies: config2.dependencies - }; - const lifecycle = options.lifecycle ? options.lifecycle : options.services ? { - onLoad: options.services - } : void 0; - return { - metadata, - lifecycle, - createRouter: options.routes - }; -} - -// api/utils/asyncHandler.ts -var asyncHandler = (fn) => (req, res, next) => { - Promise.resolve(fn(req, res, next)).catch(next); -}; - -// api/utils/response.ts -var successResponse = (res, data, statusCode = 200) => { - const response = { - success: true, - data - }; - res.status(statusCode).json(response); -}; - -// api/config/index.ts -import path from "path"; -import { fileURLToPath } from "url"; -import os from "os"; -var __filename = fileURLToPath(import.meta.url); -var __dirname = path.dirname(__filename); -var config = { - get projectRoot() { - if (__dirname.includes("app.asar")) { - return path.resolve(__dirname, "..").replace("app.asar", "app.asar.unpacked"); - } - return path.resolve(__dirname, "../../"); - }, - get notebookRoot() { - return process.env.NOTEBOOK_ROOT ? path.resolve(process.env.NOTEBOOK_ROOT) : path.join(this.projectRoot, "notebook"); - }, - get tempRoot() { - return path.join(os.tmpdir(), "xcdesktop_uploads"); - }, - get serverPort() { - return parseInt(process.env.PORT || "3001", 10); - }, - get isVercel() { - return !!process.env.VERCEL; - }, - get isElectron() { - return __dirname.includes("app.asar"); - }, - get isDev() { - return !this.isElectron && !this.isVercel; - } -}; -var PATHS = { - get PROJECT_ROOT() { - return config.projectRoot; - }, - get NOTEBOOK_ROOT() { - return config.notebookRoot; - }, - get TEMP_ROOT() { - return config.tempRoot; - } -}; - -// api/config/paths.ts -var PROJECT_ROOT = PATHS.PROJECT_ROOT; -var NOTEBOOK_ROOT = PATHS.NOTEBOOK_ROOT; -var TEMP_ROOT = PATHS.TEMP_ROOT; - -// shared/modules/types.ts -function defineApiModule(config2) { - return config2; -} -function defineEndpoints(endpoints) { - return endpoints; -} - -export { - __glob, - asyncHandler, - successResponse, - config, - PATHS, - PROJECT_ROOT, - NOTEBOOK_ROOT, - TEMP_ROOT, - createApiModule, - defineApiModule, - defineEndpoints -}; diff --git a/dist-api/chunk-ER4KPD22.js b/dist-api/chunk-ER4KPD22.js deleted file mode 100644 index c8a2458..0000000 --- a/dist-api/chunk-ER4KPD22.js +++ /dev/null @@ -1,163 +0,0 @@ -import { - NOTEBOOK_ROOT -} from "./chunk-74TMTGBG.js"; - -// shared/errors/index.ts -var AppError = class extends Error { - constructor(code, message, statusCode = 500, details) { - super(message); - this.code = code; - this.name = "AppError"; - this.statusCode = statusCode; - this.details = details; - } - statusCode; - details; - toJSON() { - return { - name: this.name, - code: this.code, - message: this.message, - statusCode: this.statusCode, - details: this.details - }; - } -}; -var ValidationError = class extends AppError { - constructor(message, details) { - super("VALIDATION_ERROR", message, 400, details); - this.name = "ValidationError"; - } -}; -var NotFoundError = class extends AppError { - constructor(message = "Resource not found", details) { - super("NOT_FOUND", message, 404, details); - this.name = "NotFoundError"; - } -}; -var AccessDeniedError = class extends AppError { - constructor(message = "Access denied", details) { - super("ACCESS_DENIED", message, 403, details); - this.name = "AccessDeniedError"; - } -}; -var BadRequestError = class extends AppError { - constructor(message, details) { - super("BAD_REQUEST", message, 400, details); - this.name = "BadRequestError"; - } -}; -var NotADirectoryError = class extends AppError { - constructor(message = "\u4E0D\u662F\u76EE\u5F55", details) { - super("NOT_A_DIRECTORY", message, 400, details); - this.name = "NotADirectoryError"; - } -}; -var AlreadyExistsError = class extends AppError { - constructor(message = "Resource already exists", details) { - super("ALREADY_EXISTS", message, 409, details); - this.name = "AlreadyExistsError"; - } -}; -var ForbiddenError = class extends AppError { - constructor(message = "\u7981\u6B62\u8BBF\u95EE", details) { - super("FORBIDDEN", message, 403, details); - this.name = "ForbiddenError"; - } -}; -var UnsupportedMediaTypeError = class extends AppError { - constructor(message = "\u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B", details) { - super("UNSUPPORTED_MEDIA_TYPE", message, 415, details); - this.name = "UnsupportedMediaTypeError"; - } -}; -var ResourceLockedError = class extends AppError { - constructor(message = "\u8D44\u6E90\u5DF2\u9501\u5B9A", details) { - super("RESOURCE_LOCKED", message, 423, details); - this.name = "ResourceLockedError"; - } -}; -var InternalError = class extends AppError { - constructor(message = "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF", details) { - super("INTERNAL_ERROR", message, 500, details); - this.name = "InternalError"; - } -}; -function isAppError(error) { - return error instanceof AppError; -} -function isNodeError(error) { - return error instanceof Error && "code" in error; -} - -// api/utils/pathSafety.ts -import path from "path"; -var DANGEROUS_PATTERNS = [ - /\.\./, - /\0/, - /%2e%2e[%/]/i, - /%252e%252e[%/]/i, - /\.\.%2f/i, - /\.\.%5c/i, - /%c0%ae/i, - /%c1%9c/i, - /%c0%ae%c0%ae/i, - /%c1%9c%c1%9c/i, - /\.\.%c0%af/i, - /\.\.%c1%9c/i, - /%252e/i, - /%uff0e/i, - /%u002e/i -]; -var DOUBLE_ENCODE_PATTERNS = [ - /%25[0-9a-fA-F]{2}/, - /%[0-9a-fA-F]{2}%[0-9a-fA-F]{2}/ -]; -var normalizeRelPath = (input) => { - const trimmed = input.replace(/\0/g, "").trim(); - return trimmed.replace(/^[/\\]+/, ""); -}; -var containsPathTraversal = (input) => { - const decoded = decodeURIComponentSafe(input); - return DANGEROUS_PATTERNS.some((pattern) => pattern.test(input) || pattern.test(decoded)); -}; -var containsDoubleEncoding = (input) => { - return DOUBLE_ENCODE_PATTERNS.some((pattern) => pattern.test(input)); -}; -var hasPathSecurityIssues = (input) => { - return containsPathTraversal(input) || containsDoubleEncoding(input); -}; -var decodeURIComponentSafe = (input) => { - try { - return decodeURIComponent(input); - } catch { - return input; - } -}; -var resolveNotebookPath = (relPath) => { - if (hasPathSecurityIssues(relPath)) { - throw new AccessDeniedError("Path traversal detected"); - } - const safeRelPath = normalizeRelPath(relPath); - const notebookRoot = path.resolve(NOTEBOOK_ROOT); - const fullPath = path.resolve(notebookRoot, safeRelPath); - if (!fullPath.startsWith(notebookRoot)) { - throw new AccessDeniedError("Access denied"); - } - return { safeRelPath, fullPath }; -}; - -export { - ValidationError, - NotFoundError, - BadRequestError, - NotADirectoryError, - AlreadyExistsError, - ForbiddenError, - UnsupportedMediaTypeError, - ResourceLockedError, - InternalError, - isAppError, - isNodeError, - resolveNotebookPath -}; diff --git a/dist-api/chunk-FTVFWJFJ.js b/dist-api/chunk-FTVFWJFJ.js deleted file mode 100644 index e79caf7..0000000 --- a/dist-api/chunk-FTVFWJFJ.js +++ /dev/null @@ -1,20 +0,0 @@ -import { - PATHS -} from "./chunk-74TMTGBG.js"; - -// api/utils/tempDir.ts -import { existsSync, mkdirSync } from "fs"; -var tempDir = null; -var getTempDir = () => { - if (!tempDir) { - tempDir = PATHS.TEMP_ROOT; - if (!existsSync(tempDir)) { - mkdirSync(tempDir, { recursive: true }); - } - } - return tempDir; -}; - -export { - getTempDir -}; diff --git a/dist-api/chunk-M2SZ5AIA.js b/dist-api/chunk-M2SZ5AIA.js deleted file mode 100644 index 72c60a7..0000000 --- a/dist-api/chunk-M2SZ5AIA.js +++ /dev/null @@ -1,234 +0,0 @@ -import { - AlreadyExistsError, - BadRequestError, - NotFoundError, - ValidationError, - resolveNotebookPath -} from "./chunk-ER4KPD22.js"; -import { - asyncHandler, - createApiModule, - defineApiModule, - defineEndpoints, - successResponse -} from "./chunk-74TMTGBG.js"; - -// shared/modules/recycle-bin/api.ts -var RECYCLE_BIN_ENDPOINTS = defineEndpoints({ - list: { path: "/", method: "GET" }, - restore: { path: "/restore", method: "POST" }, - permanent: { path: "/permanent", method: "DELETE" }, - empty: { path: "/empty", method: "DELETE" } -}); - -// shared/modules/recycle-bin/index.ts -var RECYCLE_BIN_MODULE = defineApiModule({ - id: "recycle-bin", - name: "\u56DE\u6536\u7AD9", - basePath: "/recycle-bin", - order: 40, - version: "1.0.0", - endpoints: RECYCLE_BIN_ENDPOINTS -}); - -// api/modules/recycle-bin/routes.ts -import express from "express"; -import fs2 from "fs/promises"; -import path2 from "path"; - -// api/modules/recycle-bin/recycleBinService.ts -import fs from "fs/promises"; -import path from "path"; -async function restoreFile(srcPath, destPath, deletedDate, year, month, day) { - const { fullPath: imagesDir } = resolveNotebookPath(`images/${year}/${month}/${day}`); - let content = await fs.readFile(srcPath, "utf-8"); - const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g; - let match; - const imageReplacements = []; - while ((match = imageRegex.exec(content)) !== null) { - const imagePath = match[2]; - const imageName = path.basename(imagePath); - const rbImageName = `${deletedDate}_${imageName}`; - const { fullPath: srcImagePath } = resolveNotebookPath(`RB/${rbImageName}`); - try { - await fs.access(srcImagePath); - await fs.mkdir(imagesDir, { recursive: true }); - const destImagePath = path.join(imagesDir, imageName); - await fs.rename(srcImagePath, destImagePath); - const newImagePath = `images/${year}/${month}/${day}/${imageName}`; - imageReplacements.push({ oldPath: imagePath, newPath: newImagePath }); - } catch { - } - } - for (const { oldPath, newPath } of imageReplacements) { - content = content.replace(new RegExp(oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newPath); - } - await fs.writeFile(destPath, content, "utf-8"); - await fs.unlink(srcPath); -} -async function restoreFolder(srcPath, destPath, deletedDate, year, month, day) { - await fs.mkdir(destPath, { recursive: true }); - const entries = await fs.readdir(srcPath, { withFileTypes: true }); - for (const entry of entries) { - const srcEntryPath = path.join(srcPath, entry.name); - const destEntryPath = path.join(destPath, entry.name); - if (entry.isDirectory()) { - await restoreFolder(srcEntryPath, destEntryPath, deletedDate, year, month, day); - } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) { - await restoreFile(srcEntryPath, destEntryPath, deletedDate, year, month, day); - } else { - await fs.rename(srcEntryPath, destEntryPath); - } - } - const remaining = await fs.readdir(srcPath); - if (remaining.length === 0) { - await fs.rmdir(srcPath); - } -} - -// api/modules/recycle-bin/routes.ts -var router = express.Router(); -router.get( - "/", - asyncHandler(async (req, res) => { - const { fullPath: rbDir } = resolveNotebookPath("RB"); - try { - await fs2.access(rbDir); - } catch { - successResponse(res, { groups: [] }); - return; - } - const entries = await fs2.readdir(rbDir, { withFileTypes: true }); - const items = []; - for (const entry of entries) { - const match = entry.name.match(/^(\d{8})_(.+)$/); - if (!match) continue; - const [, dateStr, originalName] = match; - if (entry.isDirectory()) { - items.push({ - name: entry.name, - originalName, - type: "dir", - deletedDate: dateStr, - path: `RB/${entry.name}` - }); - } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) { - items.push({ - name: entry.name, - originalName, - type: "file", - deletedDate: dateStr, - path: `RB/${entry.name}` - }); - } - } - const groupedMap = /* @__PURE__ */ new Map(); - for (const item of items) { - const existing = groupedMap.get(item.deletedDate) || []; - existing.push(item); - groupedMap.set(item.deletedDate, existing); - } - const groups = Array.from(groupedMap.entries()).map(([date, items2]) => ({ - date, - items: items2.sort((a, b) => a.originalName.localeCompare(b.originalName)) - })).sort((a, b) => b.date.localeCompare(a.date)); - successResponse(res, { groups }); - }) -); -router.post( - "/restore", - asyncHandler(async (req, res) => { - const { path: relPath, type } = req.body; - if (!relPath || !type) { - throw new ValidationError("Path and type are required"); - } - const { fullPath: itemPath } = resolveNotebookPath(relPath); - try { - await fs2.access(itemPath); - } catch { - throw new NotFoundError("Item not found in recycle bin"); - } - const match = path2.basename(itemPath).match(/^(\d{8})_(.+)$/); - if (!match) { - throw new BadRequestError("Invalid recycle bin item name"); - } - const [, dateStr, originalName] = match; - const year = dateStr.substring(0, 4); - const month = dateStr.substring(4, 6); - const day = dateStr.substring(6, 8); - const { fullPath: markdownsDir } = resolveNotebookPath("markdowns"); - await fs2.mkdir(markdownsDir, { recursive: true }); - const destPath = path2.join(markdownsDir, originalName); - const existing = await fs2.stat(destPath).catch(() => null); - if (existing) { - throw new AlreadyExistsError("A file or folder with this name already exists"); - } - if (type === "dir") { - await restoreFolder(itemPath, destPath, dateStr, year, month, day); - } else { - await restoreFile(itemPath, destPath, dateStr, year, month, day); - } - successResponse(res, null); - }) -); -router.delete( - "/permanent", - asyncHandler(async (req, res) => { - const { path: relPath, type } = req.body; - if (!relPath || !type) { - throw new ValidationError("Path and type are required"); - } - const { fullPath: itemPath } = resolveNotebookPath(relPath); - try { - await fs2.access(itemPath); - } catch { - throw new NotFoundError("Item not found in recycle bin"); - } - if (type === "dir") { - await fs2.rm(itemPath, { recursive: true, force: true }); - } else { - await fs2.unlink(itemPath); - } - successResponse(res, null); - }) -); -router.delete( - "/empty", - asyncHandler(async (req, res) => { - const { fullPath: rbDir } = resolveNotebookPath("RB"); - try { - await fs2.access(rbDir); - } catch { - successResponse(res, null); - return; - } - const entries = await fs2.readdir(rbDir, { withFileTypes: true }); - for (const entry of entries) { - const entryPath = path2.join(rbDir, entry.name); - if (entry.isDirectory()) { - await fs2.rm(entryPath, { recursive: true, force: true }); - } else { - await fs2.unlink(entryPath); - } - } - successResponse(res, null); - }) -); -var routes_default = router; - -// api/modules/recycle-bin/index.ts -var createRecycleBinModule = () => { - return createApiModule(RECYCLE_BIN_MODULE, { - routes: (_container) => { - return routes_default; - } - }); -}; -var recycle_bin_default = createRecycleBinModule; - -export { - restoreFile, - restoreFolder, - createRecycleBinModule, - recycle_bin_default -}; diff --git a/dist-api/chunk-QS2CMBFP.js b/dist-api/chunk-QS2CMBFP.js deleted file mode 100644 index 3f92d5e..0000000 --- a/dist-api/chunk-QS2CMBFP.js +++ /dev/null @@ -1,912 +0,0 @@ -import { - logger -} from "./chunk-47DJ6YUB.js"; -import { - NOTEBOOK_ROOT, - asyncHandler, - createApiModule, - defineApiModule, - defineEndpoints, - successResponse -} from "./chunk-74TMTGBG.js"; - -// shared/modules/time-tracking/api.ts -var TIME_TRACKING_ENDPOINTS = defineEndpoints({ - current: { path: "/current", method: "GET" }, - event: { path: "/event", method: "POST" }, - day: { path: "/day/:date", method: "GET" }, - week: { path: "/week/:startDate", method: "GET" }, - month: { path: "/month/:yearMonth", method: "GET" }, - year: { path: "/year/:year", method: "GET" }, - stats: { path: "/stats", method: "GET" } -}); - -// shared/modules/time-tracking/index.ts -var TIME_TRACKING_MODULE = defineApiModule({ - id: "time-tracking", - name: "\u65F6\u95F4\u7EDF\u8BA1", - basePath: "/time", - order: 20, - version: "1.0.0", - endpoints: TIME_TRACKING_ENDPOINTS -}); - -// shared/utils/tabType.ts -var KNOWN_MODULE_IDS = [ - "home", - "settings", - "search", - "weread", - "recycle-bin", - "todo", - "time-tracking", - "pydemos" -]; -function getTabTypeFromPath(filePath) { - if (!filePath) return "other"; - if (filePath.startsWith("file-transfer-panel")) { - return "file-transfer"; - } - if (filePath.startsWith("remote-git://")) { - return "remote-git"; - } - if (filePath.startsWith("remote-desktop://")) { - return "remote-desktop"; - } - if (filePath.startsWith("remote-") && filePath !== "remote-tab") { - return "remote-desktop"; - } - if (filePath === "remote-tab" || filePath === "remote") { - return "remote"; - } - for (const moduleId of KNOWN_MODULE_IDS) { - if (filePath === `${moduleId}-tab` || filePath === moduleId) { - if (moduleId === "home" || moduleId === "settings" || moduleId === "search" || moduleId === "weread") { - return "other"; - } - return moduleId; - } - } - if (filePath.endsWith(".md")) { - return "markdown"; - } - return "other"; -} -function getFileNameFromPath(filePath) { - if (!filePath) return "\u672A\u77E5"; - if (filePath.startsWith("file-transfer-panel")) { - const params = new URLSearchParams(filePath.split("?")[1] || ""); - const deviceName = params.get("device") || ""; - return deviceName ? `\u6587\u4EF6\u4F20\u8F93 - ${deviceName}` : "\u6587\u4EF6\u4F20\u8F93"; - } - for (const moduleId of KNOWN_MODULE_IDS) { - if (filePath === `${moduleId}-tab` || filePath === moduleId) { - const names = { - "home": "\u9996\u9875", - "settings": "\u8BBE\u7F6E", - "search": "\u641C\u7D22", - "weread": "\u5FAE\u4FE1\u8BFB\u4E66", - "recycle-bin": "\u56DE\u6536\u7AD9", - "todo": "TODO", - "time-tracking": "\u65F6\u95F4\u7EDF\u8BA1", - "pydemos": "Python Demo", - "remote": "\u8FDC\u7A0B\u684C\u9762" - }; - return names[moduleId] ?? moduleId; - } - } - const parts = filePath.split("/"); - return parts[parts.length - 1] || filePath; -} - -// api/modules/time-tracking/heartbeatService.ts -var DEFAULT_HEARTBEAT_INTERVAL = 6e4; -var HeartbeatService = class { - interval = null; - lastHeartbeat = /* @__PURE__ */ new Date(); - intervalMs; - callback = null; - constructor(intervalMs = DEFAULT_HEARTBEAT_INTERVAL) { - this.intervalMs = intervalMs; - } - setCallback(callback) { - this.callback = callback; - } - start() { - if (this.interval) { - this.stop(); - } - this.interval = setInterval(async () => { - if (this.callback) { - try { - this.lastHeartbeat = /* @__PURE__ */ new Date(); - await this.callback(); - } catch (err) { - logger.error("Heartbeat callback failed:", err); - } - } - }, this.intervalMs); - this.lastHeartbeat = /* @__PURE__ */ new Date(); - } - stop() { - if (this.interval) { - clearInterval(this.interval); - this.interval = null; - } - } - isRunning() { - return this.interval !== null; - } - getLastHeartbeat() { - return this.lastHeartbeat; - } - updateHeartbeat() { - this.lastHeartbeat = /* @__PURE__ */ new Date(); - } - getState() { - return { - lastHeartbeat: this.lastHeartbeat, - isRunning: this.isRunning() - }; - } - restoreState(state) { - this.lastHeartbeat = new Date(state.lastHeartbeat); - } -}; -var createHeartbeatService = (intervalMs) => { - return new HeartbeatService(intervalMs); -}; - -// api/modules/time-tracking/sessionPersistence.ts -import fs from "fs/promises"; -import path from "path"; -var TIME_ROOT = path.join(NOTEBOOK_ROOT, "time"); -var getDayFilePath = (year, month, day) => { - const monthStr = month.toString().padStart(2, "0"); - const dayStr = day.toString().padStart(2, "0"); - return path.join(TIME_ROOT, year.toString(), `${year}${monthStr}${dayStr}.json`); -}; -var getMonthFilePath = (year, month) => { - const monthStr = month.toString().padStart(2, "0"); - return path.join(TIME_ROOT, year.toString(), `${year}${monthStr}.json`); -}; -var getYearFilePath = (year) => { - return path.join(TIME_ROOT, "summary", `${year}.json`); -}; -var ensureDirExists = async (filePath) => { - const dir = path.dirname(filePath); - await fs.mkdir(dir, { recursive: true }); -}; -var createEmptyDayData = (year, month, day) => ({ - date: `${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}`, - totalDuration: 0, - sessions: [], - tabSummary: {}, - lastUpdated: (/* @__PURE__ */ new Date()).toISOString() -}); -var createEmptyMonthData = (year, month) => ({ - year, - month, - days: {}, - monthlyTotal: 0, - averageDaily: 0, - activeDays: 0, - lastUpdated: (/* @__PURE__ */ new Date()).toISOString() -}); -var createEmptyYearData = (year) => ({ - year, - months: {}, - yearlyTotal: 0, - averageMonthly: 0, - averageDaily: 0, - totalActiveDays: 0 -}); -var SessionPersistenceService = class { - stateFilePath; - constructor() { - this.stateFilePath = path.join(TIME_ROOT, ".current-session.json"); - } - async loadCurrentState() { - try { - const content = await fs.readFile(this.stateFilePath, "utf-8"); - const state = JSON.parse(content); - return { - session: state.session || null, - currentTabRecord: state.currentTabRecord || null, - isPaused: state.isPaused || false, - lastHeartbeat: state.lastHeartbeat || (/* @__PURE__ */ new Date()).toISOString() - }; - } catch (err) { - logger.debug("No existing session to load or session file corrupted"); - return { - session: null, - currentTabRecord: null, - isPaused: false, - lastHeartbeat: (/* @__PURE__ */ new Date()).toISOString() - }; - } - } - async saveCurrentState(state) { - await ensureDirExists(this.stateFilePath); - await fs.writeFile(this.stateFilePath, JSON.stringify({ - session: state.session, - currentTabRecord: state.currentTabRecord, - isPaused: state.isPaused, - lastHeartbeat: state.lastHeartbeat - }), "utf-8"); - } - async clearCurrentState() { - try { - await fs.unlink(this.stateFilePath); - } catch (err) { - logger.debug("Session state file already removed or does not exist"); - } - } - async saveSessionToDay(session) { - const startTime = new Date(session.startTime); - const year = startTime.getFullYear(); - const month = startTime.getMonth() + 1; - const day = startTime.getDate(); - const filePath = getDayFilePath(year, month, day); - await ensureDirExists(filePath); - let dayData = await this.getDayData(year, month, day); - dayData.sessions.push(session); - dayData.totalDuration += session.duration; - for (const record of session.tabRecords) { - const key = record.filePath || record.fileName; - if (!dayData.tabSummary[key]) { - dayData.tabSummary[key] = { - fileName: record.fileName, - tabType: record.tabType, - totalDuration: 0, - focusCount: 0 - }; - } - dayData.tabSummary[key].totalDuration += record.duration; - dayData.tabSummary[key].focusCount += record.focusedPeriods.length; - } - dayData.lastUpdated = (/* @__PURE__ */ new Date()).toISOString(); - await fs.writeFile(filePath, JSON.stringify(dayData, null, 2), "utf-8"); - await this.updateMonthSummary(year, month, day, session.duration); - await this.updateYearSummary(year, month, session.duration); - } - async getDayData(year, month, day) { - const filePath = getDayFilePath(year, month, day); - try { - const content = await fs.readFile(filePath, "utf-8"); - return JSON.parse(content); - } catch (err) { - return createEmptyDayData(year, month, day); - } - } - async getMonthData(year, month) { - const filePath = getMonthFilePath(year, month); - try { - const content = await fs.readFile(filePath, "utf-8"); - const data = JSON.parse(content); - data.activeDays = Object.values(data.days).filter((d) => d.totalDuration > 0).length; - return data; - } catch (err) { - return createEmptyMonthData(year, month); - } - } - async getYearData(year) { - const filePath = getYearFilePath(year); - try { - const content = await fs.readFile(filePath, "utf-8"); - const data = JSON.parse(content); - data.totalActiveDays = Object.values(data.months).filter((m) => m.totalDuration > 0).length; - return data; - } catch (err) { - return createEmptyYearData(year); - } - } - async updateDayDataRealtime(year, month, day, session, currentTabRecord) { - const filePath = getDayFilePath(year, month, day); - await ensureDirExists(filePath); - let dayData = await this.getDayData(year, month, day); - const currentSessionDuration = session.tabRecords.reduce((sum, r) => sum + r.duration, 0) + (currentTabRecord?.duration || 0); - const existingSessionIndex = dayData.sessions.findIndex((s) => s.id === session.id); - const realtimeSession = { - ...session, - duration: currentSessionDuration, - tabRecords: currentTabRecord ? [...session.tabRecords, currentTabRecord] : session.tabRecords - }; - if (existingSessionIndex >= 0) { - const oldDuration = dayData.sessions[existingSessionIndex].duration; - dayData.sessions[existingSessionIndex] = realtimeSession; - dayData.totalDuration = dayData.totalDuration - oldDuration + currentSessionDuration; - } else { - dayData.sessions.push(realtimeSession); - dayData.totalDuration += currentSessionDuration; - } - dayData.tabSummary = {}; - for (const s of dayData.sessions) { - for (const record of s.tabRecords) { - const key = record.filePath || record.fileName; - if (!dayData.tabSummary[key]) { - dayData.tabSummary[key] = { - fileName: record.fileName, - tabType: record.tabType, - totalDuration: 0, - focusCount: 0 - }; - } - dayData.tabSummary[key].totalDuration += record.duration; - dayData.tabSummary[key].focusCount += record.focusedPeriods.length; - } - } - dayData.lastUpdated = (/* @__PURE__ */ new Date()).toISOString(); - await fs.writeFile(filePath, JSON.stringify(dayData, null, 2), "utf-8"); - return dayData; - } - async updateMonthSummary(year, month, day, duration) { - const filePath = getMonthFilePath(year, month); - await ensureDirExists(filePath); - let monthData = await this.getMonthData(year, month); - const dayStr = day.toString().padStart(2, "0"); - if (!monthData.days[dayStr]) { - monthData.days[dayStr] = { totalDuration: 0, sessions: 0, topTabs: [] }; - } - monthData.days[dayStr].totalDuration += duration; - monthData.days[dayStr].sessions += 1; - monthData.monthlyTotal += duration; - monthData.activeDays = Object.values(monthData.days).filter((d) => d.totalDuration > 0).length; - monthData.averageDaily = monthData.activeDays > 0 ? Math.floor(monthData.monthlyTotal / monthData.activeDays) : 0; - monthData.lastUpdated = (/* @__PURE__ */ new Date()).toISOString(); - await fs.writeFile(filePath, JSON.stringify(monthData, null, 2), "utf-8"); - } - async updateYearSummary(year, month, duration) { - const filePath = getYearFilePath(year); - await ensureDirExists(filePath); - let yearData = await this.getYearData(year); - const monthStr = month.toString().padStart(2, "0"); - if (!yearData.months[monthStr]) { - yearData.months[monthStr] = { totalDuration: 0, activeDays: 0 }; - } - yearData.months[monthStr].totalDuration += duration; - yearData.yearlyTotal += duration; - yearData.totalActiveDays = Object.values(yearData.months).reduce((sum, m) => { - const hasActiveDays = m.totalDuration > 0 ? 1 : 0; - return sum + hasActiveDays; - }, 0); - const activeMonthCount = Object.values(yearData.months).filter((m) => m.totalDuration > 0).length; - yearData.averageMonthly = activeMonthCount > 0 ? Math.floor(yearData.yearlyTotal / activeMonthCount) : 0; - yearData.averageDaily = yearData.totalActiveDays > 0 ? Math.floor(yearData.yearlyTotal / yearData.totalActiveDays) : 0; - await fs.writeFile(filePath, JSON.stringify(yearData, null, 2), "utf-8"); - } - async recalculateMonthSummary(year, month, todayDuration) { - const monthFilePath = getMonthFilePath(year, month); - await ensureDirExists(monthFilePath); - let monthData = await this.getMonthData(year, month); - const dayStr = (/* @__PURE__ */ new Date()).getDate().toString().padStart(2, "0"); - if (!monthData.days[dayStr]) { - monthData.days[dayStr] = { totalDuration: 0, sessions: 0, topTabs: [] }; - } - const oldDayDuration = monthData.days[dayStr].totalDuration; - monthData.days[dayStr].totalDuration = todayDuration; - monthData.monthlyTotal = monthData.monthlyTotal - oldDayDuration + todayDuration; - monthData.activeDays = Object.values(monthData.days).filter((d) => d.totalDuration > 0).length; - monthData.averageDaily = monthData.activeDays > 0 ? Math.floor(monthData.monthlyTotal / monthData.activeDays) : 0; - monthData.lastUpdated = (/* @__PURE__ */ new Date()).toISOString(); - await fs.writeFile(monthFilePath, JSON.stringify(monthData, null, 2), "utf-8"); - } - async recalculateYearSummary(year) { - const yearFilePath = getYearFilePath(year); - await ensureDirExists(yearFilePath); - let yearData = await this.getYearData(year); - const monthStr = ((/* @__PURE__ */ new Date()).getMonth() + 1).toString().padStart(2, "0"); - const monthFilePath = getMonthFilePath(year, (/* @__PURE__ */ new Date()).getMonth() + 1); - try { - const monthContent = await fs.readFile(monthFilePath, "utf-8"); - const monthData = JSON.parse(monthContent); - if (!yearData.months[monthStr]) { - yearData.months[monthStr] = { totalDuration: 0, activeDays: 0 }; - } - const oldMonthTotal = yearData.months[monthStr].totalDuration; - yearData.months[monthStr].totalDuration = monthData.monthlyTotal; - yearData.months[monthStr].activeDays = monthData.activeDays; - yearData.yearlyTotal = yearData.yearlyTotal - oldMonthTotal + monthData.monthlyTotal; - yearData.totalActiveDays = Object.values(yearData.months).reduce((sum, m) => { - const hasActiveDays = m.totalDuration > 0 ? 1 : 0; - return sum + hasActiveDays; - }, 0); - const activeMonthCount = Object.values(yearData.months).filter((m) => m.totalDuration > 0).length; - yearData.averageMonthly = activeMonthCount > 0 ? Math.floor(yearData.yearlyTotal / activeMonthCount) : 0; - yearData.averageDaily = yearData.totalActiveDays > 0 ? Math.floor(yearData.yearlyTotal / yearData.totalActiveDays) : 0; - } catch (err) { - logger.debug("Month file not found for year summary calculation"); - } - await fs.writeFile(yearFilePath, JSON.stringify(yearData, null, 2), "utf-8"); - } -}; -var createSessionPersistence = () => { - return new SessionPersistenceService(); -}; - -// api/modules/time-tracking/timeService.ts -var generateId = () => { - return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; -}; -var TimeTrackerService = class _TimeTrackerService { - currentSession = null; - currentTabRecord = null; - isPaused = false; - todayDuration = 0; - _initialized = false; - static _initializationPromise = null; - heartbeatService; - persistence; - constructor(dependencies) { - this.heartbeatService = dependencies.heartbeatService; - this.persistence = dependencies.persistence; - } - static async create(config) { - const heartbeatService = createHeartbeatService(config?.heartbeatIntervalMs); - const persistence = createSessionPersistence(); - const instance = new _TimeTrackerService({ - heartbeatService, - persistence - }); - await instance.initialize(); - return instance; - } - static async createWithDependencies(dependencies) { - const instance = new _TimeTrackerService(dependencies); - await instance.initialize(); - return instance; - } - async initialize() { - if (this._initialized) { - return; - } - if (_TimeTrackerService._initializationPromise) { - await _TimeTrackerService._initializationPromise; - return; - } - _TimeTrackerService._initializationPromise = this.loadCurrentState(); - await _TimeTrackerService._initializationPromise; - this._initialized = true; - _TimeTrackerService._initializationPromise = null; - this.heartbeatService.setCallback(async () => { - if (this.currentSession && !this.isPaused) { - try { - this.heartbeatService.updateHeartbeat(); - await this.updateCurrentTabDuration(); - await this.saveCurrentState(); - await this.updateTodayDataRealtime(); - } catch (err) { - logger.error("Heartbeat update failed:", err); - } - } - }); - } - ensureInitialized() { - if (!this._initialized) { - throw new Error("TimeTrackerService \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u4F7F\u7528 TimeTrackerService.create() \u521B\u5EFA\u5B9E\u4F8B"); - } - } - async loadCurrentState() { - const now = /* @__PURE__ */ new Date(); - const todayData = await this.persistence.getDayData(now.getFullYear(), now.getMonth() + 1, now.getDate()); - this.todayDuration = todayData.totalDuration; - const state = await this.persistence.loadCurrentState(); - if (state.session && state.session.status === "active") { - const sessionStart = new Date(state.session.startTime); - const now2 = /* @__PURE__ */ new Date(); - if (now2.getTime() - sessionStart.getTime() < 24 * 60 * 60 * 1e3) { - this.currentSession = state.session; - this.isPaused = state.isPaused; - if (state.currentTabRecord) { - this.currentTabRecord = state.currentTabRecord; - } - this.heartbeatService.restoreState({ lastHeartbeat: state.lastHeartbeat }); - } else { - await this.endSession(); - } - } - } - async saveCurrentState() { - await this.persistence.saveCurrentState({ - session: this.currentSession, - currentTabRecord: this.currentTabRecord, - isPaused: this.isPaused, - lastHeartbeat: this.heartbeatService.getLastHeartbeat().toISOString() - }); - } - async startSession() { - if (this.currentSession && this.currentSession.status === "active") { - return this.currentSession; - } - const now = /* @__PURE__ */ new Date(); - this.currentSession = { - id: generateId(), - startTime: now.toISOString(), - duration: 0, - status: "active", - tabRecords: [] - }; - this.isPaused = false; - this.heartbeatService.updateHeartbeat(); - this.heartbeatService.start(); - await this.saveCurrentState(); - return this.currentSession; - } - async pauseSession() { - if (!this.currentSession || this.isPaused) return; - this.isPaused = true; - await this.updateCurrentTabDuration(); - await this.saveCurrentState(); - } - async resumeSession() { - if (!this.currentSession || !this.isPaused) return; - this.isPaused = false; - this.heartbeatService.updateHeartbeat(); - if (this.currentTabRecord) { - const now = /* @__PURE__ */ new Date(); - const timeStr = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`; - this.currentTabRecord.focusedPeriods.push({ start: timeStr, end: timeStr }); - } - await this.saveCurrentState(); - } - async endSession() { - if (!this.currentSession) return; - this.heartbeatService.stop(); - await this.updateCurrentTabDuration(); - const now = /* @__PURE__ */ new Date(); - this.currentSession.endTime = now.toISOString(); - this.currentSession.status = "ended"; - const startTime = new Date(this.currentSession.startTime); - this.currentSession.duration = Math.floor((now.getTime() - startTime.getTime()) / 1e3); - await this.persistence.saveSessionToDay(this.currentSession); - this.todayDuration += this.currentSession.duration; - this.currentSession = null; - this.currentTabRecord = null; - this.isPaused = false; - await this.persistence.clearCurrentState(); - } - async updateCurrentTabDuration() { - if (!this.currentSession || !this.currentTabRecord) return; - const now = /* @__PURE__ */ new Date(); - const periods = this.currentTabRecord.focusedPeriods; - if (periods.length > 0) { - const lastPeriod = periods[periods.length - 1]; - const [h, m, s] = lastPeriod.start.split(":").map(Number); - const startSeconds = h * 3600 + m * 60 + s; - const currentSeconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds(); - this.currentTabRecord.duration = currentSeconds - startSeconds; - lastPeriod.end = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`; - } - } - async updateTodayDataRealtime() { - if (!this.currentSession) return; - const now = /* @__PURE__ */ new Date(); - const year = now.getFullYear(); - const month = now.getMonth() + 1; - const day = now.getDate(); - const dayData = await this.persistence.updateDayDataRealtime( - year, - month, - day, - this.currentSession, - this.currentTabRecord - ); - this.todayDuration = dayData.totalDuration; - await this.persistence.recalculateMonthSummary(year, month, this.todayDuration); - await this.persistence.recalculateYearSummary(year); - } - async handleTabSwitch(tabInfo) { - if (!this.currentSession || this.isPaused) return; - await this.updateCurrentTabDuration(); - if (this.currentTabRecord && this.currentTabRecord.duration > 0) { - this.currentSession.tabRecords.push({ ...this.currentTabRecord }); - } - const now = /* @__PURE__ */ new Date(); - const timeStr = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`; - this.currentTabRecord = { - tabId: tabInfo.tabId, - filePath: tabInfo.filePath, - fileName: getFileNameFromPath(tabInfo.filePath), - tabType: getTabTypeFromPath(tabInfo.filePath), - duration: 0, - focusedPeriods: [{ start: timeStr, end: timeStr }] - }; - await this.saveCurrentState(); - } - async handleEvent(event) { - switch (event.type) { - case "window-focus": - if (!this.currentSession) { - await this.startSession(); - if (event.tabInfo) { - await this.handleTabSwitch(event.tabInfo); - } - } else { - await this.resumeSession(); - await this.updateTodayDataRealtime(); - } - break; - case "window-blur": - await this.pauseSession(); - await this.updateTodayDataRealtime(); - break; - case "app-quit": - await this.endSession(); - break; - case "tab-switch": - case "tab-open": - if (!this.currentSession) { - await this.startSession(); - } - if (event.tabInfo) { - await this.handleTabSwitch(event.tabInfo); - } - await this.updateTodayDataRealtime(); - break; - case "tab-close": - await this.updateCurrentTabDuration(); - await this.updateTodayDataRealtime(); - break; - case "heartbeat": - if (this.currentSession && !this.isPaused) { - this.heartbeatService.updateHeartbeat(); - await this.updateCurrentTabDuration(); - await this.saveCurrentState(); - await this.updateTodayDataRealtime(); - } - break; - } - } - async getDayData(year, month, day) { - return this.persistence.getDayData(year, month, day); - } - async getWeekData(startDate) { - const result = []; - for (let i = 0; i < 7; i++) { - const date = new Date(startDate); - date.setDate(date.getDate() + i); - const data = await this.persistence.getDayData(date.getFullYear(), date.getMonth() + 1, date.getDate()); - result.push(data); - } - return result; - } - async getMonthData(year, month) { - return this.persistence.getMonthData(year, month); - } - async getYearData(year) { - return this.persistence.getYearData(year); - } - getCurrentState() { - return { - isRunning: this.currentSession !== null, - isPaused: this.isPaused, - currentSession: this.currentSession, - todayDuration: this.todayDuration, - currentTabRecord: this.currentTabRecord - }; - } - async getStats(year, month) { - const now = /* @__PURE__ */ new Date(); - const targetYear = year || now.getFullYear(); - const targetMonth = month; - let totalDuration = 0; - let activeDays = 0; - let longestDay = null; - let longestSession = null; - const tabDurations = {}; - const tabTypeDurations = {}; - if (targetMonth) { - const monthData = await this.persistence.getMonthData(targetYear, targetMonth); - totalDuration = monthData.monthlyTotal; - activeDays = Object.values(monthData.days).filter((d) => d.totalDuration > 0).length; - for (const [day, summary] of Object.entries(monthData.days)) { - if (!longestDay || summary.totalDuration > longestDay.duration) { - longestDay = { date: `${targetYear}-${targetMonth.toString().padStart(2, "0")}-${day}`, duration: summary.totalDuration }; - } - for (const tab of summary.topTabs || []) { - tabDurations[tab.fileName] = (tabDurations[tab.fileName] || 0) + tab.duration; - } - } - const dayData = await this.persistence.getDayData(targetYear, targetMonth, 1); - for (const session of dayData.sessions) { - for (const record of session.tabRecords) { - const key = record.filePath || record.fileName; - tabDurations[key] = (tabDurations[key] || 0) + record.duration; - tabTypeDurations[record.tabType] = (tabTypeDurations[record.tabType] || 0) + record.duration; - } - } - } else { - const yearData = await this.persistence.getYearData(targetYear); - totalDuration = yearData.yearlyTotal; - activeDays = Object.values(yearData.months).reduce((sum, m) => sum + m.activeDays, 0); - for (const [month2, summary] of Object.entries(yearData.months)) { - if (!longestDay || summary.totalDuration > longestDay.duration) { - longestDay = { date: `${targetYear}-${month2}`, duration: summary.totalDuration }; - } - } - for (let m = 1; m <= 12; m++) { - const monthStr = m.toString().padStart(2, "0"); - const monthData = await this.persistence.getMonthData(targetYear, m); - for (const dayData of Object.values(monthData.days)) { - for (const tab of dayData.topTabs || []) { - tabDurations[tab.fileName] = (tabDurations[tab.fileName] || 0) + tab.duration; - } - } - } - } - return { - totalDuration, - activeDays, - averageDaily: activeDays > 0 ? Math.floor(totalDuration / activeDays) : 0, - longestDay, - longestSession, - topTabs: Object.entries(tabDurations).map(([fileName, duration]) => ({ - fileName, - duration, - percentage: totalDuration > 0 ? Math.round(duration / totalDuration * 100) : 0 - })).sort((a, b) => b.duration - a.duration).slice(0, 10), - tabTypeDistribution: Object.entries(tabTypeDurations).map(([tabType, duration]) => ({ - tabType, - duration, - percentage: totalDuration > 0 ? Math.round(duration / totalDuration * 100) : 0 - })).sort((a, b) => b.duration - a.duration) - }; - } -}; -var _timeTrackerService = null; -var getTimeTrackerService = () => { - if (!_timeTrackerService) { - throw new Error("TimeTrackerService \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 initializeTimeTrackerService()"); - } - return _timeTrackerService; -}; -var initializeTimeTrackerService = async (config) => { - if (_timeTrackerService) { - return _timeTrackerService; - } - _timeTrackerService = await TimeTrackerService.create(config); - return _timeTrackerService; -}; -var initializeTimeTrackerServiceWithDependencies = async (dependencies) => { - if (_timeTrackerService) { - return _timeTrackerService; - } - _timeTrackerService = await TimeTrackerService.createWithDependencies(dependencies); - return _timeTrackerService; -}; - -// api/modules/time-tracking/routes.ts -import express from "express"; -var createTimeTrackingRoutes = (deps) => { - const router = express.Router(); - const { timeTrackerService } = deps; - router.get( - "/current", - asyncHandler(async (_req, res) => { - const state = timeTrackerService.getCurrentState(); - successResponse(res, { - isRunning: state.isRunning, - isPaused: state.isPaused, - currentSession: state.currentSession ? { - id: state.currentSession.id, - startTime: state.currentSession.startTime, - duration: state.currentSession.duration, - currentTab: state.currentTabRecord ? { - tabId: state.currentTabRecord.tabId, - fileName: state.currentTabRecord.fileName, - tabType: state.currentTabRecord.tabType - } : null - } : null, - todayDuration: state.todayDuration - }); - }) - ); - router.post( - "/event", - asyncHandler(async (req, res) => { - const event = req.body; - await timeTrackerService.handleEvent(event); - successResponse(res, null); - }) - ); - router.get( - "/day/:date", - asyncHandler(async (req, res) => { - const { date } = req.params; - const [year, month, day] = date.split("-").map(Number); - const data = await timeTrackerService.getDayData(year, month, day); - const sessionsCount = data.sessions.length; - const averageSessionDuration = sessionsCount > 0 ? Math.floor(data.totalDuration / sessionsCount) : 0; - const longestSession = data.sessions.reduce((max, s) => s.duration > max ? s.duration : max, 0); - const topTabs = Object.entries(data.tabSummary).map(([_, summary]) => ({ - fileName: summary.fileName, - duration: summary.totalDuration - })).sort((a, b) => b.duration - a.duration).slice(0, 5); - successResponse(res, { - ...data, - stats: { - sessionsCount, - averageSessionDuration, - longestSession, - topTabs - } - }); - }) - ); - router.get( - "/week/:startDate", - asyncHandler(async (req, res) => { - const { startDate } = req.params; - const [year, month, day] = startDate.split("-").map(Number); - const start = new Date(year, month - 1, day); - const data = await timeTrackerService.getWeekData(start); - const totalDuration = data.reduce((sum, d) => sum + d.totalDuration, 0); - const activeDays = data.filter((d) => d.totalDuration > 0).length; - successResponse(res, { - days: data, - totalDuration, - activeDays, - averageDaily: activeDays > 0 ? Math.floor(totalDuration / activeDays) : 0 - }); - }) - ); - router.get( - "/month/:yearMonth", - asyncHandler(async (req, res) => { - const { yearMonth } = req.params; - const [year, month] = yearMonth.split("-").map(Number); - const data = await timeTrackerService.getMonthData(year, month); - successResponse(res, data); - }) - ); - router.get( - "/year/:year", - asyncHandler(async (req, res) => { - const { year } = req.params; - const data = await timeTrackerService.getYearData(parseInt(year)); - successResponse(res, data); - }) - ); - router.get( - "/stats", - asyncHandler(async (req, res) => { - const year = req.query.year ? parseInt(req.query.year) : void 0; - const month = req.query.month ? parseInt(req.query.month) : void 0; - const stats = await timeTrackerService.getStats(year, month); - successResponse(res, stats); - }) - ); - return router; -}; - -// api/modules/time-tracking/index.ts -var createTimeTrackingModule = (moduleConfig = {}) => { - let serviceInstance; - return createApiModule(TIME_TRACKING_MODULE, { - routes: (container) => { - const timeTrackerService = container.getSync("timeTrackerService"); - return createTimeTrackingRoutes({ timeTrackerService }); - }, - lifecycle: { - onLoad: async (container) => { - serviceInstance = await initializeTimeTrackerService(moduleConfig.config); - container.register("timeTrackerService", () => serviceInstance); - } - } - }); -}; -var time_tracking_default = createTimeTrackingModule; - -export { - HeartbeatService, - createHeartbeatService, - SessionPersistenceService, - createSessionPersistence, - TimeTrackerService, - getTimeTrackerService, - initializeTimeTrackerService, - initializeTimeTrackerServiceWithDependencies, - createTimeTrackingRoutes, - createTimeTrackingModule, - time_tracking_default -}; diff --git a/dist-api/chunk-R5LQJNQE.js b/dist-api/chunk-R5LQJNQE.js deleted file mode 100644 index adaacd1..0000000 --- a/dist-api/chunk-R5LQJNQE.js +++ /dev/null @@ -1,586 +0,0 @@ -import { - logger -} from "./chunk-47DJ6YUB.js"; -import { - getTempDir -} from "./chunk-FTVFWJFJ.js"; -import { - InternalError, - ValidationError, - resolveNotebookPath -} from "./chunk-ER4KPD22.js"; -import { - NOTEBOOK_ROOT, - PROJECT_ROOT, - TEMP_ROOT, - asyncHandler, - createApiModule, - defineApiModule, - successResponse -} from "./chunk-74TMTGBG.js"; - -// api/modules/document-parser/index.ts -import express3 from "express"; - -// shared/modules/document-parser/index.ts -var DOCUMENT_PARSER_MODULE = defineApiModule({ - id: "document-parser", - name: "Document Parser", - basePath: "/document-parser", - order: 60, - version: "1.0.0", - frontend: { - enabled: false - }, - backend: { - enabled: true - } -}); - -// api/modules/document-parser/blogRoutes.ts -import express from "express"; -import path3 from "path"; -import fs3 from "fs/promises"; -import { existsSync as existsSync2 } from "fs"; -import axios from "axios"; - -// api/utils/file.ts -import fs from "fs/promises"; -import path from "path"; -var getUniqueFilename = async (imagesDirFullPath, baseName, ext) => { - const maxAttempts = 1e3; - for (let i = 0; i < maxAttempts; i++) { - const suffix = i === 0 ? "" : `-${i + 1}`; - const filename = `${baseName}${suffix}${ext}`; - const fullPath = path.join(imagesDirFullPath, filename); - try { - await fs.access(fullPath); - } catch { - return filename; - } - } - throw new InternalError("Failed to generate unique filename"); -}; -var mimeToExt = { - "image/png": ".png", - "image/jpeg": ".jpg", - "image/jpg": ".jpg", - "image/gif": ".gif", - "image/webp": ".webp" -}; -var IMAGE_MAGIC_BYTES = { - "image/png": { bytes: [137, 80, 78, 71, 13, 10, 26, 10] }, - "image/jpeg": { bytes: [255, 216, 255] }, - "image/gif": { bytes: [71, 73, 70, 56] }, - "image/webp": { bytes: [82, 73, 70, 70], offset: 0 } -}; -var WEBP_WEBP_MARKER = [87, 69, 66, 80]; -var MIN_IMAGE_SIZE = 16; -var MAX_IMAGE_SIZE = 8 * 1024 * 1024; -var validateImageBuffer = (buffer, claimedMimeType) => { - if (buffer.byteLength < MIN_IMAGE_SIZE) { - throw new ValidationError("Image file is too small or corrupted"); - } - if (buffer.byteLength > MAX_IMAGE_SIZE) { - throw new ValidationError("Image file is too large"); - } - const magicInfo = IMAGE_MAGIC_BYTES[claimedMimeType]; - if (!magicInfo) { - throw new ValidationError("Unsupported image type for content validation"); - } - const offset = magicInfo.offset || 0; - const expectedBytes = magicInfo.bytes; - for (let i = 0; i < expectedBytes.length; i++) { - if (buffer[offset + i] !== expectedBytes[i]) { - throw new ValidationError("Image content does not match the claimed file type"); - } - } - if (claimedMimeType === "image/webp") { - if (buffer.byteLength < 12) { - throw new ValidationError("WebP image is corrupted"); - } - for (let i = 0; i < WEBP_WEBP_MARKER.length; i++) { - if (buffer[8 + i] !== WEBP_WEBP_MARKER[i]) { - throw new ValidationError("WebP image content is invalid"); - } - } - } -}; -var detectImageMimeType = (buffer) => { - if (buffer.byteLength < 8) return null; - if (buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10) { - return "image/png"; - } - if (buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) { - return "image/jpeg"; - } - if (buffer[0] === 71 && buffer[1] === 73 && buffer[2] === 70 && buffer[3] === 56) { - return "image/gif"; - } - if (buffer[0] === 82 && buffer[1] === 73 && buffer[2] === 70 && buffer[3] === 70 && buffer[8] === 87 && buffer[9] === 69 && buffer[10] === 66 && buffer[11] === 80) { - return "image/webp"; - } - return null; -}; - -// shared/utils/date.ts -var pad2 = (n) => String(n).padStart(2, "0"); -var pad3 = (n) => String(n).padStart(3, "0"); -var formatTimestamp = (d) => { - const yyyy = d.getFullYear(); - const mm = pad2(d.getMonth() + 1); - const dd = pad2(d.getDate()); - const hh = pad2(d.getHours()); - const mi = pad2(d.getMinutes()); - const ss = pad2(d.getSeconds()); - const ms = pad3(d.getMilliseconds()); - return `${yyyy}${mm}${dd}_${hh}${mi}${ss}_${ms}`; -}; - -// api/modules/document-parser/documentParser.ts -import path2 from "path"; -import { spawn } from "child_process"; -import fs2 from "fs/promises"; -import { existsSync, mkdirSync } from "fs"; -if (!existsSync(TEMP_ROOT)) { - mkdirSync(TEMP_ROOT, { recursive: true }); -} -var createJobContext = async (prefix) => { - const now = /* @__PURE__ */ new Date(); - const jobDir = path2.join(TEMP_ROOT, `${prefix}_${formatTimestamp(now)}`); - await fs2.mkdir(jobDir, { recursive: true }); - const year = now.getFullYear(); - const month = pad2(now.getMonth() + 1); - const day = pad2(now.getDate()); - const imagesSubDir = `images/${year}/${month}/${day}`; - const destImagesDir = path2.join(NOTEBOOK_ROOT, imagesSubDir); - await fs2.mkdir(destImagesDir, { recursive: true }); - return { jobDir, now, imagesSubDir, destImagesDir }; -}; -var spawnPythonScript = async (options) => { - const { scriptPath, args, cwd, inputContent } = options; - return new Promise((resolve, reject) => { - const pythonProcess = spawn("python", ["-X", "utf8", scriptPath, ...args], { - cwd, - env: { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" } - }); - let stdout = ""; - let stderr = ""; - pythonProcess.stdout.on("data", (data) => { - stdout += data.toString(); - }); - pythonProcess.stderr.on("data", (data) => { - stderr += data.toString(); - }); - pythonProcess.on("close", (code) => { - if (code !== 0) { - logger.error("Python script error:", stderr); - reject(new Error(`Process exited with code ${code}. Error: ${stderr}`)); - } else { - resolve(stdout); - } - }); - pythonProcess.on("error", (err) => { - reject(err); - }); - if (inputContent !== void 0) { - pythonProcess.stdin.write(inputContent); - pythonProcess.stdin.end(); - } - }); -}; -var findImageDestinations = (md) => { - const results = []; - let i = 0; - while (i < md.length) { - const bang = md.indexOf("![", i); - if (bang === -1) break; - const closeBracket = md.indexOf("]", bang + 2); - if (closeBracket === -1) break; - if (md[closeBracket + 1] !== "(") { - i = closeBracket + 1; - continue; - } - const urlStart = closeBracket + 2; - let depth = 1; - let j = urlStart; - for (; j < md.length; j++) { - const ch = md[j]; - if (ch === "(") depth++; - else if (ch === ")") { - depth--; - if (depth === 0) break; - } - } - if (depth !== 0) break; - results.push({ url: md.slice(urlStart, j), start: urlStart, end: j }); - i = j + 1; - } - return results; -}; -var applyReplacements = (md, replacements) => { - const sorted = [...replacements].sort((a, b) => b.start - a.start); - let result = md; - for (const r of sorted) { - result = `${result.slice(0, r.start)}${r.replacement}${result.slice(r.end)}`; - } - return result; -}; -var copyLocalImage = async (src, jobDir, htmlDir, destImagesDir, imagesSubDir, now) => { - const s0 = src.trim().replace(/^<|>$/g, ""); - if (!s0) return null; - let decoded = s0; - try { - decoded = decodeURI(s0); - } catch { - } - const s1 = decoded.replace(/\\/g, "/"); - const s2 = s1.startsWith("./") ? s1.slice(2) : s1; - const candidates = s2.startsWith("/") ? [path2.join(jobDir, s2.slice(1)), path2.join(htmlDir, s2.slice(1))] : [path2.resolve(htmlDir, s2), path2.resolve(jobDir, s2)]; - let foundFile = null; - for (const c of candidates) { - if (existsSync(c)) { - foundFile = c; - break; - } - } - if (!foundFile) return null; - const ext = path2.extname(foundFile) || ".jpg"; - const baseName = formatTimestamp(now); - const newFilename = await getUniqueFilename(destImagesDir, baseName, ext); - const newPath = path2.join(destImagesDir, newFilename); - await fs2.copyFile(foundFile, newPath); - return { newLink: `/${imagesSubDir}/${newFilename}` }; -}; -var cleanupJob = async (jobDir, additionalPaths = []) => { - await fs2.rm(jobDir, { recursive: true, force: true }).catch(() => { - }); - for (const p of additionalPaths) { - await fs2.unlink(p).catch(() => { - }); - } -}; -var getScriptPath = (toolName, scriptName) => { - return path2.join(PROJECT_ROOT, "tools", toolName, scriptName); -}; -var ensureScriptExists = (scriptPath) => { - return existsSync(scriptPath); -}; - -// api/modules/document-parser/blogRoutes.ts -var router = express.Router(); -var tempDir = getTempDir(); -router.post( - "/parse-local", - asyncHandler(async (req, res) => { - const { htmlPath, htmlDir, assetsDirName, assetsFiles, targetPath } = req.body; - if (!htmlPath || !htmlDir || !targetPath) { - throw new ValidationError("htmlPath, htmlDir and targetPath are required"); - } - let fullTargetPath; - try { - const resolved = resolveNotebookPath(targetPath); - fullTargetPath = resolved.fullPath; - } catch (error) { - throw error; - } - const scriptPath = getScriptPath("blog", "parse_blog.py"); - if (!ensureScriptExists(scriptPath)) { - throw new InternalError("Parser script not found"); - } - const jobContext = await createJobContext("blog"); - let htmlPathInJob = ""; - try { - htmlPathInJob = path3.join(jobContext.jobDir, "input.html"); - await fs3.copyFile(htmlPath, htmlPathInJob); - if (assetsDirName && assetsFiles && assetsFiles.length > 0) { - const assetsDirPath = path3.join(htmlDir, assetsDirName); - for (const relPath of assetsFiles) { - const srcPath = path3.join(assetsDirPath, relPath); - if (existsSync2(srcPath)) { - const destPath = path3.join(jobContext.jobDir, assetsDirName, relPath); - await fs3.mkdir(path3.dirname(destPath), { recursive: true }); - await fs3.copyFile(srcPath, destPath); - } - } - } - } catch (err) { - await cleanupJob(jobContext.jobDir); - throw err; - } - processHtmlInBackground({ - jobDir: jobContext.jobDir, - htmlPath: htmlPathInJob, - targetPath: fullTargetPath, - cwd: path3.dirname(scriptPath), - jobContext, - originalHtmlDir: htmlDir, - originalAssetsDirName: assetsDirName - }).catch((err) => { - logger.error("Background HTML processing failed:", err); - fs3.writeFile(fullTargetPath, `# \u89E3\u6790\u5931\u8D25 - -> \u9519\u8BEF\u4FE1\u606F: ${err.message}`, "utf-8").catch(() => { - }); - cleanupJob(jobContext.jobDir).catch(() => { - }); - }); - successResponse(res, { - message: "HTML parsing started in background.", - status: "processing" - }); - }) -); -async function processHtmlInBackground(args) { - const { jobDir, htmlPath, targetPath, cwd, jobContext, originalHtmlDir, originalAssetsDirName } = args; - try { - await spawnPythonScript({ - scriptPath: "parse_blog.py", - args: [htmlPath], - cwd - }); - const parsedPathObj = path3.parse(htmlPath); - const markdownPath = path3.join(parsedPathObj.dir, `${parsedPathObj.name}.md`); - if (!existsSync2(markdownPath)) { - throw new Error("Markdown result file not found"); - } - let mdContent = await fs3.readFile(markdownPath, "utf-8"); - const ctx = await jobContext; - const htmlDir = path3.dirname(htmlPath); - const replacements = []; - const destinations = findImageDestinations(mdContent); - for (const dest of destinations) { - const originalSrc = dest.url; - if (!originalSrc) continue; - if (originalSrc.startsWith("http://") || originalSrc.startsWith("https://")) { - try { - const response = await axios.get(originalSrc, { responseType: "arraybuffer", timeout: 1e4 }); - const contentType = response.headers["content-type"]; - let ext = ".jpg"; - if (contentType) { - if (contentType.includes("png")) ext = ".png"; - else if (contentType.includes("gif")) ext = ".gif"; - else if (contentType.includes("webp")) ext = ".webp"; - else if (contentType.includes("svg")) ext = ".svg"; - else if (contentType.includes("jpeg") || contentType.includes("jpg")) ext = ".jpg"; - } - const urlExt = path3.extname(originalSrc.split("?")[0]); - if (urlExt) ext = urlExt; - const baseName = formatTimestamp(ctx.now); - const newFilename = await getUniqueFilename(ctx.destImagesDir, baseName, ext); - const newPath = path3.join(ctx.destImagesDir, newFilename); - await fs3.writeFile(newPath, response.data); - replacements.push({ - start: dest.start, - end: dest.end, - original: originalSrc, - replacement: `/${ctx.imagesSubDir}/${newFilename}` - }); - } catch { - } - continue; - } - if (originalSrc.startsWith("data:")) continue; - let result = await copyLocalImage( - originalSrc, - jobDir, - htmlDir, - ctx.destImagesDir, - ctx.imagesSubDir, - ctx.now - ); - if (!result && originalHtmlDir && originalAssetsDirName) { - const srcWithFiles = originalSrc.replace(/^\.\//, "").replace(/^\//, ""); - const possiblePaths = [ - path3.join(originalHtmlDir, originalAssetsDirName, srcWithFiles), - path3.join(originalHtmlDir, originalAssetsDirName, path3.basename(srcWithFiles)) - ]; - for (const p of possiblePaths) { - if (existsSync2(p)) { - const ext = path3.extname(p) || ".jpg"; - const baseName = formatTimestamp(ctx.now); - const newFilename = await getUniqueFilename(ctx.destImagesDir, baseName, ext); - const newPath = path3.join(ctx.destImagesDir, newFilename); - await fs3.copyFile(p, newPath); - result = { newLink: `/${ctx.imagesSubDir}/${newFilename}` }; - break; - } - } - } - if (result) { - replacements.push({ - start: dest.start, - end: dest.end, - original: originalSrc, - replacement: result.newLink - }); - } - } - mdContent = applyReplacements(mdContent, replacements); - await fs3.writeFile(targetPath, mdContent, "utf-8"); - await fs3.unlink(markdownPath).catch(() => { - }); - } finally { - await cleanupJob(jobDir); - } -} -var blogRoutes_default = router; - -// api/modules/document-parser/mineruRoutes.ts -import express2 from "express"; -import multer from "multer"; -import path4 from "path"; -import fs4 from "fs/promises"; -import { existsSync as existsSync3 } from "fs"; -var router2 = express2.Router(); -var tempDir2 = getTempDir(); -var upload = multer({ - dest: tempDir2, - limits: { - fileSize: 50 * 1024 * 1024 - } -}); -router2.post( - "/parse", - upload.single("file"), - asyncHandler(async (req, res) => { - if (!req.file) { - throw new ValidationError("File is required"); - } - const { targetPath } = req.body; - if (!targetPath) { - await fs4.unlink(req.file.path).catch(() => { - }); - throw new ValidationError("Target path is required"); - } - let fullTargetPath; - try { - const resolved = resolveNotebookPath(targetPath); - fullTargetPath = resolved.fullPath; - } catch (error) { - await fs4.unlink(req.file.path).catch(() => { - }); - throw error; - } - const scriptPath = getScriptPath("mineru", "mineru_parser.py"); - if (!ensureScriptExists(scriptPath)) { - await fs4.unlink(req.file.path).catch(() => { - }); - throw new InternalError("Parser script not found"); - } - processPdfInBackground(req.file.path, fullTargetPath, path4.dirname(scriptPath)).catch((err) => { - logger.error("Background PDF processing failed:", err); - fs4.writeFile(fullTargetPath, `# \u89E3\u6790\u5931\u8D25 - -> \u9519\u8BEF\u4FE1\u606F: ${err.message}`, "utf-8").catch(() => { - }); - }); - successResponse(res, { - message: "PDF upload successful. Parsing started in background.", - status: "processing" - }); - }) -); -async function processPdfInBackground(filePath, targetPath, cwd) { - try { - const output = await spawnPythonScript({ - scriptPath: "mineru_parser.py", - args: [filePath], - cwd - }); - const match = output.match(/JSON_RESULT:(.*)/); - if (!match) { - throw new Error("Failed to parse Python script output: JSON_RESULT not found"); - } - const result = JSON.parse(match[1]); - const markdownPath = result.markdown_file; - const outputDir = result.output_dir; - if (!existsSync3(markdownPath)) { - throw new Error("Markdown result file not found"); - } - let mdContent = await fs4.readFile(markdownPath, "utf-8"); - const imagesDir = path4.join(outputDir, "images"); - if (existsSync3(imagesDir)) { - const jobContext = await createJobContext("pdf_images"); - const destinations = findImageDestinations(mdContent); - const replacements = []; - for (const dest of destinations) { - const originalSrc = dest.url; - if (!originalSrc) continue; - const possibleFilenames = [originalSrc, path4.basename(originalSrc)]; - let foundFile = null; - for (const fname of possibleFilenames) { - const localPath = path4.join(imagesDir, fname); - if (existsSync3(localPath)) { - foundFile = localPath; - break; - } - const directPath = path4.join(outputDir, originalSrc); - if (existsSync3(directPath)) { - foundFile = directPath; - break; - } - } - if (foundFile) { - const ext = path4.extname(foundFile); - const baseName = formatTimestamp(jobContext.now); - const newFilename = await getUniqueFilename(jobContext.destImagesDir, baseName, ext); - const newPath = path4.join(jobContext.destImagesDir, newFilename); - await fs4.copyFile(foundFile, newPath); - replacements.push({ - start: dest.start, - end: dest.end, - original: originalSrc, - replacement: `${jobContext.imagesSubDir}/${newFilename}` - }); - } - } - mdContent = applyReplacements(mdContent, replacements); - } - await fs4.writeFile(targetPath, mdContent, "utf-8"); - await fs4.unlink(markdownPath).catch(() => { - }); - if (outputDir && outputDir.includes("temp")) { - await fs4.rm(outputDir, { recursive: true, force: true }).catch(() => { - }); - } - } finally { - await fs4.unlink(filePath).catch(() => { - }); - } -} -var mineruRoutes_default = router2; - -// api/modules/document-parser/index.ts -var createDocumentParserModule = () => { - return createApiModule(DOCUMENT_PARSER_MODULE, { - routes: (_container) => { - const router3 = express3.Router(); - router3.use("/blog", blogRoutes_default); - router3.use("/mineru", mineruRoutes_default); - return router3; - } - }); -}; -var document_parser_default = createDocumentParserModule; - -export { - pad2, - formatTimestamp, - getUniqueFilename, - mimeToExt, - validateImageBuffer, - detectImageMimeType, - createJobContext, - spawnPythonScript, - findImageDestinations, - applyReplacements, - copyLocalImage, - cleanupJob, - getScriptPath, - ensureScriptExists, - blogRoutes_default, - mineruRoutes_default, - createDocumentParserModule, - document_parser_default -}; diff --git a/dist-api/chunk-T5RPAMG6.js b/dist-api/chunk-T5RPAMG6.js deleted file mode 100644 index bedbeac..0000000 --- a/dist-api/chunk-T5RPAMG6.js +++ /dev/null @@ -1,313 +0,0 @@ -import { - validateBody, - validateQuery -} from "./chunk-5EGA6GHY.js"; -import { - getTempDir -} from "./chunk-FTVFWJFJ.js"; -import { - AlreadyExistsError, - NotFoundError, - ValidationError, - isNodeError, - resolveNotebookPath -} from "./chunk-ER4KPD22.js"; -import { - asyncHandler, - createApiModule, - defineApiModule, - defineEndpoints, - successResponse -} from "./chunk-74TMTGBG.js"; - -// shared/modules/pydemos/api.ts -var PYDEMOS_ENDPOINTS = defineEndpoints({ - list: { path: "/", method: "GET" }, - create: { path: "/create", method: "POST" }, - delete: { path: "/delete", method: "DELETE" }, - rename: { path: "/rename", method: "POST" } -}); - -// shared/modules/pydemos/index.ts -var PYDEMOS_MODULE = defineApiModule({ - id: "pydemos", - name: "Python Demos", - basePath: "/pydemos", - order: 50, - version: "1.0.0", - endpoints: PYDEMOS_ENDPOINTS -}); - -// api/modules/pydemos/routes.ts -import express from "express"; -import fs from "fs/promises"; -import path from "path"; -import multer from "multer"; - -// api/schemas/files.ts -import { z } from "zod"; -var listFilesQuerySchema = z.object({ - path: z.string().optional().default("") -}); -var contentQuerySchema = z.object({ - path: z.string().min(1) -}); -var rawQuerySchema = z.object({ - path: z.string().min(1) -}); -var pathSchema = z.object({ - path: z.string().min(1) -}); -var saveFileSchema = z.object({ - path: z.string().min(1), - content: z.string() -}); -var renameSchema = z.object({ - oldPath: z.string().min(1), - newPath: z.string().min(1) -}); -var searchSchema = z.object({ - keywords: z.array(z.string()).min(1) -}); -var existsSchema = z.object({ - path: z.string().min(1) -}); -var createDirSchema = z.object({ - path: z.string().min(1) -}); -var createFileSchema = z.object({ - path: z.string().min(1) -}); - -// api/schemas/pydemos.ts -import { z as z2 } from "zod"; -var listPyDemosQuerySchema = z2.object({ - year: z2.string().optional() -}); -var createPyDemoSchema = z2.object({ - name: z2.string().min(1), - year: z2.string().min(1), - month: z2.string().min(1), - folderStructure: z2.string().optional() -}); -var deletePyDemoSchema = z2.object({ - path: z2.string().min(1) -}); -var renamePyDemoSchema = z2.object({ - oldPath: z2.string().min(1), - newName: z2.string().min(1) -}); - -// api/modules/pydemos/routes.ts -var tempDir = getTempDir(); -var upload = multer({ - dest: tempDir, - limits: { - fileSize: 50 * 1024 * 1024 - } -}); -var toPosixPath = (p) => p.replace(/\\/g, "/"); -var getYearPath = (year) => { - const relPath = `pydemos/${year}`; - const { fullPath } = resolveNotebookPath(relPath); - return { relPath, fullPath }; -}; -var getMonthPath = (year, month) => { - const monthStr = month.toString().padStart(2, "0"); - const relPath = `pydemos/${year}/${monthStr}`; - const { fullPath } = resolveNotebookPath(relPath); - return { relPath, fullPath }; -}; -var countFilesInDir = async (dirPath) => { - try { - const entries = await fs.readdir(dirPath, { withFileTypes: true }); - return entries.filter((e) => e.isFile()).length; - } catch { - return 0; - } -}; -var createPyDemosRoutes = () => { - const router = express.Router(); - router.get( - "/", - validateQuery(listPyDemosQuerySchema), - asyncHandler(async (req, res) => { - const year = parseInt(req.query.year) || (/* @__PURE__ */ new Date()).getFullYear(); - const { fullPath: yearPath } = getYearPath(year); - const months = []; - try { - await fs.access(yearPath); - } catch { - successResponse(res, { months }); - return; - } - const monthEntries = await fs.readdir(yearPath, { withFileTypes: true }); - for (const monthEntry of monthEntries) { - if (!monthEntry.isDirectory()) continue; - const monthNum = parseInt(monthEntry.name); - if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) continue; - const monthPath = path.join(yearPath, monthEntry.name); - const demoEntries = await fs.readdir(monthPath, { withFileTypes: true }); - const demos = []; - for (const demoEntry of demoEntries) { - if (!demoEntry.isDirectory()) continue; - const demoPath = path.join(monthPath, demoEntry.name); - const relDemoPath = `pydemos/${year}/${monthEntry.name}/${demoEntry.name}`; - let created; - try { - const stats = await fs.stat(demoPath); - created = stats.birthtime.toISOString(); - } catch { - created = (/* @__PURE__ */ new Date()).toISOString(); - } - const fileCount = await countFilesInDir(demoPath); - demos.push({ - name: demoEntry.name, - path: toPosixPath(relDemoPath), - created, - fileCount - }); - } - demos.sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()); - if (demos.length > 0) { - months.push({ - month: monthNum, - demos - }); - } - } - months.sort((a, b) => a.month - b.month); - successResponse(res, { months }); - }) - ); - router.post( - "/create", - upload.array("files"), - validateBody(createPyDemoSchema), - asyncHandler(async (req, res) => { - const { name, year, month, folderStructure } = req.body; - const yearNum = parseInt(year); - const monthNum = parseInt(month); - if (!/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(name)) { - throw new ValidationError("Invalid name format"); - } - const { fullPath: monthPath, relPath: monthRelPath } = getMonthPath(yearNum, monthNum); - const demoPath = path.join(monthPath, name); - const relDemoPath = `${monthRelPath}/${name}`; - try { - await fs.access(demoPath); - throw new AlreadyExistsError("Demo already exists"); - } catch (err) { - if (isNodeError(err) && err.code === "ENOENT") { - } else if (err instanceof AlreadyExistsError) { - throw err; - } else { - throw err; - } - } - await fs.mkdir(demoPath, { recursive: true }); - const files = req.files; - let fileCount = 0; - if (files && files.length > 0) { - let structure = {}; - if (folderStructure) { - try { - structure = JSON.parse(folderStructure); - } catch { - structure = {}; - } - } - for (const file of files) { - const relativePath = structure[file.originalname] || file.originalname; - const targetPath = path.join(demoPath, relativePath); - const targetDir = path.dirname(targetPath); - await fs.mkdir(targetDir, { recursive: true }); - await fs.copyFile(file.path, targetPath); - await fs.unlink(file.path).catch(() => { - }); - fileCount++; - } - } - successResponse(res, { path: toPosixPath(relDemoPath), fileCount }); - }) - ); - router.delete( - "/delete", - validateBody(deletePyDemoSchema), - asyncHandler(async (req, res) => { - const { path: demoPath } = req.body; - if (!demoPath.startsWith("pydemos/")) { - throw new ValidationError("Invalid path"); - } - const { fullPath } = resolveNotebookPath(demoPath); - try { - await fs.access(fullPath); - } catch { - throw new NotFoundError("Demo not found"); - } - await fs.rm(fullPath, { recursive: true, force: true }); - successResponse(res, null); - }) - ); - router.post( - "/rename", - validateBody(renamePyDemoSchema), - asyncHandler(async (req, res) => { - const { oldPath, newName } = req.body; - if (!oldPath.startsWith("pydemos/")) { - throw new ValidationError("Invalid path"); - } - if (!/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(newName)) { - throw new ValidationError("Invalid name format"); - } - const { fullPath: oldFullPath } = resolveNotebookPath(oldPath); - try { - await fs.access(oldFullPath); - } catch { - throw new NotFoundError("Demo not found"); - } - const parentDir = path.dirname(oldFullPath); - const newFullPath = path.join(parentDir, newName); - const newPath = toPosixPath(path.join(path.dirname(oldPath), newName)); - try { - await fs.access(newFullPath); - throw new AlreadyExistsError("Demo with this name already exists"); - } catch (err) { - if (isNodeError(err) && err.code === "ENOENT") { - } else if (err instanceof AlreadyExistsError) { - throw err; - } else { - throw err; - } - } - await fs.rename(oldFullPath, newFullPath); - successResponse(res, { newPath }); - }) - ); - return router; -}; -var routes_default = createPyDemosRoutes(); - -// api/modules/pydemos/index.ts -var createPyDemosModule = () => { - return createApiModule(PYDEMOS_MODULE, { - routes: (_container) => { - return createPyDemosRoutes(); - } - }); -}; -var pydemos_default = createPyDemosModule; - -export { - listFilesQuerySchema, - contentQuerySchema, - rawQuerySchema, - pathSchema, - saveFileSchema, - renameSchema, - createDirSchema, - createFileSchema, - createPyDemosRoutes, - createPyDemosModule, - pydemos_default -}; diff --git a/dist-api/chunk-TSQNCXAS.js b/dist-api/chunk-TSQNCXAS.js deleted file mode 100644 index 9205719..0000000 --- a/dist-api/chunk-TSQNCXAS.js +++ /dev/null @@ -1,125 +0,0 @@ -import { - InternalError, - NotFoundError, - ValidationError, - resolveNotebookPath -} from "./chunk-ER4KPD22.js"; -import { - asyncHandler, - createApiModule, - defineApiModule, - successResponse -} from "./chunk-74TMTGBG.js"; - -// shared/modules/ai/index.ts -var AI_MODULE = defineApiModule({ - id: "ai", - name: "AI", - basePath: "/ai", - order: 70, - version: "1.0.0", - frontend: { - enabled: false - }, - backend: { - enabled: true - } -}); - -// api/modules/ai/routes.ts -import express from "express"; -import { spawn } from "child_process"; -import path from "path"; -import { fileURLToPath } from "url"; -import fs from "fs/promises"; -import fsSync from "fs"; -var __filename = fileURLToPath(import.meta.url); -var __dirname = path.dirname(__filename); -var router = express.Router(); -var PYTHON_TIMEOUT_MS = 3e4; -var spawnPythonWithTimeout = (scriptPath, args, stdinContent, timeoutMs = PYTHON_TIMEOUT_MS) => { - return new Promise((resolve, reject) => { - const pythonProcess = spawn("python", args, { - env: { ...process.env } - }); - let stdout = ""; - let stderr = ""; - let timeoutId = null; - const cleanup = () => { - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; - } - }; - timeoutId = setTimeout(() => { - cleanup(); - pythonProcess.kill(); - reject(new Error(`Python script timed out after ${timeoutMs}ms`)); - }, timeoutMs); - pythonProcess.stdout.on("data", (data) => { - stdout += data.toString(); - }); - pythonProcess.stderr.on("data", (data) => { - stderr += data.toString(); - }); - pythonProcess.on("close", (code) => { - cleanup(); - if (code !== 0) { - reject(new Error(`Python script exited with code ${code}. Stderr: ${stderr}`)); - } else { - resolve(stdout); - } - }); - pythonProcess.on("error", (err) => { - cleanup(); - reject(new Error(`Failed to start python process: ${err.message}`)); - }); - pythonProcess.stdin.write(stdinContent); - pythonProcess.stdin.end(); - }); -}; -router.post( - "/doubao", - asyncHandler(async (req, res) => { - const { task, path: relPath } = req.body; - if (!task) throw new ValidationError("Task is required"); - if (!relPath) throw new ValidationError("Path is required"); - const { fullPath } = resolveNotebookPath(relPath); - try { - await fs.access(fullPath); - } catch { - throw new NotFoundError("File not found"); - } - const content = await fs.readFile(fullPath, "utf-8"); - const projectRoot = path.resolve(__dirname, "..", "..", ".."); - const scriptPath = path.join(projectRoot, "tools", "doubao", "main.py"); - if (!fsSync.existsSync(scriptPath)) { - throw new InternalError(`Python script not found: ${scriptPath}`); - } - try { - const result = await spawnPythonWithTimeout(scriptPath, ["--task", task], content); - await fs.writeFile(fullPath, result, "utf-8"); - successResponse(res, { message: "Task completed successfully" }); - } catch (err) { - const message = err instanceof Error ? err.message : "Unknown error"; - throw new InternalError(`AI task failed: ${message}`); - } - }) -); -var createAiRoutes = () => router; - -// api/modules/ai/index.ts -var createAiModule = () => { - return createApiModule(AI_MODULE, { - routes: (_container) => { - return createAiRoutes(); - } - }); -}; -var ai_default = createAiModule; - -export { - createAiRoutes, - createAiModule, - ai_default -}; diff --git a/dist-api/chunk-V2OWYGQG.js b/dist-api/chunk-V2OWYGQG.js deleted file mode 100644 index 19fb8f4..0000000 --- a/dist-api/chunk-V2OWYGQG.js +++ /dev/null @@ -1,399 +0,0 @@ -import { - validateBody, - validateQuery -} from "./chunk-5EGA6GHY.js"; -import { - NotFoundError, - resolveNotebookPath -} from "./chunk-ER4KPD22.js"; -import { - asyncHandler, - createApiModule, - defineApiModule, - defineEndpoints, - successResponse -} from "./chunk-74TMTGBG.js"; - -// shared/modules/todo/api.ts -var TODO_ENDPOINTS = defineEndpoints({ - list: { path: "/", method: "GET" }, - save: { path: "/save", method: "POST" }, - add: { path: "/add", method: "POST" }, - toggle: { path: "/toggle", method: "POST" }, - update: { path: "/update", method: "POST" }, - delete: { path: "/delete", method: "DELETE" } -}); - -// shared/modules/todo/index.ts -var TODO_MODULE = defineApiModule({ - id: "todo", - name: "TODO", - basePath: "/todo", - order: 30, - version: "1.0.0", - endpoints: TODO_ENDPOINTS -}); - -// api/modules/todo/service.ts -import fs from "fs/promises"; -import path from "path"; - -// api/modules/todo/parser.ts -var parseTodoContent = (content) => { - const lines = content.split("\n"); - const result = []; - let currentDate = null; - let currentItems = []; - let itemId = 0; - for (const line of lines) { - const dateMatch = line.match(/^##\s*(\d{4}-\d{2}-\d{2})/); - if (dateMatch) { - if (currentDate) { - result.push({ date: currentDate, items: currentItems }); - } - currentDate = dateMatch[1]; - currentItems = []; - } else if (currentDate) { - const todoMatch = line.match(/^- (√|○) (.*)$/); - if (todoMatch) { - currentItems.push({ - id: `${currentDate}-${itemId++}`, - content: todoMatch[2], - completed: todoMatch[1] === "\u221A" - }); - } - } - } - if (currentDate) { - result.push({ date: currentDate, items: currentItems }); - } - return result; -}; -var generateTodoContent = (dayTodos) => { - const lines = []; - const sortedDays = [...dayTodos].sort((a, b) => a.date.localeCompare(b.date)); - for (const day of sortedDays) { - lines.push(`## ${day.date}`); - for (const item of day.items) { - const checkbox = item.completed ? "\u221A" : "\u25CB"; - lines.push(`- ${checkbox} ${item.content}`); - } - lines.push(""); - } - return lines.join("\n").trimEnd(); -}; - -// api/modules/todo/service.ts -var TodoService = class { - constructor(deps = {}) { - this.deps = deps; - } - getTodoFilePath(year, month) { - const yearStr = year.toString(); - const monthStr = month.toString().padStart(2, "0"); - const relPath = `TODO/${yearStr}/${yearStr}${monthStr}TODO.md`; - const { fullPath } = resolveNotebookPath(relPath); - return { relPath, fullPath }; - } - async ensureTodoFileExists(fullPath) { - const dir = path.dirname(fullPath); - await fs.mkdir(dir, { recursive: true }); - try { - await fs.access(fullPath); - } catch { - await fs.writeFile(fullPath, "", "utf-8"); - } - } - async loadAndParseTodoFile(year, month) { - const { fullPath } = this.getTodoFilePath(year, month); - try { - await fs.access(fullPath); - } catch { - throw new NotFoundError("TODO file not found"); - } - const content = await fs.readFile(fullPath, "utf-8"); - return { fullPath, dayTodos: parseTodoContent(content) }; - } - async saveTodoFile(fullPath, dayTodos) { - const content = generateTodoContent(dayTodos); - await fs.writeFile(fullPath, content, "utf-8"); - } - async getTodo(year, month) { - const { fullPath } = this.getTodoFilePath(year, month); - let dayTodos = []; - try { - await fs.access(fullPath); - const content = await fs.readFile(fullPath, "utf-8"); - dayTodos = parseTodoContent(content); - } catch { - } - const now = /* @__PURE__ */ new Date(); - const todayStr = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, "0")}-${now.getDate().toString().padStart(2, "0")}`; - const yesterday = new Date(now); - yesterday.setDate(yesterday.getDate() - 1); - const yesterdayStr = `${yesterday.getFullYear()}-${(yesterday.getMonth() + 1).toString().padStart(2, "0")}-${yesterday.getDate().toString().padStart(2, "0")}`; - if (year === now.getFullYear() && month === now.getMonth() + 1) { - const migrated = this.migrateIncompleteItems(dayTodos, todayStr, yesterdayStr); - if (migrated) { - const newContent = generateTodoContent(dayTodos); - await this.ensureTodoFileExists(fullPath); - await fs.writeFile(fullPath, newContent, "utf-8"); - } - } - return { dayTodos, year, month }; - } - migrateIncompleteItems(dayTodos, todayStr, yesterdayStr) { - let migrated = false; - const yesterdayTodo = dayTodos.find((d) => d.date === yesterdayStr); - if (yesterdayTodo) { - const incompleteItems = yesterdayTodo.items.filter((item) => !item.completed); - if (incompleteItems.length > 0) { - const todayTodo = dayTodos.find((d) => d.date === todayStr); - if (todayTodo) { - const existingIds = new Set(todayTodo.items.map((i) => i.id)); - const itemsToAdd = incompleteItems.map((item, idx) => ({ - ...item, - id: existingIds.has(item.id) ? `${todayStr}-migrated-${idx}` : item.id - })); - todayTodo.items = [...itemsToAdd, ...todayTodo.items]; - } else { - dayTodos.push({ - date: todayStr, - items: incompleteItems.map((item, idx) => ({ - ...item, - id: `${todayStr}-migrated-${idx}` - })) - }); - } - yesterdayTodo.items = yesterdayTodo.items.filter((item) => item.completed); - if (yesterdayTodo.items.length === 0) { - const index = dayTodos.findIndex((d) => d.date === yesterdayStr); - if (index !== -1) { - dayTodos.splice(index, 1); - } - } - migrated = true; - } - } - return migrated; - } - async saveTodo(year, month, dayTodos) { - const { fullPath } = this.getTodoFilePath(year, month); - await this.ensureTodoFileExists(fullPath); - const content = generateTodoContent(dayTodos); - await fs.writeFile(fullPath, content, "utf-8"); - } - async addTodo(year, month, date, todoContent) { - const { fullPath } = this.getTodoFilePath(year, month); - await this.ensureTodoFileExists(fullPath); - let fileContent = await fs.readFile(fullPath, "utf-8"); - const dayTodos = parseTodoContent(fileContent); - const existingDay = dayTodos.find((d) => d.date === date); - if (existingDay) { - const newId = `${date}-${existingDay.items.length}`; - existingDay.items.push({ - id: newId, - content: todoContent, - completed: false - }); - } else { - dayTodos.push({ - date, - items: [{ - id: `${date}-0`, - content: todoContent, - completed: false - }] - }); - } - fileContent = generateTodoContent(dayTodos); - await fs.writeFile(fullPath, fileContent, "utf-8"); - return dayTodos; - } - async toggleTodo(year, month, date, itemIndex, completed) { - const { fullPath, dayTodos } = await this.loadAndParseTodoFile(year, month); - const day = dayTodos.find((d) => d.date === date); - if (!day || itemIndex >= day.items.length) { - throw new NotFoundError("TODO item not found"); - } - day.items[itemIndex].completed = completed; - await this.saveTodoFile(fullPath, dayTodos); - return dayTodos; - } - async updateTodo(year, month, date, itemIndex, newContent) { - const { fullPath, dayTodos } = await this.loadAndParseTodoFile(year, month); - const day = dayTodos.find((d) => d.date === date); - if (!day || itemIndex >= day.items.length) { - throw new NotFoundError("TODO item not found"); - } - day.items[itemIndex].content = newContent; - await this.saveTodoFile(fullPath, dayTodos); - return dayTodos; - } - async deleteTodo(year, month, date, itemIndex) { - const { fullPath, dayTodos } = await this.loadAndParseTodoFile(year, month); - const dayIndex = dayTodos.findIndex((d) => d.date === date); - if (dayIndex === -1) { - throw new NotFoundError("Day not found"); - } - const day = dayTodos[dayIndex]; - if (itemIndex >= day.items.length) { - throw new NotFoundError("TODO item not found"); - } - day.items.splice(itemIndex, 1); - if (day.items.length === 0) { - dayTodos.splice(dayIndex, 1); - } - await this.saveTodoFile(fullPath, dayTodos); - return dayTodos; - } -}; -var createTodoService = (deps) => { - return new TodoService(deps); -}; - -// api/modules/todo/routes.ts -import express from "express"; - -// api/modules/todo/schemas.ts -import { z } from "zod"; -var todoItemSchema = z.object({ - id: z.string(), - content: z.string(), - completed: z.boolean() -}); -var dayTodoSchema = z.object({ - date: z.string(), - items: z.array(todoItemSchema) -}); -var getTodoQuerySchema = z.object({ - year: z.string().optional(), - month: z.string().optional() -}); -var saveTodoSchema = z.object({ - year: z.number().int().positive(), - month: z.number().int().min(1).max(12), - dayTodos: z.array(dayTodoSchema) -}); -var addTodoSchema = z.object({ - year: z.number().int().positive(), - month: z.number().int().min(1).max(12), - date: z.string(), - content: z.string() -}); -var toggleTodoSchema = z.object({ - year: z.number().int().positive(), - month: z.number().int().min(1).max(12), - date: z.string(), - itemIndex: z.number().int().nonnegative(), - completed: z.boolean() -}); -var updateTodoSchema = z.object({ - year: z.number().int().positive(), - month: z.number().int().min(1).max(12), - date: z.string(), - itemIndex: z.number().int().nonnegative(), - content: z.string() -}); -var deleteTodoSchema = z.object({ - year: z.number().int().positive(), - month: z.number().int().min(1).max(12), - date: z.string(), - itemIndex: z.number().int().nonnegative() -}); - -// api/modules/todo/routes.ts -var createTodoRoutes = (deps) => { - const router = express.Router(); - const { todoService: todoService2 } = deps; - router.get( - "/", - validateQuery(getTodoQuerySchema), - asyncHandler(async (req, res) => { - const year = parseInt(req.query.year) || (/* @__PURE__ */ new Date()).getFullYear(); - const month = parseInt(req.query.month) || (/* @__PURE__ */ new Date()).getMonth() + 1; - const result = await todoService2.getTodo(year, month); - successResponse(res, result); - }) - ); - router.post( - "/save", - validateBody(saveTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, dayTodos } = req.body; - await todoService2.saveTodo(year, month, dayTodos); - successResponse(res, null); - }) - ); - router.post( - "/add", - validateBody(addTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, date, content: todoContent } = req.body; - const dayTodos = await todoService2.addTodo(year, month, date, todoContent); - successResponse(res, { dayTodos }); - }) - ); - router.post( - "/toggle", - validateBody(toggleTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, date, itemIndex, completed } = req.body; - const dayTodos = await todoService2.toggleTodo(year, month, date, itemIndex, completed); - successResponse(res, { dayTodos }); - }) - ); - router.post( - "/update", - validateBody(updateTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, date, itemIndex, content: newContent } = req.body; - const dayTodos = await todoService2.updateTodo(year, month, date, itemIndex, newContent); - successResponse(res, { dayTodos }); - }) - ); - router.delete( - "/delete", - validateBody(deleteTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, date, itemIndex } = req.body; - const dayTodos = await todoService2.deleteTodo(year, month, date, itemIndex); - successResponse(res, { dayTodos }); - }) - ); - return router; -}; -var todoService = new TodoService(); -var routes_default = createTodoRoutes({ todoService }); - -// api/modules/todo/index.ts -var createTodoModule = () => { - return createApiModule(TODO_MODULE, { - routes: (container) => { - const todoService2 = container.getSync("todoService"); - return createTodoRoutes({ todoService: todoService2 }); - }, - lifecycle: { - onLoad: (container) => { - container.register("todoService", () => new TodoService()); - } - } - }); -}; -var todo_default = createTodoModule; - -export { - parseTodoContent, - generateTodoContent, - TodoService, - createTodoService, - getTodoQuerySchema, - saveTodoSchema, - addTodoSchema, - toggleTodoSchema, - updateTodoSchema, - deleteTodoSchema, - createTodoRoutes, - createTodoModule, - todo_default -}; diff --git a/dist-api/chunk-W5TDYTXE.js b/dist-api/chunk-W5TDYTXE.js deleted file mode 100644 index 3b3bb90..0000000 --- a/dist-api/chunk-W5TDYTXE.js +++ /dev/null @@ -1,280 +0,0 @@ -import { - resolveNotebookPath -} from "./chunk-ER4KPD22.js"; -import { - asyncHandler, - createApiModule, - defineApiModule, - defineEndpoints, - successResponse -} from "./chunk-74TMTGBG.js"; - -// shared/modules/remote/api.ts -var REMOTE_ENDPOINTS = defineEndpoints({ - getConfig: { path: "/config", method: "GET" }, - saveConfig: { path: "/config", method: "POST" }, - getScreenshot: { path: "/screenshot", method: "GET" }, - saveScreenshot: { path: "/screenshot", method: "POST" }, - getData: { path: "/data", method: "GET" }, - saveData: { path: "/data", method: "POST" } -}); - -// shared/modules/remote/index.ts -var REMOTE_MODULE = defineApiModule({ - id: "remote", - name: "\u8FDC\u7A0B", - basePath: "/remote", - order: 25, - version: "1.0.0", - endpoints: REMOTE_ENDPOINTS -}); - -// api/modules/remote/service.ts -import fs from "fs/promises"; -import path from "path"; -var REMOTE_DIR = "remote"; -var RemoteService = class { - constructor(deps = {}) { - this.deps = deps; - } - getRemoteDir() { - const { fullPath } = resolveNotebookPath(REMOTE_DIR); - return { relPath: REMOTE_DIR, fullPath }; - } - getDeviceDir(deviceName) { - const safeName = this.sanitizeFileName(deviceName); - const { fullPath } = resolveNotebookPath(path.join(REMOTE_DIR, safeName)); - return { relPath: path.join(REMOTE_DIR, safeName), fullPath }; - } - sanitizeFileName(name) { - return name.replace(/[<>:"/\\|?*]/g, "_").trim() || "unnamed"; - } - getDeviceConfigPath(deviceName) { - const { fullPath } = this.getDeviceDir(deviceName); - return path.join(fullPath, "config.json"); - } - getDeviceScreenshotPath(deviceName) { - const { fullPath } = this.getDeviceDir(deviceName); - return path.join(fullPath, "screenshot.png"); - } - getDeviceDataPath(deviceName) { - const { fullPath } = this.getDeviceDir(deviceName); - return path.join(fullPath, "data.json"); - } - async ensureDir(dirPath) { - await fs.mkdir(dirPath, { recursive: true }); - } - async getDeviceNames() { - const { fullPath } = this.getRemoteDir(); - try { - const entries = await fs.readdir(fullPath, { withFileTypes: true }); - const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name); - return dirs; - } catch { - return []; - } - } - async getConfig() { - const deviceNames = await this.getDeviceNames(); - const devices = await Promise.all( - deviceNames.map(async (name) => { - try { - const configPath = this.getDeviceConfigPath(name); - const content = await fs.readFile(configPath, "utf-8"); - const deviceConfig = JSON.parse(content); - return { - id: deviceConfig.id || name, - deviceName: name, - serverHost: deviceConfig.serverHost || "", - desktopPort: deviceConfig.desktopPort || 3e3, - gitPort: deviceConfig.gitPort || 3001, - openCodePort: deviceConfig.openCodePort || 3002, - fileTransferPort: deviceConfig.fileTransferPort || 3003, - password: deviceConfig.password || "" - }; - } catch { - return { - id: name, - deviceName: name, - serverHost: "", - desktopPort: 3e3, - gitPort: 3001, - openCodePort: 3002, - fileTransferPort: 3003, - password: "" - }; - } - }) - ); - return { devices }; - } - async saveConfig(config) { - const { fullPath: remoteDirFullPath } = this.getRemoteDir(); - await this.ensureDir(remoteDirFullPath); - const existingDevices = await this.getDeviceNames(); - const newDeviceNames = config.devices.map((d) => this.sanitizeFileName(d.deviceName)); - for (const oldDevice of existingDevices) { - if (!newDeviceNames.includes(oldDevice)) { - try { - const oldDir = path.join(remoteDirFullPath, oldDevice); - await fs.rm(oldDir, { recursive: true, force: true }); - } catch { - } - } - } - for (const device of config.devices) { - const deviceDir = this.getDeviceDir(device.deviceName); - await this.ensureDir(deviceDir.fullPath); - const deviceConfigPath = this.getDeviceConfigPath(device.deviceName); - const deviceConfig = { - id: device.id, - serverHost: device.serverHost, - desktopPort: device.desktopPort, - gitPort: device.gitPort, - openCodePort: device.openCodePort, - fileTransferPort: device.fileTransferPort, - password: device.password || "" - }; - await fs.writeFile(deviceConfigPath, JSON.stringify(deviceConfig, null, 2), "utf-8"); - } - } - async getScreenshot(deviceName) { - if (!deviceName) { - return null; - } - const screenshotPath = this.getDeviceScreenshotPath(deviceName); - try { - return await fs.readFile(screenshotPath); - } catch { - return null; - } - } - async saveScreenshot(dataUrl, deviceName) { - console.log("[RemoteService] saveScreenshot:", { deviceName, dataUrlLength: dataUrl?.length }); - if (!deviceName || deviceName.trim() === "") { - console.warn("[RemoteService] saveScreenshot skipped: no deviceName"); - return; - } - const deviceDir = this.getDeviceDir(deviceName); - await this.ensureDir(deviceDir.fullPath); - const base64Data = dataUrl.replace(/^data:image\/png;base64,/, ""); - const buffer = Buffer.from(base64Data, "base64"); - const screenshotPath = this.getDeviceScreenshotPath(deviceName); - await fs.writeFile(screenshotPath, buffer); - } - async getData(deviceName) { - if (!deviceName || deviceName.trim() === "") { - return null; - } - const dataPath = this.getDeviceDataPath(deviceName); - try { - const content = await fs.readFile(dataPath, "utf-8"); - return JSON.parse(content); - } catch { - return null; - } - } - async saveData(data, deviceName) { - if (!deviceName || deviceName.trim() === "") { - console.warn("[RemoteService] saveData skipped: no deviceName"); - return; - } - const deviceDir = this.getDeviceDir(deviceName); - await this.ensureDir(deviceDir.fullPath); - const dataPath = this.getDeviceDataPath(deviceName); - await fs.writeFile(dataPath, JSON.stringify(data, null, 2), "utf-8"); - } -}; -var createRemoteService = (deps) => { - return new RemoteService(deps); -}; - -// api/modules/remote/routes.ts -import express from "express"; -var createRemoteRoutes = (deps) => { - const router = express.Router(); - const { remoteService: remoteService2 } = deps; - router.get( - "/config", - asyncHandler(async (req, res) => { - const config = await remoteService2.getConfig(); - successResponse(res, config); - }) - ); - router.post( - "/config", - asyncHandler(async (req, res) => { - const config = req.body; - await remoteService2.saveConfig(config); - successResponse(res, null); - }) - ); - router.get( - "/screenshot", - asyncHandler(async (req, res) => { - const deviceName = req.query.device; - const buffer = await remoteService2.getScreenshot(deviceName); - if (!buffer) { - return successResponse(res, ""); - } - const base64 = `data:image/png;base64,${buffer.toString("base64")}`; - successResponse(res, base64); - }) - ); - router.post( - "/screenshot", - asyncHandler(async (req, res) => { - const { dataUrl, deviceName } = req.body; - console.log("[Remote] saveScreenshot called:", { deviceName, hasDataUrl: !!dataUrl }); - await remoteService2.saveScreenshot(dataUrl, deviceName); - successResponse(res, null); - }) - ); - router.get( - "/data", - asyncHandler(async (req, res) => { - const deviceName = req.query.device; - const data = await remoteService2.getData(deviceName); - successResponse(res, data); - }) - ); - router.post( - "/data", - asyncHandler(async (req, res) => { - const { deviceName, lastConnected } = req.body; - const data = {}; - if (lastConnected !== void 0) { - data.lastConnected = lastConnected; - } - await remoteService2.saveData(data, deviceName); - successResponse(res, null); - }) - ); - return router; -}; -var remoteService = new RemoteService(); -var routes_default = createRemoteRoutes({ remoteService }); - -// api/modules/remote/index.ts -var createRemoteModule = () => { - return createApiModule(REMOTE_MODULE, { - routes: (container) => { - const remoteService2 = container.getSync("remoteService"); - return createRemoteRoutes({ remoteService: remoteService2 }); - }, - lifecycle: { - onLoad: (container) => { - container.register("remoteService", () => new RemoteService()); - } - } - }); -}; -var remote_default = createRemoteModule; - -export { - RemoteService, - createRemoteService, - createRemoteRoutes, - createRemoteModule, - remote_default -}; diff --git a/dist-api/document-parser-FDZMXJVD.js b/dist-api/document-parser-FDZMXJVD.js deleted file mode 100644 index 8ce010f..0000000 --- a/dist-api/document-parser-FDZMXJVD.js +++ /dev/null @@ -1,32 +0,0 @@ -import { - applyReplacements, - blogRoutes_default, - cleanupJob, - copyLocalImage, - createDocumentParserModule, - createJobContext, - document_parser_default, - ensureScriptExists, - findImageDestinations, - getScriptPath, - mineruRoutes_default, - spawnPythonScript -} from "./chunk-R5LQJNQE.js"; -import "./chunk-47DJ6YUB.js"; -import "./chunk-FTVFWJFJ.js"; -import "./chunk-ER4KPD22.js"; -import "./chunk-74TMTGBG.js"; -export { - applyReplacements, - blogRoutes_default as blogRoutes, - cleanupJob, - copyLocalImage, - createDocumentParserModule, - createJobContext, - document_parser_default as default, - ensureScriptExists, - findImageDestinations, - getScriptPath, - mineruRoutes_default as mineruRoutes, - spawnPythonScript -}; diff --git a/dist-api/pydemos-W2QKOORB.js b/dist-api/pydemos-W2QKOORB.js deleted file mode 100644 index 8f6577d..0000000 --- a/dist-api/pydemos-W2QKOORB.js +++ /dev/null @@ -1,14 +0,0 @@ -import { - createPyDemosModule, - createPyDemosRoutes, - pydemos_default -} from "./chunk-T5RPAMG6.js"; -import "./chunk-5EGA6GHY.js"; -import "./chunk-FTVFWJFJ.js"; -import "./chunk-ER4KPD22.js"; -import "./chunk-74TMTGBG.js"; -export { - createPyDemosModule, - createPyDemosRoutes, - pydemos_default as default -}; diff --git a/dist-api/recycle-bin-VT62BD42.js b/dist-api/recycle-bin-VT62BD42.js deleted file mode 100644 index a1f3fa3..0000000 --- a/dist-api/recycle-bin-VT62BD42.js +++ /dev/null @@ -1,14 +0,0 @@ -import { - createRecycleBinModule, - recycle_bin_default, - restoreFile, - restoreFolder -} from "./chunk-M2SZ5AIA.js"; -import "./chunk-ER4KPD22.js"; -import "./chunk-74TMTGBG.js"; -export { - createRecycleBinModule, - recycle_bin_default as default, - restoreFile, - restoreFolder -}; diff --git a/dist-api/remote-KLYC364F.js b/dist-api/remote-KLYC364F.js deleted file mode 100644 index 09e766b..0000000 --- a/dist-api/remote-KLYC364F.js +++ /dev/null @@ -1,16 +0,0 @@ -import { - RemoteService, - createRemoteModule, - createRemoteRoutes, - createRemoteService, - remote_default -} from "./chunk-W5TDYTXE.js"; -import "./chunk-ER4KPD22.js"; -import "./chunk-74TMTGBG.js"; -export { - RemoteService, - createRemoteModule, - createRemoteRoutes, - createRemoteService, - remote_default as default -}; diff --git a/dist-api/server.js b/dist-api/server.js deleted file mode 100644 index a9e3467..0000000 --- a/dist-api/server.js +++ /dev/null @@ -1,3026 +0,0 @@ -var __glob = (map) => (path21) => { - var fn = map[path21]; - if (fn) return fn(); - throw new Error("Module not found in bundle: " + path21); -}; - -// api/app.ts -import express15 from "express"; -import cors from "cors"; -import dotenv from "dotenv"; - -// api/core/files/routes.ts -import express from "express"; -import fs from "fs/promises"; -import path3 from "path"; - -// api/utils/asyncHandler.ts -var asyncHandler = (fn) => (req, res, next) => { - Promise.resolve(fn(req, res, next)).catch(next); -}; - -// api/utils/response.ts -var successResponse = (res, data, statusCode = 200) => { - const response = { - success: true, - data - }; - res.status(statusCode).json(response); -}; - -// api/utils/pathSafety.ts -import path2 from "path"; - -// api/config/index.ts -import path from "path"; -import { fileURLToPath } from "url"; -import os from "os"; -var __filename = fileURLToPath(import.meta.url); -var __dirname = path.dirname(__filename); -var config = { - get projectRoot() { - if (__dirname.includes("app.asar")) { - return path.resolve(__dirname, "..").replace("app.asar", "app.asar.unpacked"); - } - return path.resolve(__dirname, "../../"); - }, - get notebookRoot() { - return process.env.NOTEBOOK_ROOT ? path.resolve(process.env.NOTEBOOK_ROOT) : path.join(this.projectRoot, "notebook"); - }, - get tempRoot() { - return path.join(os.tmpdir(), "xcnote_uploads"); - }, - get serverPort() { - return parseInt(process.env.PORT || "3001", 10); - }, - get isVercel() { - return !!process.env.VERCEL; - }, - get isElectron() { - return __dirname.includes("app.asar"); - }, - get isDev() { - return !this.isElectron && !this.isVercel; - } -}; -var PATHS = { - get PROJECT_ROOT() { - return config.projectRoot; - }, - get NOTEBOOK_ROOT() { - return config.notebookRoot; - }, - get TEMP_ROOT() { - return config.tempRoot; - } -}; - -// api/config/paths.ts -var PROJECT_ROOT = PATHS.PROJECT_ROOT; -var NOTEBOOK_ROOT = PATHS.NOTEBOOK_ROOT; -var TEMP_ROOT = PATHS.TEMP_ROOT; - -// shared/errors/index.ts -var AppError = class extends Error { - constructor(code, message, statusCode = 500, details) { - super(message); - this.code = code; - this.name = "AppError"; - this.statusCode = statusCode; - this.details = details; - } - statusCode; - details; - toJSON() { - return { - name: this.name, - code: this.code, - message: this.message, - statusCode: this.statusCode, - details: this.details - }; - } -}; -var ValidationError = class extends AppError { - constructor(message, details) { - super("VALIDATION_ERROR", message, 400, details); - this.name = "ValidationError"; - } -}; -var NotFoundError = class extends AppError { - constructor(message = "Resource not found", details) { - super("NOT_FOUND", message, 404, details); - this.name = "NotFoundError"; - } -}; -var AccessDeniedError = class extends AppError { - constructor(message = "Access denied", details) { - super("ACCESS_DENIED", message, 403, details); - this.name = "AccessDeniedError"; - } -}; -var BadRequestError = class extends AppError { - constructor(message, details) { - super("BAD_REQUEST", message, 400, details); - this.name = "BadRequestError"; - } -}; -var NotADirectoryError = class extends AppError { - constructor(message = "\u4E0D\u662F\u76EE\u5F55", details) { - super("NOT_A_DIRECTORY", message, 400, details); - this.name = "NotADirectoryError"; - } -}; -var AlreadyExistsError = class extends AppError { - constructor(message = "Resource already exists", details) { - super("ALREADY_EXISTS", message, 409, details); - this.name = "AlreadyExistsError"; - } -}; -var ForbiddenError = class extends AppError { - constructor(message = "\u7981\u6B62\u8BBF\u95EE", details) { - super("FORBIDDEN", message, 403, details); - this.name = "ForbiddenError"; - } -}; -var UnsupportedMediaTypeError = class extends AppError { - constructor(message = "\u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B", details) { - super("UNSUPPORTED_MEDIA_TYPE", message, 415, details); - this.name = "UnsupportedMediaTypeError"; - } -}; -var ResourceLockedError = class extends AppError { - constructor(message = "\u8D44\u6E90\u5DF2\u9501\u5B9A", details) { - super("RESOURCE_LOCKED", message, 423, details); - this.name = "ResourceLockedError"; - } -}; -var InternalError = class extends AppError { - constructor(message = "\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF", details) { - super("INTERNAL_ERROR", message, 500, details); - this.name = "InternalError"; - } -}; -function isAppError(error) { - return error instanceof AppError; -} -function isNodeError(error) { - return error instanceof Error && "code" in error; -} - -// api/utils/pathSafety.ts -var DANGEROUS_PATTERNS = [ - /\.\./, - /\0/, - /%2e%2e[%/]/i, - /%252e%252e[%/]/i, - /\.\.%2f/i, - /\.\.%5c/i, - /%c0%ae/i, - /%c1%9c/i, - /%c0%ae%c0%ae/i, - /%c1%9c%c1%9c/i, - /\.\.%c0%af/i, - /\.\.%c1%9c/i, - /%252e/i, - /%uff0e/i, - /%u002e/i -]; -var DOUBLE_ENCODE_PATTERNS = [ - /%25[0-9a-fA-F]{2}/, - /%[0-9a-fA-F]{2}%[0-9a-fA-F]{2}/ -]; -var normalizeRelPath = (input) => { - const trimmed = input.replace(/\0/g, "").trim(); - return trimmed.replace(/^[/\\]+/, ""); -}; -var containsPathTraversal = (input) => { - const decoded = decodeURIComponentSafe(input); - return DANGEROUS_PATTERNS.some((pattern) => pattern.test(input) || pattern.test(decoded)); -}; -var containsDoubleEncoding = (input) => { - return DOUBLE_ENCODE_PATTERNS.some((pattern) => pattern.test(input)); -}; -var hasPathSecurityIssues = (input) => { - return containsPathTraversal(input) || containsDoubleEncoding(input); -}; -var decodeURIComponentSafe = (input) => { - try { - return decodeURIComponent(input); - } catch { - return input; - } -}; -var resolveNotebookPath = (relPath) => { - if (hasPathSecurityIssues(relPath)) { - throw new AccessDeniedError("Path traversal detected"); - } - const safeRelPath = normalizeRelPath(relPath); - const notebookRoot = path2.resolve(NOTEBOOK_ROOT); - const fullPath = path2.resolve(notebookRoot, safeRelPath); - if (!fullPath.startsWith(notebookRoot)) { - throw new AccessDeniedError("Access denied"); - } - return { safeRelPath, fullPath }; -}; - -// shared/utils/path.ts -var toPosixPath = (p) => p.replace(/\\/g, "/"); - -// shared/utils/date.ts -var pad2 = (n) => String(n).padStart(2, "0"); -var pad3 = (n) => String(n).padStart(3, "0"); -var formatTimestamp = (d) => { - const yyyy = d.getFullYear(); - const mm = pad2(d.getMonth() + 1); - const dd = pad2(d.getDate()); - const hh = pad2(d.getHours()); - const mi = pad2(d.getMinutes()); - const ss = pad2(d.getSeconds()); - const ms = pad3(d.getMilliseconds()); - return `${yyyy}${mm}${dd}_${hh}${mi}${ss}_${ms}`; -}; - -// api/middlewares/validate.ts -import { ZodError } from "zod"; -var validateBody = (schema) => { - return (req, _res, next) => { - try { - req.body = schema.parse(req.body); - next(); - } catch (error) { - if (error instanceof ZodError) { - next(new ValidationError("Request validation failed", { issues: error.issues })); - } else { - next(error); - } - } - }; -}; -var validateQuery = (schema) => { - return (req, _res, next) => { - try { - req.query = schema.parse(req.query); - next(); - } catch (error) { - if (error instanceof ZodError) { - next(new ValidationError("Query validation failed", { issues: error.issues })); - } else { - next(error); - } - } - }; -}; - -// api/schemas/files.ts -import { z } from "zod"; -var listFilesQuerySchema = z.object({ - path: z.string().optional().default("") -}); -var contentQuerySchema = z.object({ - path: z.string().min(1) -}); -var rawQuerySchema = z.object({ - path: z.string().min(1) -}); -var pathSchema = z.object({ - path: z.string().min(1) -}); -var saveFileSchema = z.object({ - path: z.string().min(1), - content: z.string() -}); -var renameSchema = z.object({ - oldPath: z.string().min(1), - newPath: z.string().min(1) -}); -var searchSchema = z.object({ - keywords: z.array(z.string()).min(1) -}); -var existsSchema = z.object({ - path: z.string().min(1) -}); -var createDirSchema = z.object({ - path: z.string().min(1) -}); -var createFileSchema = z.object({ - path: z.string().min(1) -}); - -// api/schemas/pydemos.ts -import { z as z2 } from "zod"; -var listPyDemosQuerySchema = z2.object({ - year: z2.string().optional() -}); -var createPyDemoSchema = z2.object({ - name: z2.string().min(1), - year: z2.string().min(1), - month: z2.string().min(1), - folderStructure: z2.string().optional() -}); -var deletePyDemoSchema = z2.object({ - path: z2.string().min(1) -}); -var renamePyDemoSchema = z2.object({ - oldPath: z2.string().min(1), - newName: z2.string().min(1) -}); - -// api/utils/logger.ts -var createLogger = () => { - const isProd = process.env.NODE_ENV === "production"; - const debug = isProd ? () => { - } : console.debug.bind(console); - const info = console.info.bind(console); - const warn = console.warn.bind(console); - const error = console.error.bind(console); - return { debug, info, warn, error }; -}; -var logger = createLogger(); - -// api/core/files/routes.ts -var router = express.Router(); -router.get( - "/", - validateQuery(listFilesQuerySchema), - asyncHandler(async (req, res) => { - const relPath = req.query.path; - const { safeRelPath, fullPath } = resolveNotebookPath(relPath); - try { - await fs.access(fullPath); - } catch { - throw new NotFoundError("\u8DEF\u5F84\u4E0D\u5B58\u5728"); - } - const stats = await fs.stat(fullPath); - if (!stats.isDirectory()) { - throw new NotADirectoryError(); - } - const files = await fs.readdir(fullPath); - const items = await Promise.all( - files.map(async (name) => { - const filePath = path3.join(fullPath, name); - try { - const fileStats = await fs.stat(filePath); - return { - name, - type: fileStats.isDirectory() ? "dir" : "file", - size: fileStats.size, - modified: fileStats.mtime.toISOString(), - path: toPosixPath(path3.join(safeRelPath, name)) - }; - } catch { - return null; - } - }) - ); - const visibleItems = items.filter((i) => i !== null && !i.name.startsWith(".")); - visibleItems.sort((a, b) => { - if (a.type === b.type) return a.name.localeCompare(b.name); - return a.type === "dir" ? -1 : 1; - }); - successResponse(res, { items: visibleItems }); - }) -); -router.get( - "/content", - validateQuery(contentQuerySchema), - asyncHandler(async (req, res) => { - const relPath = req.query.path; - const { fullPath } = resolveNotebookPath(relPath); - const stats = await fs.stat(fullPath).catch(() => { - throw new NotFoundError("\u6587\u4EF6\u4E0D\u5B58\u5728"); - }); - if (!stats.isFile()) throw new BadRequestError("\u4E0D\u662F\u6587\u4EF6"); - const content = await fs.readFile(fullPath, "utf-8"); - successResponse(res, { - content, - metadata: { - size: stats.size, - modified: stats.mtime.toISOString() - } - }); - }) -); -router.get( - "/raw", - validateQuery(rawQuerySchema), - asyncHandler(async (req, res) => { - const relPath = req.query.path; - const { fullPath } = resolveNotebookPath(relPath); - const stats = await fs.stat(fullPath).catch(() => { - throw new NotFoundError("\u6587\u4EF6\u4E0D\u5B58\u5728"); - }); - if (!stats.isFile()) throw new BadRequestError("\u4E0D\u662F\u6587\u4EF6"); - const ext = path3.extname(fullPath).toLowerCase(); - const mimeTypes = { - ".png": "image/png", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".gif": "image/gif", - ".svg": "image/svg+xml", - ".pdf": "application/pdf" - }; - const mimeType = mimeTypes[ext]; - if (mimeType) res.setHeader("Content-Type", mimeType); - res.sendFile(fullPath); - }) -); -router.post( - "/save", - validateBody(saveFileSchema), - asyncHandler(async (req, res) => { - const { path: relPath, content } = req.body; - const { fullPath } = resolveNotebookPath(relPath); - await fs.mkdir(path3.dirname(fullPath), { recursive: true }); - await fs.writeFile(fullPath, content, "utf-8"); - successResponse(res, null); - }) -); -router.delete( - "/delete", - validateBody(pathSchema), - asyncHandler(async (req, res) => { - const { path: relPath } = req.body; - const { fullPath } = resolveNotebookPath(relPath); - await fs.stat(fullPath).catch(() => { - throw new NotFoundError("\u6587\u4EF6\u6216\u76EE\u5F55\u4E0D\u5B58\u5728"); - }); - const { fullPath: rbDir } = resolveNotebookPath("RB"); - await fs.mkdir(rbDir, { recursive: true }); - const originalName = path3.basename(fullPath); - const now = /* @__PURE__ */ new Date(); - const year = now.getFullYear(); - const month = pad2(now.getMonth() + 1); - const day = pad2(now.getDate()); - const timestamp = `${year}${month}${day}`; - const newName = `${timestamp}_${originalName}`; - const rbDestPath = path3.join(rbDir, newName); - await fs.rename(fullPath, rbDestPath); - successResponse(res, null); - }) -); -router.post( - "/exists", - validateBody(pathSchema), - asyncHandler(async (req, res) => { - const { path: relPath } = req.body; - const { fullPath } = resolveNotebookPath(relPath); - try { - const stats = await fs.stat(fullPath); - const type = stats.isDirectory() ? "dir" : stats.isFile() ? "file" : null; - successResponse(res, { exists: true, type }); - } catch (err) { - if (isNodeError(err) && err.code === "ENOENT") { - successResponse(res, { exists: false, type: null }); - return; - } - throw err; - } - }) -); -router.post( - "/create/dir", - validateBody(createDirSchema), - asyncHandler(async (req, res) => { - const { path: relPath } = req.body; - const { fullPath } = resolveNotebookPath(relPath); - try { - await fs.mkdir(fullPath, { recursive: true }); - } catch (err) { - if (isNodeError(err)) { - if (err.code === "EEXIST") { - throw new AlreadyExistsError("\u8DEF\u5F84\u5DF2\u5B58\u5728"); - } - if (err.code === "EACCES") { - throw new ForbiddenError("\u6CA1\u6709\u6743\u9650\u521B\u5EFA\u76EE\u5F55"); - } - } - throw err; - } - successResponse(res, null); - }) -); -router.post( - "/create/file", - validateBody(createFileSchema), - asyncHandler(async (req, res) => { - const { path: relPath } = req.body; - const { fullPath } = resolveNotebookPath(relPath); - await fs.mkdir(path3.dirname(fullPath), { recursive: true }); - try { - const fileName = path3.basename(relPath, ".md"); - const content = `# ${fileName}`; - await fs.writeFile(fullPath, content, { encoding: "utf-8", flag: "wx" }); - } catch (err) { - if (isNodeError(err)) { - if (err.code === "EEXIST") throw new AlreadyExistsError("\u8DEF\u5F84\u5DF2\u5B58\u5728"); - if (err.code === "EACCES") throw new ForbiddenError("\u6CA1\u6709\u6743\u9650\u521B\u5EFA\u6587\u4EF6"); - } - throw err; - } - successResponse(res, null); - }) -); -router.post( - "/rename", - validateBody(renameSchema), - asyncHandler(async (req, res) => { - const { oldPath, newPath } = req.body; - const { fullPath: oldFullPath } = resolveNotebookPath(oldPath); - const { fullPath: newFullPath } = resolveNotebookPath(newPath); - await fs.mkdir(path3.dirname(newFullPath), { recursive: true }); - try { - await fs.rename(oldFullPath, newFullPath); - } catch (err) { - if (isNodeError(err)) { - if (err.code === "ENOENT") { - throw new NotFoundError("\u6587\u4EF6\u4E0D\u5B58\u5728"); - } - if (err.code === "EEXIST") { - throw new AlreadyExistsError("\u8DEF\u5F84\u5DF2\u5B58\u5728"); - } - if (err.code === "EPERM" || err.code === "EACCES") { - throw new ForbiddenError("\u6CA1\u6709\u6743\u9650\u91CD\u547D\u540D\u6587\u4EF6\u6216\u76EE\u5F55"); - } - if (err.code === "EBUSY") { - throw new ResourceLockedError("\u6587\u4EF6\u6216\u76EE\u5F55\u6B63\u5728\u4F7F\u7528\u4E2D\u6216\u88AB\u9501\u5B9A"); - } - } - logger.error("\u91CD\u547D\u540D\u9519\u8BEF:", err); - throw new InternalError("\u91CD\u547D\u540D\u6587\u4EF6\u6216\u76EE\u5F55\u5931\u8D25"); - } - successResponse(res, null); - }) -); -var routes_default = router; - -// api/core/events/routes.ts -import express2 from "express"; - -// api/events/eventBus.ts -var clients = []; -var eventBus = { - addClient: (res) => { - clients.push(res); - logger.info(`SSE client connected. Total clients: ${clients.length}`); - }, - removeClient: (res) => { - clients = clients.filter((c) => c !== res); - logger.info(`SSE client disconnected. Total clients: ${clients.length}`); - }, - broadcast: (payload) => { - const data = `data: ${JSON.stringify(payload)} - -`; - logger.info(`Broadcasting to ${clients.length} clients: ${payload.event} - ${payload.path || ""}`); - clients = clients.filter((client) => { - try { - client.write(data); - return true; - } catch (error) { - logger.warn("SSE client write failed, removing"); - return false; - } - }); - } -}; - -// api/core/events/routes.ts -var router2 = express2.Router(); -router2.get("/", (req, res) => { - if (process.env.VERCEL) { - const response = { - success: false, - error: { code: "SSE_UNSUPPORTED", message: "SSE\u5728\u65E0\u670D\u52A1\u5668\u8FD0\u884C\u65F6\u4E2D\u4E0D\u53D7\u652F\u6301" } - }; - return res.status(501).json(response); - } - const headers = { - "Content-Type": "text/event-stream", - "Connection": "keep-alive", - "Cache-Control": "no-cache" - }; - res.writeHead(200, headers); - res.write(`data: ${JSON.stringify({ event: "connected" })} - -`); - eventBus.addClient(res); - req.on("close", () => { - eventBus.removeClient(res); - }); -}); -var routes_default2 = router2; - -// api/core/settings/routes.ts -import express3 from "express"; -import fs2 from "fs/promises"; -import path4 from "path"; -var router3 = express3.Router(); -var getSettingsPath = () => path4.join(NOTEBOOK_ROOT, ".config", "settings.json"); -router3.get( - "/", - asyncHandler(async (req, res) => { - const settingsPath = getSettingsPath(); - try { - const content = await fs2.readFile(settingsPath, "utf-8"); - const settings = JSON.parse(content); - successResponse(res, settings); - } catch (error) { - successResponse(res, {}); - } - }) -); -router3.post( - "/", - asyncHandler(async (req, res) => { - const settings = req.body; - const settingsPath = getSettingsPath(); - const configDir = path4.dirname(settingsPath); - try { - await fs2.mkdir(configDir, { recursive: true }); - let existingSettings = {}; - try { - const content = await fs2.readFile(settingsPath, "utf-8"); - existingSettings = JSON.parse(content); - } catch { - } - const newSettings = { ...existingSettings, ...settings }; - await fs2.writeFile(settingsPath, JSON.stringify(newSettings, null, 2), "utf-8"); - successResponse(res, newSettings); - } catch (error) { - throw error; - } - }) -); -var routes_default3 = router3; - -// api/core/upload/routes.ts -import express4 from "express"; -import fs4 from "fs/promises"; -import path6 from "path"; - -// api/utils/file.ts -import fs3 from "fs/promises"; -import path5 from "path"; -var getUniqueFilename = async (imagesDirFullPath, baseName, ext) => { - const maxAttempts = 1e3; - for (let i = 0; i < maxAttempts; i++) { - const suffix = i === 0 ? "" : `-${i + 1}`; - const filename = `${baseName}${suffix}${ext}`; - const fullPath = path5.join(imagesDirFullPath, filename); - try { - await fs3.access(fullPath); - } catch { - return filename; - } - } - throw new InternalError("Failed to generate unique filename"); -}; -var mimeToExt = { - "image/png": ".png", - "image/jpeg": ".jpg", - "image/jpg": ".jpg", - "image/gif": ".gif", - "image/webp": ".webp" -}; -var IMAGE_MAGIC_BYTES = { - "image/png": { bytes: [137, 80, 78, 71, 13, 10, 26, 10] }, - "image/jpeg": { bytes: [255, 216, 255] }, - "image/gif": { bytes: [71, 73, 70, 56] }, - "image/webp": { bytes: [82, 73, 70, 70], offset: 0 } -}; -var WEBP_WEBP_MARKER = [87, 69, 66, 80]; -var MIN_IMAGE_SIZE = 16; -var MAX_IMAGE_SIZE = 8 * 1024 * 1024; -var validateImageBuffer = (buffer, claimedMimeType) => { - if (buffer.byteLength < MIN_IMAGE_SIZE) { - throw new ValidationError("Image file is too small or corrupted"); - } - if (buffer.byteLength > MAX_IMAGE_SIZE) { - throw new ValidationError("Image file is too large"); - } - const magicInfo = IMAGE_MAGIC_BYTES[claimedMimeType]; - if (!magicInfo) { - throw new ValidationError("Unsupported image type for content validation"); - } - const offset = magicInfo.offset || 0; - const expectedBytes = magicInfo.bytes; - for (let i = 0; i < expectedBytes.length; i++) { - if (buffer[offset + i] !== expectedBytes[i]) { - throw new ValidationError("Image content does not match the claimed file type"); - } - } - if (claimedMimeType === "image/webp") { - if (buffer.byteLength < 12) { - throw new ValidationError("WebP image is corrupted"); - } - for (let i = 0; i < WEBP_WEBP_MARKER.length; i++) { - if (buffer[8 + i] !== WEBP_WEBP_MARKER[i]) { - throw new ValidationError("WebP image content is invalid"); - } - } - } -}; -var detectImageMimeType = (buffer) => { - if (buffer.byteLength < 8) return null; - if (buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10) { - return "image/png"; - } - if (buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) { - return "image/jpeg"; - } - if (buffer[0] === 71 && buffer[1] === 73 && buffer[2] === 70 && buffer[3] === 56) { - return "image/gif"; - } - if (buffer[0] === 82 && buffer[1] === 73 && buffer[2] === 70 && buffer[3] === 70 && buffer[8] === 87 && buffer[9] === 69 && buffer[10] === 66 && buffer[11] === 80) { - return "image/webp"; - } - return null; -}; - -// api/core/upload/routes.ts -var router4 = express4.Router(); -var parseImageDataUrl = (dataUrl) => { - const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=\s]+)$/); - if (!match) return null; - const [, mimeType, base64Data] = match; - return { mimeType, base64Data: base64Data.replace(/\s/g, "") }; -}; -router4.post( - "/image", - asyncHandler(async (req, res) => { - const { image } = req.body; - if (!image) throw new ValidationError("\u9700\u8981\u56FE\u7247\u6570\u636E"); - const parsed = parseImageDataUrl(image); - if (!parsed) { - throw new ValidationError("\u65E0\u6548\u7684\u56FE\u7247\u6570\u636EURL"); - } - const ext = mimeToExt[parsed.mimeType]; - if (!ext) { - throw new UnsupportedMediaTypeError("\u4E0D\u652F\u6301\u7684\u56FE\u7247\u7C7B\u578B"); - } - const buffer = Buffer.from(parsed.base64Data, "base64"); - validateImageBuffer(buffer, parsed.mimeType); - const detectedMimeType = detectImageMimeType(buffer); - if (!detectedMimeType || detectedMimeType !== parsed.mimeType) { - throw new ValidationError("\u56FE\u7247\u5185\u5BB9\u7C7B\u578B\u4E0D\u5339\u914D\u6216\u56FE\u7247\u5DF2\u635F\u574F"); - } - const now = /* @__PURE__ */ new Date(); - const year = now.getFullYear(); - const month = pad2(now.getMonth() + 1); - const day = pad2(now.getDate()); - const imagesSubDir = `images/${year}/${month}/${day}`; - const { fullPath: imagesDirFullPath } = resolveNotebookPath(imagesSubDir); - await fs4.mkdir(imagesDirFullPath, { recursive: true }); - const baseName = formatTimestamp(now); - const filename = await getUniqueFilename(imagesDirFullPath, baseName, ext); - const relPath = `${imagesSubDir}/${filename}`; - const { fullPath } = resolveNotebookPath(relPath); - await fs4.writeFile(fullPath, buffer); - successResponse(res, { name: toPosixPath(relPath), path: toPosixPath(relPath) }); - }) -); -router4.post( - "/wallpaper", - asyncHandler(async (req, res) => { - const { image } = req.body; - if (!image) throw new ValidationError("\u9700\u8981\u56FE\u7247\u6570\u636E"); - const parsed = parseImageDataUrl(image); - if (!parsed) { - throw new ValidationError("\u65E0\u6548\u7684\u56FE\u7247\u6570\u636EURL"); - } - const allowedWallpaperTypes = ["image/png", "image/jpeg", "image/webp"]; - if (!allowedWallpaperTypes.includes(parsed.mimeType)) { - throw new UnsupportedMediaTypeError("\u58C1\u7EB8\u53EA\u652F\u6301PNG\u3001JPEG\u548CWebP\u683C\u5F0F"); - } - const buffer = Buffer.from(parsed.base64Data, "base64"); - validateImageBuffer(buffer, parsed.mimeType); - const detectedMimeType = detectImageMimeType(buffer); - if (!detectedMimeType || detectedMimeType !== parsed.mimeType) { - throw new ValidationError("\u56FE\u7247\u5185\u5BB9\u7C7B\u578B\u4E0D\u5339\u914D\u6216\u56FE\u7247\u5DF2\u635F\u574F"); - } - const configDir = path6.join(NOTEBOOK_ROOT, ".config"); - const backgroundPath = path6.join(configDir, "background.png"); - await fs4.mkdir(configDir, { recursive: true }); - await fs4.writeFile(backgroundPath, buffer); - successResponse(res, { message: "\u58C1\u7EB8\u5DF2\u66F4\u65B0" }); - }) -); -var routes_default4 = router4; - -// api/core/search/routes.ts -import express5 from "express"; -import fs5 from "fs/promises"; -import path7 from "path"; -var router5 = express5.Router(); -router5.post( - "/", - asyncHandler(async (req, res) => { - const { keywords } = req.body; - if (!keywords || !Array.isArray(keywords) || keywords.length === 0) { - successResponse(res, { items: [] }); - return; - } - const searchTerms = keywords.map((k) => k.trim().toLowerCase()).filter((k) => k.length > 0); - if (searchTerms.length === 0) { - successResponse(res, { items: [] }); - return; - } - const { fullPath: rootPath } = resolveNotebookPath(""); - const results = []; - const maxResults = 100; - const searchDir = async (dir, relativeDir) => { - if (results.length >= maxResults) return; - const entries = await fs5.readdir(dir, { withFileTypes: true }); - for (const entry of entries) { - if (results.length >= maxResults) break; - const entryPath = path7.join(dir, entry.name); - const entryRelativePath = path7.join(relativeDir, entry.name); - if (entry.name.startsWith(".") || entry.name === "RB" || entry.name === "node_modules") continue; - if (entry.isDirectory()) { - await searchDir(entryPath, entryRelativePath); - } else if (entry.isFile()) { - const fileNameLower = entry.name.toLowerCase(); - let contentLower = ""; - let contentLoaded = false; - const checkKeyword = async (term) => { - if (fileNameLower.includes(term)) return true; - if (entry.name.toLowerCase().endsWith(".md")) { - if (!contentLoaded) { - try { - const content = await fs5.readFile(entryPath, "utf-8"); - contentLower = content.toLowerCase(); - contentLoaded = true; - } catch { - return false; - } - } - return contentLower.includes(term); - } - return false; - }; - let allMatched = true; - for (const term of searchTerms) { - const matched = await checkKeyword(term); - if (!matched) { - allMatched = false; - break; - } - } - if (allMatched) { - results.push({ - name: entry.name, - path: toPosixPath(entryRelativePath), - type: "file", - size: 0, - modified: (/* @__PURE__ */ new Date()).toISOString() - }); - } - } - } - }; - await searchDir(rootPath, ""); - successResponse(res, { items: results, limited: results.length >= maxResults }); - }) -); -var routes_default5 = router5; - -// shared/constants/errors.ts -var ERROR_CODES = { - PATH_NOT_FOUND: "PATH_NOT_FOUND", - NOT_A_DIRECTORY: "NOT_A_DIRECTORY", - ACCESS_DENIED: "ACCESS_DENIED", - FILE_EXISTS: "FILE_EXISTS", - INVALID_PATH: "INVALID_PATH", - VALIDATION_ERROR: "VALIDATION_ERROR", - INTERNAL_ERROR: "INTERNAL_ERROR", - NOT_FOUND: "NOT_FOUND", - BAD_REQUEST: "BAD_REQUEST", - NAME_GENERATION_FAILED: "NAME_GENERATION_FAILED", - SSE_UNSUPPORTED: "SSE_UNSUPPORTED", - ALREADY_EXISTS: "ALREADY_EXISTS", - NOT_A_FILE: "NOT_A_FILE", - FORBIDDEN: "FORBIDDEN", - UNSUPPORTED_MEDIA_TYPE: "UNSUPPORTED_MEDIA_TYPE", - PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE", - RESOURCE_LOCKED: "RESOURCE_LOCKED", - INVALID_NAME: "INVALID_NAME" -}; - -// api/middlewares/errorHandler.ts -var errorHandler = (err, _req, res, _next) => { - let statusCode = 500; - let code = ERROR_CODES.INTERNAL_ERROR; - let message = "Server internal error"; - let details = void 0; - if (isAppError(err)) { - statusCode = err.statusCode; - code = err.code; - message = err.message; - details = err.details; - } else if (isNodeError(err)) { - message = err.message; - if (process.env.NODE_ENV !== "production") { - details = { stack: err.stack, nodeErrorCode: err.code }; - } - } else if (err instanceof Error) { - message = err.message; - if (process.env.NODE_ENV !== "production") { - details = { stack: err.stack }; - } - } - logger.error(err); - const response = { - success: false, - error: { - code, - message, - details: process.env.NODE_ENV === "production" ? void 0 : details - } - }; - res.status(statusCode).json(response); -}; - -// api/infra/moduleManager.ts -var ModuleManager = class { - modules = /* @__PURE__ */ new Map(); - activeModules = /* @__PURE__ */ new Set(); - container; - constructor(container2) { - this.container = container2; - } - async register(module) { - const { id, dependencies = [] } = module.metadata; - for (const dep of dependencies) { - if (!this.modules.has(dep)) { - throw new Error(`Module '${id}' depends on '${dep}' which is not registered`); - } - } - this.modules.set(id, module); - if (module.lifecycle?.onLoad) { - await module.lifecycle.onLoad(this.container); - } - } - async activate(id) { - const module = this.modules.get(id); - if (!module) { - throw new Error(`Module '${id}' not found`); - } - if (this.activeModules.has(id)) { - return; - } - const { dependencies = [] } = module.metadata; - for (const dep of dependencies) { - await this.activate(dep); - } - if (module.lifecycle?.onActivate) { - await module.lifecycle.onActivate(this.container); - } - this.activeModules.add(id); - } - async deactivate(id) { - const module = this.modules.get(id); - if (!module) return; - if (!this.activeModules.has(id)) return; - if (module.lifecycle?.onDeactivate) { - await module.lifecycle.onDeactivate(this.container); - } - this.activeModules.delete(id); - } - getModule(id) { - return this.modules.get(id); - } - getAllModules() { - return Array.from(this.modules.values()).sort((a, b) => (a.metadata.order || 0) - (b.metadata.order || 0)); - } - getActiveModules() { - return Array.from(this.activeModules); - } -}; - -// api/infra/container.ts -var ServiceContainer = class { - descriptors = /* @__PURE__ */ new Map(); - singletonInstances = /* @__PURE__ */ new Map(); - scopedInstances = /* @__PURE__ */ new Map(); - resolutionStack = []; - disposed = false; - register(nameOrDescriptor, factory) { - this.ensureNotDisposed(); - if (typeof nameOrDescriptor === "string") { - const descriptor = { - name: nameOrDescriptor, - factory, - lifetime: "singleton" /* Singleton */ - }; - this.descriptors.set(nameOrDescriptor, descriptor); - } else { - this.descriptors.set(nameOrDescriptor.name, nameOrDescriptor); - } - } - async get(name) { - this.ensureNotDisposed(); - return this.resolveInternal(name, null); - } - getSync(name) { - this.ensureNotDisposed(); - const descriptor = this.descriptors.get(name); - if (!descriptor) { - throw new Error(`Service '${name}' not registered`); - } - if (descriptor.lifetime === "singleton" /* Singleton */) { - if (this.singletonInstances.has(name)) { - return this.singletonInstances.get(name); - } - } - const result = descriptor.factory(); - if (result instanceof Promise) { - throw new Error( - `Service '${name}' has an async factory but getSync() was called. Use get() instead.` - ); - } - if (descriptor.lifetime === "singleton" /* Singleton */) { - this.singletonInstances.set(name, result); - } - return result; - } - createScope(scopeId) { - this.ensureNotDisposed(); - return new ServiceScope(this, scopeId); - } - has(name) { - return this.descriptors.has(name); - } - async dispose() { - if (this.disposed) { - return; - } - for (const [name, instance] of this.singletonInstances) { - const descriptor = this.descriptors.get(name); - if (descriptor?.onDispose) { - try { - await descriptor.onDispose(instance); - } catch (error) { - console.error(`Error disposing service '${name}':`, error); - } - } - } - for (const [, scopeMap] of this.scopedInstances) { - for (const [name, instance] of scopeMap) { - const descriptor = this.descriptors.get(name); - if (descriptor?.onDispose) { - try { - await descriptor.onDispose(instance); - } catch (error) { - console.error(`Error disposing scoped service '${name}':`, error); - } - } - } - } - this.singletonInstances.clear(); - this.scopedInstances.clear(); - this.descriptors.clear(); - this.resolutionStack = []; - this.disposed = true; - } - clear() { - this.singletonInstances.clear(); - this.scopedInstances.clear(); - this.descriptors.clear(); - this.resolutionStack = []; - } - isDisposed() { - return this.disposed; - } - async resolveInternal(name, scopeId) { - if (this.resolutionStack.includes(name)) { - const cycle = [...this.resolutionStack, name].join(" -> "); - throw new Error(`Circular dependency detected: ${cycle}`); - } - const descriptor = this.descriptors.get(name); - if (!descriptor) { - throw new Error(`Service '${name}' not registered`); - } - if (descriptor.lifetime === "singleton" /* Singleton */) { - if (this.singletonInstances.has(name)) { - return this.singletonInstances.get(name); - } - this.resolutionStack.push(name); - try { - const instance = await descriptor.factory(); - this.singletonInstances.set(name, instance); - return instance; - } finally { - this.resolutionStack.pop(); - } - } - if (descriptor.lifetime === "scoped" /* Scoped */) { - if (!scopeId) { - throw new Error( - `Scoped service '${name}' cannot be resolved outside of a scope. Use createScope() first.` - ); - } - let scopeMap = this.scopedInstances.get(scopeId); - if (!scopeMap) { - scopeMap = /* @__PURE__ */ new Map(); - this.scopedInstances.set(scopeId, scopeMap); - } - if (scopeMap.has(name)) { - return scopeMap.get(name); - } - this.resolutionStack.push(name); - try { - const instance = await descriptor.factory(); - scopeMap.set(name, instance); - return instance; - } finally { - this.resolutionStack.pop(); - } - } - this.resolutionStack.push(name); - try { - const instance = await descriptor.factory(); - return instance; - } finally { - this.resolutionStack.pop(); - } - } - ensureNotDisposed() { - if (this.disposed) { - throw new Error("ServiceContainer has been disposed"); - } - } -}; -var ServiceScope = class { - container; - scopeId; - disposed = false; - constructor(container2, scopeId) { - this.container = container2; - this.scopeId = scopeId; - } - async get(name) { - if (this.disposed) { - throw new Error("ServiceScope has been disposed"); - } - return this.container["resolveInternal"](name, this.scopeId); - } - async dispose() { - if (this.disposed) { - return; - } - const scopeMap = this.container["scopedInstances"].get(this.scopeId); - if (scopeMap) { - for (const [name, instance] of scopeMap) { - const descriptor = this.container["descriptors"].get(name); - if (descriptor?.onDispose) { - try { - await descriptor.onDispose(instance); - } catch (error) { - console.error(`Error disposing scoped service '${name}':`, error); - } - } - } - this.container["scopedInstances"].delete(this.scopeId); - } - this.disposed = true; - } - isDisposed() { - return this.disposed; - } -}; - -// api/modules/index.ts -import { readdirSync, statSync } from "fs"; -import { join, dirname } from "path"; -import { fileURLToPath as fileURLToPath3 } from "url"; - -// shared/modules/types.ts -function defineApiModule(config2) { - return config2; -} -function defineEndpoints(endpoints) { - return endpoints; -} - -// shared/modules/todo/api.ts -var TODO_ENDPOINTS = defineEndpoints({ - list: { path: "/", method: "GET" }, - save: { path: "/save", method: "POST" }, - add: { path: "/add", method: "POST" }, - toggle: { path: "/toggle", method: "POST" }, - update: { path: "/update", method: "POST" }, - delete: { path: "/delete", method: "DELETE" } -}); - -// shared/modules/todo/index.ts -var TODO_MODULE = defineApiModule({ - id: "todo", - name: "TODO", - basePath: "/todo", - order: 30, - version: "1.0.0", - endpoints: TODO_ENDPOINTS -}); - -// api/modules/todo/service.ts -import fs6 from "fs/promises"; -import path8 from "path"; - -// api/modules/todo/parser.ts -var parseTodoContent = (content) => { - const lines = content.split("\n"); - const result = []; - let currentDate = null; - let currentItems = []; - let itemId = 0; - for (const line of lines) { - const dateMatch = line.match(/^##\s*(\d{4}-\d{2}-\d{2})/); - if (dateMatch) { - if (currentDate) { - result.push({ date: currentDate, items: currentItems }); - } - currentDate = dateMatch[1]; - currentItems = []; - } else if (currentDate) { - const todoMatch = line.match(/^- (√|○) (.*)$/); - if (todoMatch) { - currentItems.push({ - id: `${currentDate}-${itemId++}`, - content: todoMatch[2], - completed: todoMatch[1] === "\u221A" - }); - } - } - } - if (currentDate) { - result.push({ date: currentDate, items: currentItems }); - } - return result; -}; -var generateTodoContent = (dayTodos) => { - const lines = []; - const sortedDays = [...dayTodos].sort((a, b) => a.date.localeCompare(b.date)); - for (const day of sortedDays) { - lines.push(`## ${day.date}`); - for (const item of day.items) { - const checkbox = item.completed ? "\u221A" : "\u25CB"; - lines.push(`- ${checkbox} ${item.content}`); - } - lines.push(""); - } - return lines.join("\n").trimEnd(); -}; - -// api/modules/todo/service.ts -var TodoService = class { - constructor(deps = {}) { - this.deps = deps; - } - getTodoFilePath(year, month) { - const yearStr = year.toString(); - const monthStr = month.toString().padStart(2, "0"); - const relPath = `TODO/${yearStr}/${yearStr}${monthStr}TODO.md`; - const { fullPath } = resolveNotebookPath(relPath); - return { relPath, fullPath }; - } - async ensureTodoFileExists(fullPath) { - const dir = path8.dirname(fullPath); - await fs6.mkdir(dir, { recursive: true }); - try { - await fs6.access(fullPath); - } catch { - await fs6.writeFile(fullPath, "", "utf-8"); - } - } - async loadAndParseTodoFile(year, month) { - const { fullPath } = this.getTodoFilePath(year, month); - try { - await fs6.access(fullPath); - } catch { - throw new NotFoundError("TODO file not found"); - } - const content = await fs6.readFile(fullPath, "utf-8"); - return { fullPath, dayTodos: parseTodoContent(content) }; - } - async saveTodoFile(fullPath, dayTodos) { - const content = generateTodoContent(dayTodos); - await fs6.writeFile(fullPath, content, "utf-8"); - } - async getTodo(year, month) { - const { fullPath } = this.getTodoFilePath(year, month); - let dayTodos = []; - try { - await fs6.access(fullPath); - const content = await fs6.readFile(fullPath, "utf-8"); - dayTodos = parseTodoContent(content); - } catch { - } - const now = /* @__PURE__ */ new Date(); - const todayStr = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, "0")}-${now.getDate().toString().padStart(2, "0")}`; - const yesterday = new Date(now); - yesterday.setDate(yesterday.getDate() - 1); - const yesterdayStr = `${yesterday.getFullYear()}-${(yesterday.getMonth() + 1).toString().padStart(2, "0")}-${yesterday.getDate().toString().padStart(2, "0")}`; - if (year === now.getFullYear() && month === now.getMonth() + 1) { - const migrated = this.migrateIncompleteItems(dayTodos, todayStr, yesterdayStr); - if (migrated) { - const newContent = generateTodoContent(dayTodos); - await this.ensureTodoFileExists(fullPath); - await fs6.writeFile(fullPath, newContent, "utf-8"); - } - } - return { dayTodos, year, month }; - } - migrateIncompleteItems(dayTodos, todayStr, yesterdayStr) { - let migrated = false; - const yesterdayTodo = dayTodos.find((d) => d.date === yesterdayStr); - if (yesterdayTodo) { - const incompleteItems = yesterdayTodo.items.filter((item) => !item.completed); - if (incompleteItems.length > 0) { - const todayTodo = dayTodos.find((d) => d.date === todayStr); - if (todayTodo) { - const existingIds = new Set(todayTodo.items.map((i) => i.id)); - const itemsToAdd = incompleteItems.map((item, idx) => ({ - ...item, - id: existingIds.has(item.id) ? `${todayStr}-migrated-${idx}` : item.id - })); - todayTodo.items = [...itemsToAdd, ...todayTodo.items]; - } else { - dayTodos.push({ - date: todayStr, - items: incompleteItems.map((item, idx) => ({ - ...item, - id: `${todayStr}-migrated-${idx}` - })) - }); - } - yesterdayTodo.items = yesterdayTodo.items.filter((item) => item.completed); - if (yesterdayTodo.items.length === 0) { - const index = dayTodos.findIndex((d) => d.date === yesterdayStr); - if (index !== -1) { - dayTodos.splice(index, 1); - } - } - migrated = true; - } - } - return migrated; - } - async saveTodo(year, month, dayTodos) { - const { fullPath } = this.getTodoFilePath(year, month); - await this.ensureTodoFileExists(fullPath); - const content = generateTodoContent(dayTodos); - await fs6.writeFile(fullPath, content, "utf-8"); - } - async addTodo(year, month, date, todoContent) { - const { fullPath } = this.getTodoFilePath(year, month); - await this.ensureTodoFileExists(fullPath); - let fileContent = await fs6.readFile(fullPath, "utf-8"); - const dayTodos = parseTodoContent(fileContent); - const existingDay = dayTodos.find((d) => d.date === date); - if (existingDay) { - const newId = `${date}-${existingDay.items.length}`; - existingDay.items.push({ - id: newId, - content: todoContent, - completed: false - }); - } else { - dayTodos.push({ - date, - items: [{ - id: `${date}-0`, - content: todoContent, - completed: false - }] - }); - } - fileContent = generateTodoContent(dayTodos); - await fs6.writeFile(fullPath, fileContent, "utf-8"); - return dayTodos; - } - async toggleTodo(year, month, date, itemIndex, completed) { - const { fullPath, dayTodos } = await this.loadAndParseTodoFile(year, month); - const day = dayTodos.find((d) => d.date === date); - if (!day || itemIndex >= day.items.length) { - throw new NotFoundError("TODO item not found"); - } - day.items[itemIndex].completed = completed; - await this.saveTodoFile(fullPath, dayTodos); - return dayTodos; - } - async updateTodo(year, month, date, itemIndex, newContent) { - const { fullPath, dayTodos } = await this.loadAndParseTodoFile(year, month); - const day = dayTodos.find((d) => d.date === date); - if (!day || itemIndex >= day.items.length) { - throw new NotFoundError("TODO item not found"); - } - day.items[itemIndex].content = newContent; - await this.saveTodoFile(fullPath, dayTodos); - return dayTodos; - } - async deleteTodo(year, month, date, itemIndex) { - const { fullPath, dayTodos } = await this.loadAndParseTodoFile(year, month); - const dayIndex = dayTodos.findIndex((d) => d.date === date); - if (dayIndex === -1) { - throw new NotFoundError("Day not found"); - } - const day = dayTodos[dayIndex]; - if (itemIndex >= day.items.length) { - throw new NotFoundError("TODO item not found"); - } - day.items.splice(itemIndex, 1); - if (day.items.length === 0) { - dayTodos.splice(dayIndex, 1); - } - await this.saveTodoFile(fullPath, dayTodos); - return dayTodos; - } -}; - -// api/modules/todo/routes.ts -import express6 from "express"; - -// api/modules/todo/schemas.ts -import { z as z3 } from "zod"; -var todoItemSchema = z3.object({ - id: z3.string(), - content: z3.string(), - completed: z3.boolean() -}); -var dayTodoSchema = z3.object({ - date: z3.string(), - items: z3.array(todoItemSchema) -}); -var getTodoQuerySchema = z3.object({ - year: z3.string().optional(), - month: z3.string().optional() -}); -var saveTodoSchema = z3.object({ - year: z3.number().int().positive(), - month: z3.number().int().min(1).max(12), - dayTodos: z3.array(dayTodoSchema) -}); -var addTodoSchema = z3.object({ - year: z3.number().int().positive(), - month: z3.number().int().min(1).max(12), - date: z3.string(), - content: z3.string() -}); -var toggleTodoSchema = z3.object({ - year: z3.number().int().positive(), - month: z3.number().int().min(1).max(12), - date: z3.string(), - itemIndex: z3.number().int().nonnegative(), - completed: z3.boolean() -}); -var updateTodoSchema = z3.object({ - year: z3.number().int().positive(), - month: z3.number().int().min(1).max(12), - date: z3.string(), - itemIndex: z3.number().int().nonnegative(), - content: z3.string() -}); -var deleteTodoSchema = z3.object({ - year: z3.number().int().positive(), - month: z3.number().int().min(1).max(12), - date: z3.string(), - itemIndex: z3.number().int().nonnegative() -}); - -// api/modules/todo/routes.ts -var createTodoRoutes = (deps) => { - const router10 = express6.Router(); - const { todoService: todoService2 } = deps; - router10.get( - "/", - validateQuery(getTodoQuerySchema), - asyncHandler(async (req, res) => { - const year = parseInt(req.query.year) || (/* @__PURE__ */ new Date()).getFullYear(); - const month = parseInt(req.query.month) || (/* @__PURE__ */ new Date()).getMonth() + 1; - const result = await todoService2.getTodo(year, month); - successResponse(res, result); - }) - ); - router10.post( - "/save", - validateBody(saveTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, dayTodos } = req.body; - await todoService2.saveTodo(year, month, dayTodos); - successResponse(res, null); - }) - ); - router10.post( - "/add", - validateBody(addTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, date, content: todoContent } = req.body; - const dayTodos = await todoService2.addTodo(year, month, date, todoContent); - successResponse(res, { dayTodos }); - }) - ); - router10.post( - "/toggle", - validateBody(toggleTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, date, itemIndex, completed } = req.body; - const dayTodos = await todoService2.toggleTodo(year, month, date, itemIndex, completed); - successResponse(res, { dayTodos }); - }) - ); - router10.post( - "/update", - validateBody(updateTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, date, itemIndex, content: newContent } = req.body; - const dayTodos = await todoService2.updateTodo(year, month, date, itemIndex, newContent); - successResponse(res, { dayTodos }); - }) - ); - router10.delete( - "/delete", - validateBody(deleteTodoSchema), - asyncHandler(async (req, res) => { - const { year, month, date, itemIndex } = req.body; - const dayTodos = await todoService2.deleteTodo(year, month, date, itemIndex); - successResponse(res, { dayTodos }); - }) - ); - return router10; -}; -var todoService = new TodoService(); -var routes_default6 = createTodoRoutes({ todoService }); - -// shared/modules/time-tracking/api.ts -var TIME_TRACKING_ENDPOINTS = defineEndpoints({ - current: { path: "/current", method: "GET" }, - event: { path: "/event", method: "POST" }, - day: { path: "/day/:date", method: "GET" }, - week: { path: "/week/:startDate", method: "GET" }, - month: { path: "/month/:yearMonth", method: "GET" }, - year: { path: "/year/:year", method: "GET" }, - stats: { path: "/stats", method: "GET" } -}); - -// shared/modules/time-tracking/index.ts -var TIME_TRACKING_MODULE = defineApiModule({ - id: "time-tracking", - name: "\u65F6\u95F4\u7EDF\u8BA1", - basePath: "/time", - order: 20, - version: "1.0.0", - endpoints: TIME_TRACKING_ENDPOINTS -}); - -// api/modules/time-tracking/sessionPersistence.ts -import path9 from "path"; -var TIME_ROOT = path9.join(NOTEBOOK_ROOT, "time"); - -// api/modules/time-tracking/routes.ts -import express7 from "express"; - -// shared/modules/recycle-bin/api.ts -var RECYCLE_BIN_ENDPOINTS = defineEndpoints({ - list: { path: "/", method: "GET" }, - restore: { path: "/restore", method: "POST" }, - permanent: { path: "/permanent", method: "DELETE" }, - empty: { path: "/empty", method: "DELETE" } -}); - -// shared/modules/recycle-bin/index.ts -var RECYCLE_BIN_MODULE = defineApiModule({ - id: "recycle-bin", - name: "\u56DE\u6536\u7AD9", - basePath: "/recycle-bin", - order: 40, - version: "1.0.0", - endpoints: RECYCLE_BIN_ENDPOINTS -}); - -// api/modules/recycle-bin/routes.ts -import express8 from "express"; -import fs8 from "fs/promises"; -import path11 from "path"; - -// api/modules/recycle-bin/recycleBinService.ts -import fs7 from "fs/promises"; -import path10 from "path"; -async function restoreFile(srcPath, destPath, deletedDate, year, month, day) { - const { fullPath: imagesDir } = resolveNotebookPath(`images/${year}/${month}/${day}`); - let content = await fs7.readFile(srcPath, "utf-8"); - const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g; - let match; - const imageReplacements = []; - while ((match = imageRegex.exec(content)) !== null) { - const imagePath = match[2]; - const imageName = path10.basename(imagePath); - const rbImageName = `${deletedDate}_${imageName}`; - const { fullPath: srcImagePath } = resolveNotebookPath(`RB/${rbImageName}`); - try { - await fs7.access(srcImagePath); - await fs7.mkdir(imagesDir, { recursive: true }); - const destImagePath = path10.join(imagesDir, imageName); - await fs7.rename(srcImagePath, destImagePath); - const newImagePath = `images/${year}/${month}/${day}/${imageName}`; - imageReplacements.push({ oldPath: imagePath, newPath: newImagePath }); - } catch { - } - } - for (const { oldPath, newPath } of imageReplacements) { - content = content.replace(new RegExp(oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newPath); - } - await fs7.writeFile(destPath, content, "utf-8"); - await fs7.unlink(srcPath); -} -async function restoreFolder(srcPath, destPath, deletedDate, year, month, day) { - await fs7.mkdir(destPath, { recursive: true }); - const entries = await fs7.readdir(srcPath, { withFileTypes: true }); - for (const entry of entries) { - const srcEntryPath = path10.join(srcPath, entry.name); - const destEntryPath = path10.join(destPath, entry.name); - if (entry.isDirectory()) { - await restoreFolder(srcEntryPath, destEntryPath, deletedDate, year, month, day); - } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) { - await restoreFile(srcEntryPath, destEntryPath, deletedDate, year, month, day); - } else { - await fs7.rename(srcEntryPath, destEntryPath); - } - } - const remaining = await fs7.readdir(srcPath); - if (remaining.length === 0) { - await fs7.rmdir(srcPath); - } -} - -// api/modules/recycle-bin/routes.ts -var router6 = express8.Router(); -router6.get( - "/", - asyncHandler(async (req, res) => { - const { fullPath: rbDir } = resolveNotebookPath("RB"); - try { - await fs8.access(rbDir); - } catch { - successResponse(res, { groups: [] }); - return; - } - const entries = await fs8.readdir(rbDir, { withFileTypes: true }); - const items = []; - for (const entry of entries) { - const match = entry.name.match(/^(\d{8})_(.+)$/); - if (!match) continue; - const [, dateStr, originalName] = match; - if (entry.isDirectory()) { - items.push({ - name: entry.name, - originalName, - type: "dir", - deletedDate: dateStr, - path: `RB/${entry.name}` - }); - } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) { - items.push({ - name: entry.name, - originalName, - type: "file", - deletedDate: dateStr, - path: `RB/${entry.name}` - }); - } - } - const groupedMap = /* @__PURE__ */ new Map(); - for (const item of items) { - const existing = groupedMap.get(item.deletedDate) || []; - existing.push(item); - groupedMap.set(item.deletedDate, existing); - } - const groups = Array.from(groupedMap.entries()).map(([date, items2]) => ({ - date, - items: items2.sort((a, b) => a.originalName.localeCompare(b.originalName)) - })).sort((a, b) => b.date.localeCompare(a.date)); - successResponse(res, { groups }); - }) -); -router6.post( - "/restore", - asyncHandler(async (req, res) => { - const { path: relPath, type } = req.body; - if (!relPath || !type) { - throw new ValidationError("Path and type are required"); - } - const { fullPath: itemPath } = resolveNotebookPath(relPath); - try { - await fs8.access(itemPath); - } catch { - throw new NotFoundError("Item not found in recycle bin"); - } - const match = path11.basename(itemPath).match(/^(\d{8})_(.+)$/); - if (!match) { - throw new BadRequestError("Invalid recycle bin item name"); - } - const [, dateStr, originalName] = match; - const year = dateStr.substring(0, 4); - const month = dateStr.substring(4, 6); - const day = dateStr.substring(6, 8); - const { fullPath: markdownsDir } = resolveNotebookPath("markdowns"); - await fs8.mkdir(markdownsDir, { recursive: true }); - const destPath = path11.join(markdownsDir, originalName); - const existing = await fs8.stat(destPath).catch(() => null); - if (existing) { - throw new AlreadyExistsError("A file or folder with this name already exists"); - } - if (type === "dir") { - await restoreFolder(itemPath, destPath, dateStr, year, month, day); - } else { - await restoreFile(itemPath, destPath, dateStr, year, month, day); - } - successResponse(res, null); - }) -); -router6.delete( - "/permanent", - asyncHandler(async (req, res) => { - const { path: relPath, type } = req.body; - if (!relPath || !type) { - throw new ValidationError("Path and type are required"); - } - const { fullPath: itemPath } = resolveNotebookPath(relPath); - try { - await fs8.access(itemPath); - } catch { - throw new NotFoundError("Item not found in recycle bin"); - } - if (type === "dir") { - await fs8.rm(itemPath, { recursive: true, force: true }); - } else { - await fs8.unlink(itemPath); - } - successResponse(res, null); - }) -); -router6.delete( - "/empty", - asyncHandler(async (req, res) => { - const { fullPath: rbDir } = resolveNotebookPath("RB"); - try { - await fs8.access(rbDir); - } catch { - successResponse(res, null); - return; - } - const entries = await fs8.readdir(rbDir, { withFileTypes: true }); - for (const entry of entries) { - const entryPath = path11.join(rbDir, entry.name); - if (entry.isDirectory()) { - await fs8.rm(entryPath, { recursive: true, force: true }); - } else { - await fs8.unlink(entryPath); - } - } - successResponse(res, null); - }) -); - -// shared/modules/pydemos/api.ts -var PYDEMOS_ENDPOINTS = defineEndpoints({ - list: { path: "/", method: "GET" }, - create: { path: "/create", method: "POST" }, - delete: { path: "/delete", method: "DELETE" }, - rename: { path: "/rename", method: "POST" } -}); - -// shared/modules/pydemos/index.ts -var PYDEMOS_MODULE = defineApiModule({ - id: "pydemos", - name: "Python Demos", - basePath: "/pydemos", - order: 50, - version: "1.0.0", - endpoints: PYDEMOS_ENDPOINTS -}); - -// api/modules/pydemos/routes.ts -import express9 from "express"; -import fs9 from "fs/promises"; -import path12 from "path"; -import multer from "multer"; - -// api/utils/tempDir.ts -import { existsSync, mkdirSync } from "fs"; -var tempDir = null; -var getTempDir = () => { - if (!tempDir) { - tempDir = PATHS.TEMP_ROOT; - if (!existsSync(tempDir)) { - mkdirSync(tempDir, { recursive: true }); - } - } - return tempDir; -}; - -// api/modules/pydemos/routes.ts -var tempDir2 = getTempDir(); -var upload = multer({ - dest: tempDir2, - limits: { - fileSize: 50 * 1024 * 1024 - } -}); -var toPosixPath2 = (p) => p.replace(/\\/g, "/"); -var getYearPath = (year) => { - const relPath = `pydemos/${year}`; - const { fullPath } = resolveNotebookPath(relPath); - return { relPath, fullPath }; -}; -var getMonthPath = (year, month) => { - const monthStr = month.toString().padStart(2, "0"); - const relPath = `pydemos/${year}/${monthStr}`; - const { fullPath } = resolveNotebookPath(relPath); - return { relPath, fullPath }; -}; -var countFilesInDir = async (dirPath) => { - try { - const entries = await fs9.readdir(dirPath, { withFileTypes: true }); - return entries.filter((e) => e.isFile()).length; - } catch { - return 0; - } -}; -var createPyDemosRoutes = () => { - const router10 = express9.Router(); - router10.get( - "/", - validateQuery(listPyDemosQuerySchema), - asyncHandler(async (req, res) => { - const year = parseInt(req.query.year) || (/* @__PURE__ */ new Date()).getFullYear(); - const { fullPath: yearPath } = getYearPath(year); - const months = []; - try { - await fs9.access(yearPath); - } catch { - successResponse(res, { months }); - return; - } - const monthEntries = await fs9.readdir(yearPath, { withFileTypes: true }); - for (const monthEntry of monthEntries) { - if (!monthEntry.isDirectory()) continue; - const monthNum = parseInt(monthEntry.name); - if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) continue; - const monthPath = path12.join(yearPath, monthEntry.name); - const demoEntries = await fs9.readdir(monthPath, { withFileTypes: true }); - const demos = []; - for (const demoEntry of demoEntries) { - if (!demoEntry.isDirectory()) continue; - const demoPath = path12.join(monthPath, demoEntry.name); - const relDemoPath = `pydemos/${year}/${monthEntry.name}/${demoEntry.name}`; - let created; - try { - const stats = await fs9.stat(demoPath); - created = stats.birthtime.toISOString(); - } catch { - created = (/* @__PURE__ */ new Date()).toISOString(); - } - const fileCount = await countFilesInDir(demoPath); - demos.push({ - name: demoEntry.name, - path: toPosixPath2(relDemoPath), - created, - fileCount - }); - } - demos.sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()); - if (demos.length > 0) { - months.push({ - month: monthNum, - demos - }); - } - } - months.sort((a, b) => a.month - b.month); - successResponse(res, { months }); - }) - ); - router10.post( - "/create", - upload.array("files"), - validateBody(createPyDemoSchema), - asyncHandler(async (req, res) => { - const { name, year, month, folderStructure } = req.body; - const yearNum = parseInt(year); - const monthNum = parseInt(month); - if (!/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(name)) { - throw new ValidationError("Invalid name format"); - } - const { fullPath: monthPath, relPath: monthRelPath } = getMonthPath(yearNum, monthNum); - const demoPath = path12.join(monthPath, name); - const relDemoPath = `${monthRelPath}/${name}`; - try { - await fs9.access(demoPath); - throw new AlreadyExistsError("Demo already exists"); - } catch (err) { - if (isNodeError(err) && err.code === "ENOENT") { - } else if (err instanceof AlreadyExistsError) { - throw err; - } else { - throw err; - } - } - await fs9.mkdir(demoPath, { recursive: true }); - const files = req.files; - let fileCount = 0; - if (files && files.length > 0) { - let structure = {}; - if (folderStructure) { - try { - structure = JSON.parse(folderStructure); - } catch { - structure = {}; - } - } - for (const file of files) { - const relativePath = structure[file.originalname] || file.originalname; - const targetPath = path12.join(demoPath, relativePath); - const targetDir = path12.dirname(targetPath); - await fs9.mkdir(targetDir, { recursive: true }); - await fs9.copyFile(file.path, targetPath); - await fs9.unlink(file.path).catch(() => { - }); - fileCount++; - } - } - successResponse(res, { path: toPosixPath2(relDemoPath), fileCount }); - }) - ); - router10.delete( - "/delete", - validateBody(deletePyDemoSchema), - asyncHandler(async (req, res) => { - const { path: demoPath } = req.body; - if (!demoPath.startsWith("pydemos/")) { - throw new ValidationError("Invalid path"); - } - const { fullPath } = resolveNotebookPath(demoPath); - try { - await fs9.access(fullPath); - } catch { - throw new NotFoundError("Demo not found"); - } - await fs9.rm(fullPath, { recursive: true, force: true }); - successResponse(res, null); - }) - ); - router10.post( - "/rename", - validateBody(renamePyDemoSchema), - asyncHandler(async (req, res) => { - const { oldPath, newName } = req.body; - if (!oldPath.startsWith("pydemos/")) { - throw new ValidationError("Invalid path"); - } - if (!/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(newName)) { - throw new ValidationError("Invalid name format"); - } - const { fullPath: oldFullPath } = resolveNotebookPath(oldPath); - try { - await fs9.access(oldFullPath); - } catch { - throw new NotFoundError("Demo not found"); - } - const parentDir = path12.dirname(oldFullPath); - const newFullPath = path12.join(parentDir, newName); - const newPath = toPosixPath2(path12.join(path12.dirname(oldPath), newName)); - try { - await fs9.access(newFullPath); - throw new AlreadyExistsError("Demo with this name already exists"); - } catch (err) { - if (isNodeError(err) && err.code === "ENOENT") { - } else if (err instanceof AlreadyExistsError) { - throw err; - } else { - throw err; - } - } - await fs9.rename(oldFullPath, newFullPath); - successResponse(res, { newPath }); - }) - ); - return router10; -}; -var routes_default8 = createPyDemosRoutes(); - -// api/modules/document-parser/index.ts -import express12 from "express"; - -// shared/modules/document-parser/index.ts -var DOCUMENT_PARSER_MODULE = defineApiModule({ - id: "document-parser", - name: "Document Parser", - basePath: "/document-parser", - order: 60, - version: "1.0.0", - frontend: { - enabled: false - }, - backend: { - enabled: true - } -}); - -// api/modules/document-parser/blogRoutes.ts -import express10 from "express"; -import path14 from "path"; -import fs11 from "fs/promises"; -import { existsSync as existsSync3 } from "fs"; -import axios from "axios"; - -// api/modules/document-parser/documentParser.ts -import path13 from "path"; -import { spawn } from "child_process"; -import fs10 from "fs/promises"; -import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs"; -if (!existsSync2(TEMP_ROOT)) { - mkdirSync2(TEMP_ROOT, { recursive: true }); -} -var createJobContext = async (prefix) => { - const now = /* @__PURE__ */ new Date(); - const jobDir = path13.join(TEMP_ROOT, `${prefix}_${formatTimestamp(now)}`); - await fs10.mkdir(jobDir, { recursive: true }); - const year = now.getFullYear(); - const month = pad2(now.getMonth() + 1); - const day = pad2(now.getDate()); - const imagesSubDir = `images/${year}/${month}/${day}`; - const destImagesDir = path13.join(NOTEBOOK_ROOT, imagesSubDir); - await fs10.mkdir(destImagesDir, { recursive: true }); - return { jobDir, now, imagesSubDir, destImagesDir }; -}; -var spawnPythonScript = async (options) => { - const { scriptPath, args, cwd, inputContent } = options; - return new Promise((resolve, reject) => { - const pythonProcess = spawn("python", ["-X", "utf8", scriptPath, ...args], { - cwd, - env: { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" } - }); - let stdout = ""; - let stderr = ""; - pythonProcess.stdout.on("data", (data) => { - stdout += data.toString(); - }); - pythonProcess.stderr.on("data", (data) => { - stderr += data.toString(); - }); - pythonProcess.on("close", (code) => { - if (code !== 0) { - logger.error("Python script error:", stderr); - reject(new Error(`Process exited with code ${code}. Error: ${stderr}`)); - } else { - resolve(stdout); - } - }); - pythonProcess.on("error", (err) => { - reject(err); - }); - if (inputContent !== void 0) { - pythonProcess.stdin.write(inputContent); - pythonProcess.stdin.end(); - } - }); -}; -var findImageDestinations = (md) => { - const results = []; - let i = 0; - while (i < md.length) { - const bang = md.indexOf("![", i); - if (bang === -1) break; - const closeBracket = md.indexOf("]", bang + 2); - if (closeBracket === -1) break; - if (md[closeBracket + 1] !== "(") { - i = closeBracket + 1; - continue; - } - const urlStart = closeBracket + 2; - let depth = 1; - let j = urlStart; - for (; j < md.length; j++) { - const ch = md[j]; - if (ch === "(") depth++; - else if (ch === ")") { - depth--; - if (depth === 0) break; - } - } - if (depth !== 0) break; - results.push({ url: md.slice(urlStart, j), start: urlStart, end: j }); - i = j + 1; - } - return results; -}; -var applyReplacements = (md, replacements) => { - const sorted = [...replacements].sort((a, b) => b.start - a.start); - let result = md; - for (const r of sorted) { - result = `${result.slice(0, r.start)}${r.replacement}${result.slice(r.end)}`; - } - return result; -}; -var copyLocalImage = async (src, jobDir, htmlDir, destImagesDir, imagesSubDir, now) => { - const s0 = src.trim().replace(/^<|>$/g, ""); - if (!s0) return null; - let decoded = s0; - try { - decoded = decodeURI(s0); - } catch { - } - const s1 = decoded.replace(/\\/g, "/"); - const s2 = s1.startsWith("./") ? s1.slice(2) : s1; - const candidates = s2.startsWith("/") ? [path13.join(jobDir, s2.slice(1)), path13.join(htmlDir, s2.slice(1))] : [path13.resolve(htmlDir, s2), path13.resolve(jobDir, s2)]; - let foundFile = null; - for (const c of candidates) { - if (existsSync2(c)) { - foundFile = c; - break; - } - } - if (!foundFile) return null; - const ext = path13.extname(foundFile) || ".jpg"; - const baseName = formatTimestamp(now); - const newFilename = await getUniqueFilename(destImagesDir, baseName, ext); - const newPath = path13.join(destImagesDir, newFilename); - await fs10.copyFile(foundFile, newPath); - return { newLink: `/${imagesSubDir}/${newFilename}` }; -}; -var cleanupJob = async (jobDir, additionalPaths = []) => { - await fs10.rm(jobDir, { recursive: true, force: true }).catch(() => { - }); - for (const p of additionalPaths) { - await fs10.unlink(p).catch(() => { - }); - } -}; -var getScriptPath = (toolName, scriptName) => { - return path13.join(PROJECT_ROOT, "tools", toolName, scriptName); -}; -var ensureScriptExists = (scriptPath) => { - return existsSync2(scriptPath); -}; - -// api/modules/document-parser/blogRoutes.ts -var router7 = express10.Router(); -var tempDir3 = getTempDir(); -router7.post( - "/parse-local", - asyncHandler(async (req, res) => { - const { htmlPath, htmlDir, assetsDirName, assetsFiles, targetPath } = req.body; - if (!htmlPath || !htmlDir || !targetPath) { - throw new ValidationError("htmlPath, htmlDir and targetPath are required"); - } - let fullTargetPath; - try { - const resolved = resolveNotebookPath(targetPath); - fullTargetPath = resolved.fullPath; - } catch (error) { - throw error; - } - const scriptPath = getScriptPath("blog", "parse_blog.py"); - if (!ensureScriptExists(scriptPath)) { - throw new InternalError("Parser script not found"); - } - const jobContext = await createJobContext("blog"); - let htmlPathInJob = ""; - try { - htmlPathInJob = path14.join(jobContext.jobDir, "input.html"); - await fs11.copyFile(htmlPath, htmlPathInJob); - if (assetsDirName && assetsFiles && assetsFiles.length > 0) { - const assetsDirPath = path14.join(htmlDir, assetsDirName); - for (const relPath of assetsFiles) { - const srcPath = path14.join(assetsDirPath, relPath); - if (existsSync3(srcPath)) { - const destPath = path14.join(jobContext.jobDir, assetsDirName, relPath); - await fs11.mkdir(path14.dirname(destPath), { recursive: true }); - await fs11.copyFile(srcPath, destPath); - } - } - } - } catch (err) { - await cleanupJob(jobContext.jobDir); - throw err; - } - processHtmlInBackground({ - jobDir: jobContext.jobDir, - htmlPath: htmlPathInJob, - targetPath: fullTargetPath, - cwd: path14.dirname(scriptPath), - jobContext, - originalHtmlDir: htmlDir, - originalAssetsDirName: assetsDirName - }).catch((err) => { - logger.error("Background HTML processing failed:", err); - fs11.writeFile(fullTargetPath, `# \u89E3\u6790\u5931\u8D25 - -> \u9519\u8BEF\u4FE1\u606F: ${err.message}`, "utf-8").catch(() => { - }); - cleanupJob(jobContext.jobDir).catch(() => { - }); - }); - successResponse(res, { - message: "HTML parsing started in background.", - status: "processing" - }); - }) -); -async function processHtmlInBackground(args) { - const { jobDir, htmlPath, targetPath, cwd, jobContext, originalHtmlDir, originalAssetsDirName } = args; - try { - await spawnPythonScript({ - scriptPath: "parse_blog.py", - args: [htmlPath], - cwd - }); - const parsedPathObj = path14.parse(htmlPath); - const markdownPath = path14.join(parsedPathObj.dir, `${parsedPathObj.name}.md`); - if (!existsSync3(markdownPath)) { - throw new Error("Markdown result file not found"); - } - let mdContent = await fs11.readFile(markdownPath, "utf-8"); - const ctx = await jobContext; - const htmlDir = path14.dirname(htmlPath); - const replacements = []; - const destinations = findImageDestinations(mdContent); - for (const dest of destinations) { - const originalSrc = dest.url; - if (!originalSrc) continue; - if (originalSrc.startsWith("http://") || originalSrc.startsWith("https://")) { - try { - const response = await axios.get(originalSrc, { responseType: "arraybuffer", timeout: 1e4 }); - const contentType = response.headers["content-type"]; - let ext = ".jpg"; - if (contentType) { - if (contentType.includes("png")) ext = ".png"; - else if (contentType.includes("gif")) ext = ".gif"; - else if (contentType.includes("webp")) ext = ".webp"; - else if (contentType.includes("svg")) ext = ".svg"; - else if (contentType.includes("jpeg") || contentType.includes("jpg")) ext = ".jpg"; - } - const urlExt = path14.extname(originalSrc.split("?")[0]); - if (urlExt) ext = urlExt; - const baseName = formatTimestamp(ctx.now); - const newFilename = await getUniqueFilename(ctx.destImagesDir, baseName, ext); - const newPath = path14.join(ctx.destImagesDir, newFilename); - await fs11.writeFile(newPath, response.data); - replacements.push({ - start: dest.start, - end: dest.end, - original: originalSrc, - replacement: `/${ctx.imagesSubDir}/${newFilename}` - }); - } catch { - } - continue; - } - if (originalSrc.startsWith("data:")) continue; - let result = await copyLocalImage( - originalSrc, - jobDir, - htmlDir, - ctx.destImagesDir, - ctx.imagesSubDir, - ctx.now - ); - if (!result && originalHtmlDir && originalAssetsDirName) { - const srcWithFiles = originalSrc.replace(/^\.\//, "").replace(/^\//, ""); - const possiblePaths = [ - path14.join(originalHtmlDir, originalAssetsDirName, srcWithFiles), - path14.join(originalHtmlDir, originalAssetsDirName, path14.basename(srcWithFiles)) - ]; - for (const p of possiblePaths) { - if (existsSync3(p)) { - const ext = path14.extname(p) || ".jpg"; - const baseName = formatTimestamp(ctx.now); - const newFilename = await getUniqueFilename(ctx.destImagesDir, baseName, ext); - const newPath = path14.join(ctx.destImagesDir, newFilename); - await fs11.copyFile(p, newPath); - result = { newLink: `/${ctx.imagesSubDir}/${newFilename}` }; - break; - } - } - } - if (result) { - replacements.push({ - start: dest.start, - end: dest.end, - original: originalSrc, - replacement: result.newLink - }); - } - } - mdContent = applyReplacements(mdContent, replacements); - await fs11.writeFile(targetPath, mdContent, "utf-8"); - await fs11.unlink(markdownPath).catch(() => { - }); - } finally { - await cleanupJob(jobDir); - } -} - -// api/modules/document-parser/mineruRoutes.ts -import express11 from "express"; -import multer2 from "multer"; -import path15 from "path"; -import fs12 from "fs/promises"; -import { existsSync as existsSync4 } from "fs"; -var router8 = express11.Router(); -var tempDir4 = getTempDir(); -var upload2 = multer2({ - dest: tempDir4, - limits: { - fileSize: 50 * 1024 * 1024 - } -}); -router8.post( - "/parse", - upload2.single("file"), - asyncHandler(async (req, res) => { - if (!req.file) { - throw new ValidationError("File is required"); - } - const { targetPath } = req.body; - if (!targetPath) { - await fs12.unlink(req.file.path).catch(() => { - }); - throw new ValidationError("Target path is required"); - } - let fullTargetPath; - try { - const resolved = resolveNotebookPath(targetPath); - fullTargetPath = resolved.fullPath; - } catch (error) { - await fs12.unlink(req.file.path).catch(() => { - }); - throw error; - } - const scriptPath = getScriptPath("mineru", "mineru_parser.py"); - if (!ensureScriptExists(scriptPath)) { - await fs12.unlink(req.file.path).catch(() => { - }); - throw new InternalError("Parser script not found"); - } - processPdfInBackground(req.file.path, fullTargetPath, path15.dirname(scriptPath)).catch((err) => { - logger.error("Background PDF processing failed:", err); - fs12.writeFile(fullTargetPath, `# \u89E3\u6790\u5931\u8D25 - -> \u9519\u8BEF\u4FE1\u606F: ${err.message}`, "utf-8").catch(() => { - }); - }); - successResponse(res, { - message: "PDF upload successful. Parsing started in background.", - status: "processing" - }); - }) -); -async function processPdfInBackground(filePath, targetPath, cwd) { - try { - const output = await spawnPythonScript({ - scriptPath: "mineru_parser.py", - args: [filePath], - cwd - }); - const match = output.match(/JSON_RESULT:(.*)/); - if (!match) { - throw new Error("Failed to parse Python script output: JSON_RESULT not found"); - } - const result = JSON.parse(match[1]); - const markdownPath = result.markdown_file; - const outputDir = result.output_dir; - if (!existsSync4(markdownPath)) { - throw new Error("Markdown result file not found"); - } - let mdContent = await fs12.readFile(markdownPath, "utf-8"); - const imagesDir = path15.join(outputDir, "images"); - if (existsSync4(imagesDir)) { - const jobContext = await createJobContext("pdf_images"); - const destinations = findImageDestinations(mdContent); - const replacements = []; - for (const dest of destinations) { - const originalSrc = dest.url; - if (!originalSrc) continue; - const possibleFilenames = [originalSrc, path15.basename(originalSrc)]; - let foundFile = null; - for (const fname of possibleFilenames) { - const localPath = path15.join(imagesDir, fname); - if (existsSync4(localPath)) { - foundFile = localPath; - break; - } - const directPath = path15.join(outputDir, originalSrc); - if (existsSync4(directPath)) { - foundFile = directPath; - break; - } - } - if (foundFile) { - const ext = path15.extname(foundFile); - const baseName = formatTimestamp(jobContext.now); - const newFilename = await getUniqueFilename(jobContext.destImagesDir, baseName, ext); - const newPath = path15.join(jobContext.destImagesDir, newFilename); - await fs12.copyFile(foundFile, newPath); - replacements.push({ - start: dest.start, - end: dest.end, - original: originalSrc, - replacement: `${jobContext.imagesSubDir}/${newFilename}` - }); - } - } - mdContent = applyReplacements(mdContent, replacements); - } - await fs12.writeFile(targetPath, mdContent, "utf-8"); - await fs12.unlink(markdownPath).catch(() => { - }); - if (outputDir && outputDir.includes("temp")) { - await fs12.rm(outputDir, { recursive: true, force: true }).catch(() => { - }); - } - } finally { - await fs12.unlink(filePath).catch(() => { - }); - } -} - -// shared/modules/ai/index.ts -var AI_MODULE = defineApiModule({ - id: "ai", - name: "AI", - basePath: "/ai", - order: 70, - version: "1.0.0", - frontend: { - enabled: false - }, - backend: { - enabled: true - } -}); - -// api/modules/ai/routes.ts -import express13 from "express"; -import { spawn as spawn2 } from "child_process"; -import path16 from "path"; -import { fileURLToPath as fileURLToPath2 } from "url"; -import fs13 from "fs/promises"; -import fsSync from "fs"; -var __filename2 = fileURLToPath2(import.meta.url); -var __dirname2 = path16.dirname(__filename2); -var router9 = express13.Router(); -var PYTHON_TIMEOUT_MS = 3e4; -var spawnPythonWithTimeout = (scriptPath, args, stdinContent, timeoutMs = PYTHON_TIMEOUT_MS) => { - return new Promise((resolve, reject) => { - const pythonProcess = spawn2("python", args, { - env: { ...process.env } - }); - let stdout = ""; - let stderr = ""; - let timeoutId = null; - const cleanup = () => { - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; - } - }; - timeoutId = setTimeout(() => { - cleanup(); - pythonProcess.kill(); - reject(new Error(`Python script timed out after ${timeoutMs}ms`)); - }, timeoutMs); - pythonProcess.stdout.on("data", (data) => { - stdout += data.toString(); - }); - pythonProcess.stderr.on("data", (data) => { - stderr += data.toString(); - }); - pythonProcess.on("close", (code) => { - cleanup(); - if (code !== 0) { - reject(new Error(`Python script exited with code ${code}. Stderr: ${stderr}`)); - } else { - resolve(stdout); - } - }); - pythonProcess.on("error", (err) => { - cleanup(); - reject(new Error(`Failed to start python process: ${err.message}`)); - }); - pythonProcess.stdin.write(stdinContent); - pythonProcess.stdin.end(); - }); -}; -router9.post( - "/doubao", - asyncHandler(async (req, res) => { - const { task, path: relPath } = req.body; - if (!task) throw new ValidationError("Task is required"); - if (!relPath) throw new ValidationError("Path is required"); - const { fullPath } = resolveNotebookPath(relPath); - try { - await fs13.access(fullPath); - } catch { - throw new NotFoundError("File not found"); - } - const content = await fs13.readFile(fullPath, "utf-8"); - const projectRoot = path16.resolve(__dirname2, "..", "..", ".."); - const scriptPath = path16.join(projectRoot, "tools", "doubao", "main.py"); - if (!fsSync.existsSync(scriptPath)) { - throw new InternalError(`Python script not found: ${scriptPath}`); - } - try { - const result = await spawnPythonWithTimeout(scriptPath, ["--task", task], content); - await fs13.writeFile(fullPath, result, "utf-8"); - successResponse(res, { message: "Task completed successfully" }); - } catch (err) { - const message = err instanceof Error ? err.message : "Unknown error"; - throw new InternalError(`AI task failed: ${message}`); - } - }) -); - -// shared/modules/remote/api.ts -var REMOTE_ENDPOINTS = defineEndpoints({ - getConfig: { path: "/config", method: "GET" }, - saveConfig: { path: "/config", method: "POST" }, - getScreenshot: { path: "/screenshot", method: "GET" }, - saveScreenshot: { path: "/screenshot", method: "POST" }, - getData: { path: "/data", method: "GET" }, - saveData: { path: "/data", method: "POST" } -}); - -// shared/modules/remote/index.ts -var REMOTE_MODULE = defineApiModule({ - id: "remote", - name: "\u8FDC\u7A0B", - basePath: "/remote", - order: 25, - version: "1.0.0", - endpoints: REMOTE_ENDPOINTS -}); - -// api/modules/remote/service.ts -import fs14 from "fs/promises"; -import path17 from "path"; -var REMOTE_DIR = "remote"; -var RemoteService = class { - constructor(deps = {}) { - this.deps = deps; - } - getRemoteDir() { - const { fullPath } = resolveNotebookPath(REMOTE_DIR); - return { relPath: REMOTE_DIR, fullPath }; - } - getDeviceDir(deviceName) { - const safeName = this.sanitizeFileName(deviceName); - const { fullPath } = resolveNotebookPath(path17.join(REMOTE_DIR, safeName)); - return { relPath: path17.join(REMOTE_DIR, safeName), fullPath }; - } - sanitizeFileName(name) { - return name.replace(/[<>:"/\\|?*]/g, "_").trim() || "unnamed"; - } - getDeviceConfigPath(deviceName) { - const { fullPath } = this.getDeviceDir(deviceName); - return path17.join(fullPath, "config.json"); - } - getDeviceScreenshotPath(deviceName) { - const { fullPath } = this.getDeviceDir(deviceName); - return path17.join(fullPath, "screenshot.png"); - } - getDeviceDataPath(deviceName) { - const { fullPath } = this.getDeviceDir(deviceName); - return path17.join(fullPath, "data.json"); - } - async ensureDir(dirPath) { - await fs14.mkdir(dirPath, { recursive: true }); - } - async getDeviceNames() { - const { fullPath } = this.getRemoteDir(); - try { - const entries = await fs14.readdir(fullPath, { withFileTypes: true }); - const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name); - return dirs; - } catch { - return []; - } - } - async getConfig() { - const deviceNames = await this.getDeviceNames(); - const devices = await Promise.all( - deviceNames.map(async (name) => { - try { - const configPath = this.getDeviceConfigPath(name); - const content = await fs14.readFile(configPath, "utf-8"); - const deviceConfig = JSON.parse(content); - return { - id: deviceConfig.id || name, - deviceName: name, - serverHost: deviceConfig.serverHost || "", - desktopPort: deviceConfig.desktopPort || 3e3, - gitPort: deviceConfig.gitPort || 3001 - }; - } catch { - return { - id: name, - deviceName: name, - serverHost: "", - desktopPort: 3e3, - gitPort: 3001 - }; - } - }) - ); - return { devices }; - } - async saveConfig(config2) { - const { fullPath: remoteDirFullPath } = this.getRemoteDir(); - await this.ensureDir(remoteDirFullPath); - const existingDevices = await this.getDeviceNames(); - const newDeviceNames = config2.devices.map((d) => this.sanitizeFileName(d.deviceName)); - for (const oldDevice of existingDevices) { - if (!newDeviceNames.includes(oldDevice)) { - try { - const oldDir = path17.join(remoteDirFullPath, oldDevice); - await fs14.rm(oldDir, { recursive: true, force: true }); - } catch { - } - } - } - for (const device of config2.devices) { - const deviceDir = this.getDeviceDir(device.deviceName); - await this.ensureDir(deviceDir.fullPath); - const deviceConfigPath = this.getDeviceConfigPath(device.deviceName); - const deviceConfig = { - id: device.id, - serverHost: device.serverHost, - desktopPort: device.desktopPort, - gitPort: device.gitPort - }; - await fs14.writeFile(deviceConfigPath, JSON.stringify(deviceConfig, null, 2), "utf-8"); - } - } - async getScreenshot(deviceName) { - if (!deviceName) { - return null; - } - const screenshotPath = this.getDeviceScreenshotPath(deviceName); - try { - return await fs14.readFile(screenshotPath); - } catch { - return null; - } - } - async saveScreenshot(dataUrl, deviceName) { - console.log("[RemoteService] saveScreenshot:", { deviceName, dataUrlLength: dataUrl?.length }); - if (!deviceName || deviceName.trim() === "") { - console.warn("[RemoteService] saveScreenshot skipped: no deviceName"); - return; - } - const deviceDir = this.getDeviceDir(deviceName); - await this.ensureDir(deviceDir.fullPath); - const base64Data = dataUrl.replace(/^data:image\/png;base64,/, ""); - const buffer = Buffer.from(base64Data, "base64"); - const screenshotPath = this.getDeviceScreenshotPath(deviceName); - await fs14.writeFile(screenshotPath, buffer); - } - async getData(deviceName) { - if (!deviceName || deviceName.trim() === "") { - return null; - } - const dataPath = this.getDeviceDataPath(deviceName); - try { - const content = await fs14.readFile(dataPath, "utf-8"); - return JSON.parse(content); - } catch { - return null; - } - } - async saveData(data, deviceName) { - if (!deviceName || deviceName.trim() === "") { - console.warn("[RemoteService] saveData skipped: no deviceName"); - return; - } - const deviceDir = this.getDeviceDir(deviceName); - await this.ensureDir(deviceDir.fullPath); - const dataPath = this.getDeviceDataPath(deviceName); - await fs14.writeFile(dataPath, JSON.stringify(data, null, 2), "utf-8"); - } -}; - -// api/modules/remote/routes.ts -import express14 from "express"; -var createRemoteRoutes = (deps) => { - const router10 = express14.Router(); - const { remoteService: remoteService2 } = deps; - router10.get( - "/config", - asyncHandler(async (req, res) => { - const config2 = await remoteService2.getConfig(); - successResponse(res, config2); - }) - ); - router10.post( - "/config", - asyncHandler(async (req, res) => { - const config2 = req.body; - await remoteService2.saveConfig(config2); - successResponse(res, null); - }) - ); - router10.get( - "/screenshot", - asyncHandler(async (req, res) => { - const deviceName = req.query.device; - const buffer = await remoteService2.getScreenshot(deviceName); - if (!buffer) { - return successResponse(res, ""); - } - const base64 = `data:image/png;base64,${buffer.toString("base64")}`; - successResponse(res, base64); - }) - ); - router10.post( - "/screenshot", - asyncHandler(async (req, res) => { - const { dataUrl, deviceName } = req.body; - console.log("[Remote] saveScreenshot called:", { deviceName, hasDataUrl: !!dataUrl }); - await remoteService2.saveScreenshot(dataUrl, deviceName); - successResponse(res, null); - }) - ); - router10.get( - "/data", - asyncHandler(async (req, res) => { - const deviceName = req.query.device; - const data = await remoteService2.getData(deviceName); - successResponse(res, data); - }) - ); - router10.post( - "/data", - asyncHandler(async (req, res) => { - const { deviceName, lastConnected } = req.body; - const data = {}; - if (lastConnected !== void 0) { - data.lastConnected = lastConnected; - } - await remoteService2.saveData(data, deviceName); - successResponse(res, null); - }) - ); - return router10; -}; -var remoteService = new RemoteService(); -var routes_default9 = createRemoteRoutes({ remoteService }); - -// import("./**/*/index.js") in api/modules/index.ts -var globImport_index_js = __glob({}); - -// api/modules/index.ts -var __filename3 = fileURLToPath3(import.meta.url); -var __dirname3 = dirname(__filename3); -var moduleFactoryPattern = /^create\w+Module$/; -async function discoverModules() { - const modules = []; - const entries = readdirSync(__dirname3); - for (const entry of entries) { - const entryPath = join(__dirname3, entry); - try { - const stats = statSync(entryPath); - if (!stats.isDirectory()) { - continue; - } - const moduleIndexPath = join(entryPath, "index.ts"); - let moduleIndexStats; - try { - moduleIndexStats = statSync(moduleIndexPath); - } catch { - continue; - } - if (!moduleIndexStats.isFile()) { - continue; - } - const moduleExports = await globImport_index_js(`./${entry}/index.js`); - for (const exportName of Object.keys(moduleExports)) { - if (moduleFactoryPattern.test(exportName)) { - const factory = moduleExports[exportName]; - if (typeof factory === "function") { - const module = factory(); - modules.push(module); - } - } - } - } catch (error) { - console.warn(`[ModuleLoader] Failed to load module '${entry}':`, error); - } - } - modules.sort((a, b) => { - const orderA = a.metadata.order ?? 0; - const orderB = b.metadata.order ?? 0; - return orderA - orderB; - }); - return modules; -} -var apiModules = await discoverModules(); - -// api/infra/moduleValidator.ts -import { readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync5 } from "fs"; -import { join as join2, dirname as dirname2 } from "path"; -import { fileURLToPath as fileURLToPath4 } from "url"; - -// import("../../shared/modules/**/*/index.js") in api/infra/moduleValidator.ts -var globImport_shared_modules_index_js = __glob({}); - -// api/infra/moduleValidator.ts -var __filename4 = fileURLToPath4(import.meta.url); -var __dirname4 = dirname2(__filename4); -function getSharedModulesPath() { - const possiblePaths = [ - join2(__dirname4, "../../shared/modules"), - join2(__dirname4, "../../../shared/modules"), - join2(process?.resourcesPath || "", "shared/modules") - ]; - for (const p of possiblePaths) { - if (existsSync5(p)) { - return p; - } - } - return null; -} -function needsBackendImplementation(moduleDef) { - return moduleDef.backend?.enabled !== false; -} -async function loadModuleDefinitions() { - const modules = []; - const sharedModulesPath = getSharedModulesPath(); - if (!sharedModulesPath) { - return modules; - } - const entries = readdirSync2(sharedModulesPath); - for (const entry of entries) { - const entryPath = join2(sharedModulesPath, entry); - const stat = statSync2(entryPath); - if (!stat.isDirectory()) { - continue; - } - try { - const moduleExports = await globImport_shared_modules_index_js(`../../shared/modules/${entry}/index.js`); - for (const key of Object.keys(moduleExports)) { - if (key.endsWith("_MODULE")) { - const moduleDef = moduleExports[key]; - if (moduleDef && moduleDef.id) { - modules.push({ - id: moduleDef.id, - name: moduleDef.name, - backend: moduleDef.backend - }); - } - } - } - } catch { - } - } - return modules; -} -async function validateModuleConsistency(apiModules2) { - const sharedModules = await loadModuleDefinitions(); - if (sharedModules.length === 0) { - console.log("[ModuleValidator] Skipping validation (shared modules not found, likely packaged mode)"); - return; - } - const apiModuleIds = new Set(apiModules2.map((m) => m.metadata.id)); - const errors = []; - for (const sharedModule of sharedModules) { - const needsBackend = needsBackendImplementation(sharedModule); - const hasApiModule = apiModuleIds.has(sharedModule.id); - if (needsBackend && !hasApiModule) { - errors.push( - `Module '${sharedModule.id}' is defined in shared but not registered in API modules` - ); - } - if (!needsBackend && hasApiModule) { - errors.push( - `Module '${sharedModule.id}' has backend disabled but is registered in API modules` - ); - } - } - if (errors.length > 0) { - throw new Error(`Module consistency validation failed: - - ${errors.join("\n - ")}`); - } - console.log( - `[ModuleValidator] \u2713 Module consistency validated: ${sharedModules.length} shared, ${apiModules2.length} API` - ); -} - -// api/app.ts -import path18 from "path"; -import fs15 from "fs"; -dotenv.config(); -var app = express15(); -var container = new ServiceContainer(); -var moduleManager = new ModuleManager(container); -app.use(cors()); -app.use(express15.json({ limit: "200mb" })); -app.use(express15.urlencoded({ extended: true, limit: "200mb" })); -app.use("/api/files", routes_default); -app.use("/api/events", routes_default2); -app.use("/api/settings", routes_default3); -app.use("/api/upload", routes_default4); -app.use("/api/search", routes_default5); -for (const module of apiModules) { - await moduleManager.register(module); -} -await validateModuleConsistency(apiModules); -for (const module of moduleManager.getAllModules()) { - await moduleManager.activate(module.metadata.id); - const router10 = await module.createRouter(container); - app.use("/api" + module.metadata.basePath, router10); -} -app.get("/background.png", (req, res, next) => { - const customBgPath = path18.join(NOTEBOOK_ROOT, ".config", "background.png"); - if (fs15.existsSync(customBgPath)) { - res.sendFile(customBgPath); - } else { - next(); - } -}); -app.use( - "/api/health", - (_req, res) => { - const response = { - success: true, - data: { message: "ok" } - }; - res.status(200).json(response); - } -); -app.use((req, res, next) => { - if (req.path.startsWith("/api")) { - const response = { - success: false, - error: { code: "NOT_FOUND", message: "API\u4E0D\u5B58\u5728" } - }; - res.status(404).json(response); - } else { - next(); - } -}); -app.use(errorHandler); -var app_default = app; - -// electron/server.ts -import path20 from "path"; -import express16 from "express"; -import { fileURLToPath as fileURLToPath5 } from "url"; - -// api/watcher/watcher.ts -import chokidar from "chokidar"; -import path19 from "path"; -var watcher = null; -var startWatcher = () => { - if (watcher) return; - logger.info(`Starting file watcher for: ${NOTEBOOK_ROOT}`); - watcher = chokidar.watch(NOTEBOOK_ROOT, { - ignored: /(^|[\/\\])\../, - persistent: true, - ignoreInitial: true - }); - const broadcast = (event, changedPath) => { - const rel = path19.relative(NOTEBOOK_ROOT, changedPath); - if (!rel || rel.startsWith("..") || path19.isAbsolute(rel)) return; - logger.info(`File event: ${event} - ${rel}`); - eventBus.broadcast({ event, path: toPosixPath(rel) }); - }; - watcher.on("add", (p) => broadcast("add", p)).on("change", (p) => broadcast("change", p)).on("unlink", (p) => broadcast("unlink", p)).on("addDir", (p) => broadcast("addDir", p)).on("unlinkDir", (p) => broadcast("unlinkDir", p)).on("ready", () => logger.info("File watcher ready")).on("error", (err) => logger.error("File watcher error:", err)); -}; - -// electron/server.ts -var __filename5 = fileURLToPath5(import.meta.url); -var __dirname5 = path20.dirname(__filename5); -startWatcher(); -var distPath = path20.join(__dirname5, "../dist"); -app_default.use(express16.static(distPath)); -app_default.get("*", (req, res) => { - res.sendFile(path20.join(distPath, "index.html")); -}); -var startServer = () => { - return new Promise((resolve, reject) => { - const server = app_default.listen(0, () => { - const address = server.address(); - const port = address.port; - logger.info(`Electron internal server running on port ${port}`); - resolve(port); - }); - server.on("error", (err) => { - logger.error("Failed to start server:", err); - reject(err); - }); - }); -}; -export { - startServer -}; diff --git a/dist-api/time-tracking-5F7NPQRY.js b/dist-api/time-tracking-5F7NPQRY.js deleted file mode 100644 index ef43b82..0000000 --- a/dist-api/time-tracking-5F7NPQRY.js +++ /dev/null @@ -1,28 +0,0 @@ -import { - HeartbeatService, - SessionPersistenceService, - TimeTrackerService, - createHeartbeatService, - createSessionPersistence, - createTimeTrackingModule, - createTimeTrackingRoutes, - getTimeTrackerService, - initializeTimeTrackerService, - initializeTimeTrackerServiceWithDependencies, - time_tracking_default -} from "./chunk-QS2CMBFP.js"; -import "./chunk-47DJ6YUB.js"; -import "./chunk-74TMTGBG.js"; -export { - HeartbeatService, - SessionPersistenceService, - TimeTrackerService, - createHeartbeatService, - createSessionPersistence, - createTimeTrackingModule, - createTimeTrackingRoutes, - time_tracking_default as default, - getTimeTrackerService, - initializeTimeTrackerService, - initializeTimeTrackerServiceWithDependencies -}; diff --git a/dist-api/todo-GDTZZEQA.js b/dist-api/todo-GDTZZEQA.js deleted file mode 100644 index f421ab3..0000000 --- a/dist-api/todo-GDTZZEQA.js +++ /dev/null @@ -1,33 +0,0 @@ -import { - TodoService, - addTodoSchema, - createTodoModule, - createTodoRoutes, - createTodoService, - deleteTodoSchema, - generateTodoContent, - getTodoQuerySchema, - parseTodoContent, - saveTodoSchema, - todo_default, - toggleTodoSchema, - updateTodoSchema -} from "./chunk-V2OWYGQG.js"; -import "./chunk-5EGA6GHY.js"; -import "./chunk-ER4KPD22.js"; -import "./chunk-74TMTGBG.js"; -export { - TodoService, - addTodoSchema, - createTodoModule, - createTodoRoutes, - createTodoService, - todo_default as default, - deleteTodoSchema, - generateTodoContent, - getTodoQuerySchema, - parseTodoContent, - saveTodoSchema, - toggleTodoSchema, - updateTodoSchema -}; diff --git a/remote/xcopencodeweb/README.md b/remote/xcopencodeweb/README.md deleted file mode 100644 index afb0778..0000000 --- a/remote/xcopencodeweb/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# XCOpenCodeWeb - -XCOpenCodeWeb 是一个基于 Web 的 AI 编程助手界面,用于与 OpenCode 服务器交互。 - -## 快速开始 - -### 使用单文件 exe(推荐) - -直接下载 `XCOpenCodeWeb.exe`,双击运行: - -```bash -# 默认端口 3000 -XCOpenCodeWeb.exe - -# 指定端口 -XCOpenCodeWeb.exe --port 8080 - -# 查看帮助 -XCOpenCodeWeb.exe --help -``` - -启动后访问 http://localhost:3000 - -### 从源码运行 - -需要安装 [Bun](https://bun.sh) 或 Node.js 20+ - -```bash -# 安装依赖 -bun install - -# 构建前端 -bun run build - -# 启动服务器 -bun server/index.js --port 3000 -``` - -## 构建单文件 exe - -```bash -cd web -bun run build:exe -``` - -输出:`web/XCOpenCodeWeb.exe`(约 320MB) - -详细文档:[docs/single-file-exe-build.md](docs/single-file-exe-build.md) - -## 项目结构 - -``` -├── ui/ # 前端组件库 -├── web/ -│ ├── src/ # 前端源码 -│ ├── server/ # 后端服务器 -│ ├── bin/ # CLI 工具 -│ └── dist/ # 构建输出 -├── docs/ # 文档 -└── AGENTS.md # AI Agent 参考文档 -``` - -## 常用命令 - -```bash -# 开发模式 -bun run dev # 前端热更新 -bun run dev:server # 启动开发服务器 - -# 构建 -bun run build # 构建前端 -bun run build:exe # 构建单文件 exe - -# 代码检查 -bun run type-check:web # TypeScript 类型检查 -bun run lint:web # ESLint 检查 -``` - -## 依赖 - -- [Bun](https://bun.sh) - 运行时和打包工具 -- [React](https://react.dev) - 前端框架 -- [Express](https://expressjs.com) - 后端服务器 -- [Tailwind CSS](https://tailwindcss.com) - 样式框架 - -## 配置 - -### 环境变量 - -| 变量 | 说明 | -|------|------| -| `XCOpenCodeWeb_PORT` | 服务器端口(默认 3000) | -| `OPENCODE_HOST` | 外部 OpenCode 服务器地址 | -| `OPENCODE_PORT` | 外部 OpenCode 端口 | -| `OPENCODE_SKIP_START` | 跳过启动 OpenCode | - -## 许可证 - -MIT \ No newline at end of file diff --git a/remote/xcopencodeweb/XCOpenCodeWeb.exe b/remote/xcopencodeweb/XCOpenCodeWeb.exe deleted file mode 100644 index e9377dc..0000000 Binary files a/remote/xcopencodeweb/XCOpenCodeWeb.exe and /dev/null differ diff --git a/service/xcopencodeweb/README.md b/service/xcopencodeweb/README.md deleted file mode 100644 index afb0778..0000000 --- a/service/xcopencodeweb/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# XCOpenCodeWeb - -XCOpenCodeWeb 是一个基于 Web 的 AI 编程助手界面,用于与 OpenCode 服务器交互。 - -## 快速开始 - -### 使用单文件 exe(推荐) - -直接下载 `XCOpenCodeWeb.exe`,双击运行: - -```bash -# 默认端口 3000 -XCOpenCodeWeb.exe - -# 指定端口 -XCOpenCodeWeb.exe --port 8080 - -# 查看帮助 -XCOpenCodeWeb.exe --help -``` - -启动后访问 http://localhost:3000 - -### 从源码运行 - -需要安装 [Bun](https://bun.sh) 或 Node.js 20+ - -```bash -# 安装依赖 -bun install - -# 构建前端 -bun run build - -# 启动服务器 -bun server/index.js --port 3000 -``` - -## 构建单文件 exe - -```bash -cd web -bun run build:exe -``` - -输出:`web/XCOpenCodeWeb.exe`(约 320MB) - -详细文档:[docs/single-file-exe-build.md](docs/single-file-exe-build.md) - -## 项目结构 - -``` -├── ui/ # 前端组件库 -├── web/ -│ ├── src/ # 前端源码 -│ ├── server/ # 后端服务器 -│ ├── bin/ # CLI 工具 -│ └── dist/ # 构建输出 -├── docs/ # 文档 -└── AGENTS.md # AI Agent 参考文档 -``` - -## 常用命令 - -```bash -# 开发模式 -bun run dev # 前端热更新 -bun run dev:server # 启动开发服务器 - -# 构建 -bun run build # 构建前端 -bun run build:exe # 构建单文件 exe - -# 代码检查 -bun run type-check:web # TypeScript 类型检查 -bun run lint:web # ESLint 检查 -``` - -## 依赖 - -- [Bun](https://bun.sh) - 运行时和打包工具 -- [React](https://react.dev) - 前端框架 -- [Express](https://expressjs.com) - 后端服务器 -- [Tailwind CSS](https://tailwindcss.com) - 样式框架 - -## 配置 - -### 环境变量 - -| 变量 | 说明 | -|------|------| -| `XCOpenCodeWeb_PORT` | 服务器端口(默认 3000) | -| `OPENCODE_HOST` | 外部 OpenCode 服务器地址 | -| `OPENCODE_PORT` | 外部 OpenCode 端口 | -| `OPENCODE_SKIP_START` | 跳过启动 OpenCode | - -## 许可证 - -MIT \ No newline at end of file diff --git a/service/xcopencodeweb/XCOpenCodeWeb.exe b/service/xcopencodeweb/XCOpenCodeWeb.exe deleted file mode 100644 index e9377dc..0000000 Binary files a/service/xcopencodeweb/XCOpenCodeWeb.exe and /dev/null differ