Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions packages/backend/src/managers/catalogManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ beforeEach(async () => {
describe('invalid user catalog', () => {
beforeEach(async () => {
vi.mocked(promises.readFile).mockResolvedValue('invalid json');
catalogManager.init();
await catalogManager.init();
});

test('expect correct model is returned with valid id', () => {
Expand All @@ -116,7 +116,7 @@ describe('invalid user catalog', () => {

test('expect correct model is returned from default catalog with valid id when no user catalog exists', async () => {
vi.mocked(existsSync).mockReturnValue(false);
catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getRecipes().length > 0);

const model = catalogManager.getModelById('llama-2-7b-chat.Q5_K_S');
Expand All @@ -132,7 +132,7 @@ test('expect correct model is returned with valid id when the user catalog is va
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));

const model = catalogManager.getModelById('model1');
Expand All @@ -146,7 +146,7 @@ test('expect to call writeFile in addLocalModelsToCatalog with catalog updated',
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getRecipes().length > 0);

const mtimeDate = new Date('2024-04-03T09:51:15.766Z');
Expand Down Expand Up @@ -174,7 +174,7 @@ test('expect to call writeFile in removeLocalModelFromCatalog with catalog updat
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
vi.mocked(path.resolve).mockReturnValue('path');

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getRecipes().length > 0);

vi.mocked(promises.writeFile).mockResolvedValue();
Expand All @@ -196,7 +196,7 @@ test('catalog should be the combination of user catalog and default catalog', as
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
vi.mocked(path.resolve).mockReturnValue('path');

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getModels().length > userContent.models.length);

const mtimeDate = new Date('2024-04-03T09:51:15.766Z');
Expand Down Expand Up @@ -238,7 +238,7 @@ test('catalog should use user items in favour of default', async () => {

vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(overwriteFullCatalog));

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getModels().length > 0);

const mtimeDate = new Date('2024-04-03T09:51:15.766Z');
Expand Down Expand Up @@ -330,7 +330,7 @@ test('filter recipes by language', async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));
const result1 = catalogManager.filterRecipes({
languages: ['lang1'],
Expand Down Expand Up @@ -375,7 +375,7 @@ test('filter recipes by tool', async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));

const result1 = catalogManager.filterRecipes({
Expand Down Expand Up @@ -445,7 +445,7 @@ test('filter recipes by framework', async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));

const result1 = catalogManager.filterRecipes({
Expand Down Expand Up @@ -519,7 +519,7 @@ test('filter recipes by language and framework', async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));

const result1 = catalogManager.filterRecipes({
Expand All @@ -546,7 +546,7 @@ test('filter recipes by language, tool and framework', async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));

catalogManager.init();
await catalogManager.init();
await vi.waitUntil(() => catalogManager.getModels().some(model => model.id === 'model1'));

const result1 = catalogManager.filterRecipes({
Expand All @@ -567,3 +567,14 @@ test('filter recipes by language, tool and framework', async () => {
tools: [{ name: 'tool1', count: 1 }],
});
});

test('models are loaded as soon as init is finished when no user catalog', async () => {
await catalogManager.init();
expect(catalogManager.getModels()).toHaveLength(3);
});

test('models are loaded as soon as init is finished when user catalog exists', async () => {
vi.mocked(promises.readFile).mockResolvedValue(JSON.stringify(userContent));
await catalogManager.init();
expect(catalogManager.getModels()).toHaveLength(5);
});
17 changes: 16 additions & 1 deletion packages/backend/src/managers/catalogManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class CatalogManager extends Publisher<ApplicationCatalog> implements Dis
/**
* The init method will start a watcher on the user catalog.json
*/
init(): void {
async init(): Promise<void> {
// Creating a json watcher
this.#jsonWatcher = new JsonWatcher(this.getUserCatalogPath(), {
version: CatalogFormat.CURRENT,
Expand All @@ -70,6 +70,21 @@ export class CatalogManager extends Publisher<ApplicationCatalog> implements Dis
});
this.#jsonWatcher.onContentUpdated(content => this.onUserCatalogUpdate(content));
this.#jsonWatcher.init();

await this.readCatalogs();
}
Copy link
Contributor

@axel7083 axel7083 May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async init(): Promise<void> {
  return new Promise<void>((resolve) => {
    // Creating a json watcher
    this.#jsonWatcher = new JsonWatcher(this.getUserCatalogPath(), {
      version: CatalogFormat.CURRENT,
      recipes: [],
      models: [],
      categories: [],
    });
    
    this.#jsonWatcher.onContentUpdated(content => {
      resolve();
      this.onUserCatalogUpdate(content);
    });
    this.#jsonWatcher.init();
  });
}

can't we just wrap the JSON watcher instead of reading manually the file??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, great idea. Just moving resolve() after onSuerCatalogUpdate()


// read user and default catalog
private async readCatalogs(): Promise<void> {
let content: unknown;
try {
const str = await promises.readFile(this.getUserCatalogPath(), 'utf8');
content = JSON.parse(str);
} catch {
// Ignore all errors at this time, errors will be caught from JSON watcher event
content = {};
}
this.onUserCatalogUpdate(content);
}

private loadDefaultCatalog(): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export class Studio {
* Create catalog manager, responsible for loading the catalog files and watching for changes
*/
this.#catalogManager = new CatalogManager(this.#rpcExtension, appUserDirectory);
this.#catalogManager.init();
await this.#catalogManager.init();

/**
* The builder manager is handling the building tasks, create corresponding tasks
Expand Down
Loading