diff --git a/packages/shared/src/features.ts b/packages/shared/src/features.ts new file mode 100644 index 00000000..e6be16e2 --- /dev/null +++ b/packages/shared/src/features.ts @@ -0,0 +1,82 @@ +export interface FeatureBundleInfo { + id: string; + name: string; + description: string; + estimatedSize: string; + enablesTools: string[]; +} + +export type FeatureStatus = "not_installed" | "installing" | "installed" | "error"; + +export interface FeatureBundleState { + id: string; + name: string; + description: string; + status: FeatureStatus; + installedVersion: string | null; + estimatedSize: string; + enablesTools: string[]; + progress: { percent: number; stage: string } | null; + error: string | null; +} + +export const FEATURE_BUNDLES: Record = { + "background-removal": { + id: "background-removal", + name: "Background Removal", + description: "Remove image backgrounds with AI", + estimatedSize: "700 MB - 1 GB", + enablesTools: ["remove-background", "passport-photo"], + }, + "face-detection": { + id: "face-detection", + name: "Face Detection", + description: "Detect and blur faces, fix red-eye, smart crop", + estimatedSize: "200-300 MB", + enablesTools: ["blur-faces", "red-eye-removal", "smart-crop"], + }, + "object-eraser-colorize": { + id: "object-eraser-colorize", + name: "Object Eraser & Colorize", + description: "Erase objects from photos and colorize B&W images", + estimatedSize: "600-800 MB", + enablesTools: ["erase-object", "colorize"], + }, + "upscale-enhance": { + id: "upscale-enhance", + name: "Upscale & Enhance", + description: "AI upscaling, face enhancement, and noise removal", + estimatedSize: "4-5 GB", + enablesTools: ["upscale", "enhance-faces", "noise-removal"], + }, + "photo-restoration": { + id: "photo-restoration", + name: "Photo Restoration", + description: "Restore old or damaged photos", + estimatedSize: "800 MB - 1 GB", + enablesTools: ["restore-photo"], + }, + ocr: { + id: "ocr", + name: "OCR", + description: "Extract text from images", + estimatedSize: "3-4 GB", + enablesTools: ["ocr"], + }, +}; + +export const TOOL_BUNDLE_MAP: Record = {}; +for (const [bundleId, bundle] of Object.entries(FEATURE_BUNDLES)) { + for (const toolId of bundle.enablesTools) { + TOOL_BUNDLE_MAP[toolId] = bundleId; + } +} + +export function getBundleForTool(toolId: string): FeatureBundleInfo | null { + const bundleId = TOOL_BUNDLE_MAP[toolId]; + return bundleId ? FEATURE_BUNDLES[bundleId] : null; +} + +export function getToolsForBundle(bundleId: string): string[] { + return FEATURE_BUNDLES[bundleId]?.enablesTools ?? []; +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index b37f5bbe..848984c6 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,4 +1,5 @@ export * from "./constants.js"; +export * from "./features.js"; export * from "./i18n/index.js"; export * from "./permissions.js"; export * from "./types.js"; diff --git a/tests/unit/features.test.ts b/tests/unit/features.test.ts new file mode 100644 index 00000000..e883a8c7 --- /dev/null +++ b/tests/unit/features.test.ts @@ -0,0 +1,46 @@ +import { + FEATURE_BUNDLES, + getBundleForTool, + getToolsForBundle, + PYTHON_SIDECAR_TOOLS, + TOOL_BUNDLE_MAP, +} from "@ashim/shared"; +import { describe, expect, it } from "vitest"; + +describe("Feature bundles", () => { + it("every PYTHON_SIDECAR_TOOL maps to exactly one bundle", () => { + for (const toolId of PYTHON_SIDECAR_TOOLS) { + const bundle = getBundleForTool(toolId); + expect(bundle, `${toolId} has no bundle`).toBeDefined(); + } + }); + + it("getBundleForTool returns null for non-AI tools", () => { + expect(getBundleForTool("resize")).toBeNull(); + expect(getBundleForTool("crop")).toBeNull(); + }); + + it("getToolsForBundle returns correct tools", () => { + const tools = getToolsForBundle("background-removal"); + expect(tools).toContain("remove-background"); + expect(tools).toContain("passport-photo"); + expect(tools).not.toContain("upscale"); + }); + + it("all 6 bundles are defined", () => { + expect(Object.keys(FEATURE_BUNDLES)).toHaveLength(6); + expect(FEATURE_BUNDLES["background-removal"]).toBeDefined(); + expect(FEATURE_BUNDLES["face-detection"]).toBeDefined(); + expect(FEATURE_BUNDLES["object-eraser-colorize"]).toBeDefined(); + expect(FEATURE_BUNDLES["upscale-enhance"]).toBeDefined(); + expect(FEATURE_BUNDLES["photo-restoration"]).toBeDefined(); + expect(FEATURE_BUNDLES["ocr"]).toBeDefined(); + }); + + it("TOOL_BUNDLE_MAP covers all sidecar tools", () => { + const mappedTools = Object.keys(TOOL_BUNDLE_MAP); + for (const toolId of PYTHON_SIDECAR_TOOLS) { + expect(mappedTools, `${toolId} missing from TOOL_BUNDLE_MAP`).toContain(toolId); + } + }); +});