Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add openDirectory api #122

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
15 changes: 11 additions & 4 deletions packages/backend/src/managers/modelsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import fs from 'fs';
import * as path from 'node:path';

export class ModelsManager {
constructor(private appUserDirectory: string) {}
private readonly modelsDir: string;

constructor(private appUserDirectory: string) {
this.modelsDir = path.join(this.appUserDirectory, 'models');
}

getModelsDirectory(): string {
return this.modelsDir;
}

getLocalModels(): LocalModelInfo[] {
const result: LocalModelInfo[] = [];
const modelsDir = path.join(this.appUserDirectory, 'models');
if (!fs.existsSync(modelsDir)) {
if (!fs.existsSync(this.modelsDir)) {
return [];
}
const entries = fs.readdirSync(modelsDir, { withFileTypes: true });
const entries = fs.readdirSync(this.modelsDir, { withFileTypes: true });
const dirs = entries.filter(dir => dir.isDirectory());
for (const d of dirs) {
const modelEntries = fs.readdirSync(path.resolve(d.path, d.name));
Expand Down
1 change: 0 additions & 1 deletion packages/backend/src/studio-api-impl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ beforeEach(async () => {

// Creating StudioApiImpl
studioApiImpl = new StudioApiImpl(
appUserDirectory,
{} as unknown as ApplicationManager,
{} as unknown as RecipeStatusRegistry,
{} as unknown as PlayGroundManager,
Expand Down
10 changes: 8 additions & 2 deletions packages/backend/src/studio-api-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import type { LocalModelInfo } from '@shared/src/models/ILocalModelInfo';

export class StudioApiImpl implements StudioAPI {
constructor(
private appUserDirectory: string,
private applicationManager: ApplicationManager,
private recipeStatusRegistry: RecipeStatusRegistry,
private playgroundManager: PlayGroundManager,
Expand Down Expand Up @@ -101,7 +100,7 @@ export class StudioApiImpl implements StudioAPI {
}

// TODO: we need to stop doing that.
const modelPath = path.resolve(this.appUserDirectory, 'models', modelId, localModelInfo[0].file);
const modelPath = path.resolve(this.modelsManager.getModelsDirectory(), modelId, localModelInfo[0].file);

await this.playgroundManager.startPlayground(modelId, modelPath);
}
Expand Down Expand Up @@ -129,4 +128,11 @@ export class StudioApiImpl implements StudioAPI {
async getCatalog(): Promise<Catalog> {
return this.catalogManager.getCatalog();
}

async getModelsDirectory(): Promise<string> {
return this.modelsManager.getModelsDirectory();
}
async openDirectory(path: string): Promise<void> {
void podmanDesktopApi.env.openExternal(podmanDesktopApi.Uri.file(path));
}
}
1 change: 0 additions & 1 deletion packages/backend/src/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ export class Studio {

// Creating StudioApiImpl
this.studioApi = new StudioApiImpl(
appUserDirectory,
applicationManager,
recipeStatusRegistry,
this.playgroundManager,
Expand Down
68 changes: 68 additions & 0 deletions packages/frontend/src/pages/Models.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import '@testing-library/jest-dom/vitest';
import { vi, test, expect } from 'vitest';
import { screen, fireEvent, render, waitFor } from '@testing-library/svelte';
import Models from '/@/pages/Models.svelte';

const mocks = vi.hoisted(() => {
return {
getModelsDirectoryMock: vi.fn(),
openDirectoryMock: vi.fn(),
localModelsMock: {
subscribe: (_f: (msg: any) => void) => {
return () => {};
},
},
modelsPullingSubscribeMock: vi.fn(),
modelsPullingMock: {
subscribe: (f: (msg: any) => void) => {
f(mocks.modelsPullingSubscribeMock());
return () => {};
},
},
};
});

vi.mock('../utils/client', async () => {
return {
studioClient: {
getModelsDirectory: mocks.getModelsDirectoryMock,
openDirectory: mocks.openDirectoryMock,
},
rpcBrowser: {
subscribe: () => {
return {
unsubscribe: () => {},
};
},
},
};
});

vi.mock('../stores/local-models', async () => {
return {
localModels: mocks.localModelsMock,
};
});

vi.mock('../stores/recipe', async () => {
return {
modelsPulling: mocks.modelsPullingMock,
};
});

test('open models directory should call the api', async () => {
mocks.getModelsDirectoryMock.mockResolvedValue('fake');
mocks.modelsPullingSubscribeMock.mockReturnValue([]);
render(Models);

await waitFor(async () => {
const open = screen.getByTitle('open-models-directory');
expect(open).toBeDefined();

await fireEvent.click(open);
});

await waitFor(() => {
expect(mocks.openDirectoryMock).toHaveBeenNthCalledWith(1, 'fake');
});
});
30 changes: 28 additions & 2 deletions packages/frontend/src/pages/Models.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import Card from '/@/lib/Card.svelte';
import { modelsPulling } from '../stores/recipe';
import { onMount } from 'svelte';
import ModelColumnSize from '../lib/table/model/ModelColumnSize.svelte';
import ModelColumnCreation from '../lib/table/model/ModelColumnCreation.svelte';
import ModelColumnCreation from '../lib/table/model/ModelColumnCreation.svelte';
import { studioClient } from '/@/utils/client';
import { faFolderOpen } from '@fortawesome/free-solid-svg-icons';
import Button from '/@/lib/button/Button.svelte';

const columns: Column<ModelInfo>[] = [
new Column<ModelInfo>('Name', { width: '3fr', renderer: ModelColumnName }),
Expand All @@ -33,6 +36,7 @@ let loading: boolean = true;
let tasks: Task[] = [];
let models: ModelInfo[] = [];
let filteredModels: ModelInfo[] = [];
let modelsDir: string | undefined = undefined;

function filterModels(): void {
// Let's collect the models we do not want to show (loading, error).
Expand All @@ -49,7 +53,10 @@ function filterModels(): void {
}

onMount(() => {
// Pulling update
studioClient.getModelsDirectory().then((modelsDirectory) => {
modelsDir = modelsDirectory;
});

const modelsPullingUnsubscribe = modelsPulling.subscribe(runningTasks => {
tasks = runningTasks;
loading = false;
Expand All @@ -67,12 +74,31 @@ onMount(() => {
localModelsUnsubscribe();
}
});

const openModelsDir = () => {
if(modelsDir === undefined)
return;
studioClient.openDirectory(modelsDir);
}
</script>

<NavPage title="Models on disk" searchEnabled="{false}" loading="{loading}">
<div slot="content" class="flex flex-col min-w-full min-h-full">
<div class="min-w-full min-h-full flex-1">
<div class="mt-4 px-5 space-y-5 h-full">
<div class="mx-4">
{#if modelsDir}
<Card classes="bg-charcoal-800 mt-4">
<div slot="content" class="text-base font-normal p-2 flex w-full">
<div class="text-base flex-grow">
Models are stored in
<code class="text-sm sm:text-base inline-flex text-left items-center space-x-4 px-2 bg-charcoal-500 rounded-lg">{modelsDir}</code>
</div>
<Button title="open-models-directory" on:click={openModelsDir} icon={faFolderOpen}/>
</div>
</Card>
{/if}
</div>
{#if !loading}
{#if tasks.length > 0}
<div class="mx-4">
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/src/StudioAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export abstract class StudioAPI {
abstract getPullingStatuses(): Promise<Map<string, RecipeStatus>>;
abstract pullApplication(recipeId: string): Promise<void>;
abstract openURL(url: string): Promise<boolean>;
abstract openDirectory(path: string): Promise<void>;
/**
* Get the information of models saved locally into the extension's storage directory
*/
Expand All @@ -21,4 +22,5 @@ export abstract class StudioAPI {
abstract askPlayground(modelId: string, prompt: string): Promise<number>;
abstract getPlaygroundQueriesState(): Promise<QueryState[]>;
abstract getPlaygroundsState(): Promise<PlaygroundState[]>;
abstract getModelsDirectory(): Promise<string>;
}
Loading