From fd83a4101c328d3e69ead2dfad0c2f5e92732616 Mon Sep 17 00:00:00 2001 From: Brian Date: Fri, 23 Jan 2026 00:18:03 -0500 Subject: [PATCH] fix: correctly resolve model folder paths in URLModelHandler Signed-off-by: Brian --- .../src/managers/modelsManager.spec.ts | 53 +++++++++++++++++-- .../backend/src/models/URLModelHandler.ts | 6 +-- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/managers/modelsManager.spec.ts b/packages/backend/src/managers/modelsManager.spec.ts index 9d7a3dd34..5c429dd81 100644 --- a/packages/backend/src/managers/modelsManager.spec.ts +++ b/packages/backend/src/managers/modelsManager.spec.ts @@ -234,7 +234,7 @@ test('getModelsInfo should get models in local directory', async () => { file: { size: 32000, creation: now, - path: path.resolve(dirent[0].parentPath, dirent[0].name), + path: path.resolve(modelsDir, dirent[0].name), file: 'model-id-1-model', }, }, @@ -244,13 +244,55 @@ test('getModelsInfo should get models in local directory', async () => { file: { size: 32000, creation: now, - path: path.resolve(dirent[1].parentPath, dirent[1].name), + path: path.resolve(modelsDir, dirent[1].name), file: 'model-id-2-model', }, }, ]); }); +test('URLModelHandler.getLocalModelsFromDisk should work with undefined parentPath', async () => { + const modelsDir = 'models'; + const manager = { + getModelInfo: vi.fn(id => ({ id, name: `${id}-model` }) as ModelInfo), + } as unknown as ModelsManager; + + const handler = new URLModelHandler(manager, modelsDir); + + vi.spyOn(fs, 'existsSync').mockReturnValue(true); + + const mockReaddir = vi.fn().mockImplementation(async path => { + if (path === modelsDir) { + return [ + { + isDirectory: () => true, + parentPath: undefined, // This simulates the issue + name: 'test-model', + isFile: () => false, + isBlockDevice: () => false, + isCharacterDevice: () => false, + isSymbolicLink: () => false, + isFIFO: () => false, + isSocket: () => false, + } as unknown as fs.Dirent, + ]; + } + return ['test-model.gguf']; + }); + + vi.spyOn(fs.promises, 'readdir').mockImplementation(mockReaddir); + vi.spyOn(fs.promises, 'stat').mockResolvedValue({ + size: 1000, + mtime: new Date(), + } as fs.Stats); + + // Should not throw error even with undefined parentPath + await expect(handler.getLocalModelsFromDisk()).resolves.not.toThrow(); + + // Verify the model was updated with correct path + expect(manager.getModelInfo).toHaveBeenCalledWith('test-model'); +}); + test('getModelsInfo should return an empty array if the models folder does not exist', async () => { vi.spyOn(os, 'homedir').mockReturnValue('/home/user'); const existsSyncSpy = vi.spyOn(fs, 'existsSync'); @@ -327,7 +369,7 @@ test('getLocalModelsFromDisk should return undefined Date and size when stat fai file: { size: undefined, creation: undefined, - path: path.resolve(dirent[0].parentPath, dirent[0].name), + path: path.resolve(modelsDir, dirent[0].name), file: 'model-id-1-model', }, }, @@ -425,7 +467,7 @@ test('loadLocalModels should post a message with the message on disk and on cata creation: now, file: 'model-id-1-model', size: 32000, - path: path.resolve(dirent[0].parentPath, dirent[0].name), + path: path.resolve(modelsDir, dirent[0].name), }, id: 'model-id-1', }, @@ -552,8 +594,9 @@ describe('deleting models', () => { creation: now, file: 'model-id-1-model', size: 32000, - path: path.resolve(dirent[0].parentPath, dirent[0].name), + path: path.resolve(modelsDir, dirent[0].name), }, + state: undefined, }, ]); expect(mocks.showErrorMessageMock).toHaveBeenCalledOnce(); diff --git a/packages/backend/src/models/URLModelHandler.ts b/packages/backend/src/models/URLModelHandler.ts index 24100c13a..e2deb4167 100644 --- a/packages/backend/src/models/URLModelHandler.ts +++ b/packages/backend/src/models/URLModelHandler.ts @@ -60,13 +60,13 @@ export class URLModelHandler extends ModelHandler { const entries = await fs.promises.readdir(this.modelsDir, { withFileTypes: true }); const dirs = entries.filter(dir => dir.isDirectory()); for (const d of dirs) { - const modelEntries = await fs.promises.readdir(resolve(d.parentPath, d.name)); + const modelEntries = await fs.promises.readdir(resolve(this.modelsDir, d.name)); if (modelEntries.length !== 1) { // we support models with one file only for now continue; } const modelFile = modelEntries[0]; - const fullPath = resolve(d.parentPath, d.name, modelFile); + const fullPath = resolve(this.modelsDir, d.name, modelFile); // Check for corresponding models or tmp file that should be ignored try { @@ -84,7 +84,7 @@ export class URLModelHandler extends ModelHandler { model.file = { file: modelFile, - path: resolve(d.parentPath, d.name), + path: resolve(this.modelsDir, d.name), size: info.size, creation: info.mtime, };