diff --git a/.github/hooks/pre-commit b/.github/hooks/pre-commit new file mode 100644 index 0000000..9af2a43 --- /dev/null +++ b/.github/hooks/pre-commit @@ -0,0 +1,13 @@ +#!/bin/bash + +VERSION_FILE="app/version" + +CURRENT_DATE=$(date +"%Y-%m-%d %H:%M:%S") + +if jq --arg date "$CURRENT_DATE" '.last_update = $date' "$VERSION_FILE" > temp.json; then + mv temp.json "$VERSION_FILE" + git add "$VERSION_FILE" +else + echo "Error: Failed to update version file" + exit 1 +fi diff --git a/README.md b/README.md index 689038d..d467078 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ # Dependencies - Before running install + - [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) + - [JQ](https://jqlang.org/download/) - [Podman](https://podman.io/docs/installation) - [Ollama](https://ollama.com/download) diff --git a/ace b/ace index 166c10d..d7896cf 100755 --- a/ace +++ b/ace @@ -4,6 +4,14 @@ ACE_LOGGER_VERBOSE_ENV="." +# SETUP +setup() { + echo "Setting up environment..." + git_hooks_folder=".github/hooks" + mv -r "$git_hooks_folder" ".git/hooks" +} + + # STARTUP run_tests() { echo "Installing/Updating test dependencies..." @@ -30,6 +38,8 @@ run_ace() { main() { # Store original arguments original_args=("$@") + + setup # Initialize variables run_tests=false diff --git a/app/components/controller/api/__init__.py b/app/components/controller/api/__init__.py index 1c363db..0a4c11a 100644 --- a/app/components/controller/api/__init__.py +++ b/app/components/controller/api/__init__.py @@ -1 +1,21 @@ -from .routes import controller_api \ No newline at end of file +# DEPENDENCIES +## Third-Party +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +## Local +from .routes import root, model_provider + + +controller_api = FastAPI() + +controller_api.include_router(root) +controller_api.include_router(model_provider) + +controller_api.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:4200", "http://127.0.0.1:4200"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + expose_headers=["*"] +) diff --git a/app/components/controller/api/routes/__init__.py b/app/components/controller/api/routes/__init__.py new file mode 100644 index 0000000..930bd09 --- /dev/null +++ b/app/components/controller/api/routes/__init__.py @@ -0,0 +1,2 @@ +from .model_provider import model_provider +from .root import root diff --git a/app/components/controller/api/routes/model_provider.py b/app/components/controller/api/routes/model_provider.py new file mode 100644 index 0000000..9d89b08 --- /dev/null +++ b/app/components/controller/api/routes/model_provider.py @@ -0,0 +1,43 @@ +# DEPENDENCIES +## Third-Party +from fastapi import APIRouter, HTTPException +from http import HTTPStatus +from pydantic import ValidationError +## Local +from constants import APIRoutes, Defaults, Names +from logger import logger +from models.api_schemas.controller import GetLLMModelsResponse +from ..services import model_provider_service + + +model_provider = APIRouter() + +@model_provider.get( + f"{APIRoutes.MODEL_PROVIDER}llm/model-types", + response_model=tuple[str, ...], + description=f"Get the {Names.ACE} available LLM model types" +) +async def get_llm_model_types_route() -> tuple[str, ...]: + try: + return model_provider_service.get_llm_model_types() + except ValidationError as error: + logger.error(error) + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="LLM model types data error!") + except Exception as error: + logger.error(error) + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=Defaults.INTERNAL_SERVER_ERROR_MESSAGE) + +@model_provider.get( + f"{APIRoutes.MODEL_PROVIDER}llm/models", + response_model=list[GetLLMModelsResponse], + description=f"Get the {Names.ACE} available LLM models" +) +async def get_llm_models_route() -> list[GetLLMModelsResponse]: + try: + return model_provider_service.get_llm_models() + except ValidationError as error: + logger.error(error) + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="LLM model types data error!") + except Exception as error: + logger.error(error) + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=Defaults.INTERNAL_SERVER_ERROR_MESSAGE) diff --git a/app/components/controller/api/routes.py b/app/components/controller/api/routes/root.py similarity index 54% rename from app/components/controller/api/routes.py rename to app/components/controller/api/routes/root.py index 04e7610..c1e3e33 100644 --- a/app/components/controller/api/routes.py +++ b/app/components/controller/api/routes/root.py @@ -1,40 +1,29 @@ # DEPENDENCIES ## Third-Party -from fastapi import FastAPI, HTTPException -from fastapi.middleware.cors import CORSMiddleware +from fastapi import APIRouter, HTTPException from http import HTTPStatus from pydantic import ValidationError ## Local -from constants import Defaults, Names +from constants import APIRoutes, Defaults, Names from logger import logger from models.api_schemas.controller import ( GetVersionDetailsResponse, - GetSettingsResponse, EditSettingsRequest, - GetLLMModelsResponse + GetSettingsResponse, EditSettingsRequest ) from models.api_schemas.defaults import DefaultAPIResponse -from . import service +from ..services import root_service -controller_api = FastAPI() -controller_api.add_middleware( - CORSMiddleware, - allow_origins=["http://localhost:4200"], # Allow requests from your Angular app - allow_credentials=True, - allow_methods=["*"], # Allow all HTTP methods - allow_headers=["*"], # Allow all headers -) - +root = APIRouter() -# ROUTES -@controller_api.get( - "/version", +@root.get( + f"{APIRoutes.ROOT}version", response_model=GetVersionDetailsResponse, description=f"Get the {Names.ACE}'s version data" ) async def get_version_route() -> dict: try: - return service.get_version_data() + return root_service.get_version() except ValidationError as error: logger.error(error) raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Version data error!") @@ -42,14 +31,14 @@ async def get_version_route() -> dict: logger.error(error) raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=Defaults.INTERNAL_SERVER_ERROR_MESSAGE) -@controller_api.get( - "/settings", +@root.get( + f"{APIRoutes.ROOT}settings", response_model=GetSettingsResponse, description=f"Get the {Names.ACE} controller settings data" ) async def get_settings_route() -> dict: try: - return service.get_settings_data() + return root_service.get_settings_data() except ValidationError as error: logger.error(error) raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Settings data error!") @@ -57,14 +46,14 @@ async def get_settings_route() -> dict: logger.error(error) raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=Defaults.INTERNAL_SERVER_ERROR_MESSAGE) -@controller_api.post( - "/settings", +@root.post( + f"{APIRoutes.ROOT}settings", response_model=DefaultAPIResponse, description=f"Edit the {Names.ACE} controller settings data" ) async def set_settings_route(updated_settings: EditSettingsRequest) -> dict: try: - service.edit_settings_data(updated_settings=updated_settings.model_dump()) + root_service.edit_settings_data(updated_settings=updated_settings.model_dump()) return DefaultAPIResponse(message="Settings data updated successfully!") except ValidationError as error: logger.error(error) @@ -73,32 +62,18 @@ async def set_settings_route(updated_settings: EditSettingsRequest) -> dict: logger.error(error) raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=Defaults.INTERNAL_SERVER_ERROR_MESSAGE) -@controller_api.get( - "/model-provider/model-types", - response_model=dict[str, tuple[str, ...]], - description=f"Get the {Names.ACE} available LLM model types" -) -async def get_model_types_route() -> dict[str, tuple[str, ...]]: - try: - return service.get_model_types() - except ValidationError as error: - logger.error(error) - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="LLM model types data error!") - except Exception as error: - logger.error(error) - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=Defaults.INTERNAL_SERVER_ERROR_MESSAGE) - -@controller_api.get( - "/model-provider/model-type/llm", - response_model=list[GetLLMModelsResponse], - description=f"Get the {Names.ACE} available LLM models" +@root.delete( + f"{APIRoutes.ROOT}settings", + response_model=DefaultAPIResponse, + description=f"Delete the {Names.ACE} controller settings data" ) -async def get_llm_models_route() -> list[GetLLMModelsResponse]: +async def delete_settings_route() -> dict: try: - return service.get_llm_models() + root_service.delete_settings_data() + return DefaultAPIResponse(message="Settings data deleted successfully!") except ValidationError as error: logger.error(error) - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="LLM model types data error!") + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Settings data error!") except Exception as error: logger.error(error) - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=Defaults.INTERNAL_SERVER_ERROR_MESSAGE) + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=Defaults.INTERNAL_SERVER_ERROR_MESSAGE) \ No newline at end of file diff --git a/app/components/controller/api/service.py b/app/components/controller/api/service.py deleted file mode 100644 index ee242ba..0000000 --- a/app/components/controller/api/service.py +++ /dev/null @@ -1,59 +0,0 @@ -# DEPENDENCIES -## Built-In -import json -## Local -from constants import ( - DictKeys, - Files, - ModelProviders, - ModelTypes, ThreeDModelTypes, AudioModelTypes, ImageModelTypes, LLMModelTypes, MultiModalModelTypes, RAGModelTypes, RoboticsModelTypes, VideoModelTypes -) -from models.config.controller import ControllerSettingsSchema -from models.data.initial import INTITAL_LLM_MODEL_PROVIDERS - - -# HELPERS -def _get_settings() -> dict: - settings: dict = {} - with open(Files.CONTROLLER_SETTINGS, "r", encoding="utf-8") as settings_file: - settings = json.loads(settings_file.read()) - settings = ControllerSettingsSchema(**settings).model_dump() - with open(Files.CONTROLLER_SETTINGS, "w", encoding="utf-8") as settings_file: - settings_file.write(json.dumps(settings)) - return settings - - -# GENERAL -def get_version_data() -> dict: - with open(Files.VERSION, "r", encoding="utf-8") as settings_file: - return json.loads(settings_file.read()) - -def get_settings_data() -> dict: - return _get_settings() - -def edit_settings_data(updated_settings: dict): - settings: dict = _get_settings() - settings.update(updated_settings) - with open(Files.CONTROLLER_SETTINGS, "w", encoding="utf-8") as settings_file: - settings_file.write(json.dumps(settings)) - -# MODEL PROVIDERS -## Model Types -def get_model_types() -> dict[str, tuple[str, ...]]: - return { - ModelTypes.THREE_D: ThreeDModelTypes.get_tuple(), - ModelTypes.AUDIO: AudioModelTypes.get_tuple(), - ModelTypes.IMAGE: ImageModelTypes.get_tuple(), - ModelTypes.LLM: LLMModelTypes.get_tuple(), - ModelTypes.MULTIMODAL: MultiModalModelTypes.get_tuple(), - ModelTypes.RAG: RAGModelTypes.get_tuple(), - ModelTypes.ROBOTICS: RoboticsModelTypes.get_tuple(), - ModelTypes.VIDEO: VideoModelTypes.get_tuple() - } - -def get_llm_models() -> list[dict]: - llm_model_providers: list[dict] = [initial_llm_model_provider.model_dump() for initial_llm_model_provider in INTITAL_LLM_MODEL_PROVIDERS] - with open(Files.CONTROLLER_LLM_MODELS, "r", encoding="utf-8") as llm_models_file: - llm_models: dict = json.loads(llm_models_file.read()) - llm_model_providers.extend(llm_models) - return llm_model_providers diff --git a/app/components/controller/api/services/__init__.py b/app/components/controller/api/services/__init__.py new file mode 100644 index 0000000..5f4be59 --- /dev/null +++ b/app/components/controller/api/services/__init__.py @@ -0,0 +1,4 @@ +from . import ( + model_provider as model_provider_service, + root as root_service +) \ No newline at end of file diff --git a/app/components/controller/api/services/model_provider.py b/app/components/controller/api/services/model_provider.py new file mode 100644 index 0000000..124a039 --- /dev/null +++ b/app/components/controller/api/services/model_provider.py @@ -0,0 +1,20 @@ +# DEPENDENCIES +## Built-In +import json +## Local +from constants import ( + Files, + ModelTypes, ThreeDModelTypes, AudioModelTypes, ImageModelTypes, LLMModelTypes, MultiModalModelTypes, RAGModelTypes, RoboticsModelTypes, VideoModelTypes +) +from models.data.initial import INTITAL_LLM_MODEL_PROVIDERS + + +def get_llm_model_types() -> tuple[str, ...]: + return LLMModelTypes.get_tuple() + +def get_llm_models() -> list[dict]: + llm_model_providers: list[dict] = [initial_llm_model_provider.model_dump() for initial_llm_model_provider in INTITAL_LLM_MODEL_PROVIDERS] + with open(Files.CONTROLLER_LLM_MODELS, "r", encoding="utf-8") as llm_models_file: + llm_models: dict = json.loads(llm_models_file.read()) + llm_model_providers.extend(llm_models) + return llm_model_providers diff --git a/app/components/controller/api/services/root.py b/app/components/controller/api/services/root.py new file mode 100644 index 0000000..079be34 --- /dev/null +++ b/app/components/controller/api/services/root.py @@ -0,0 +1,36 @@ +# DEPENDENCIES +## Built-In +import json +## Local +from constants import Files +from models.config.controller import ControllerSettingsSchema + + +# HELPERS +def _get_settings() -> dict: + settings: dict = {} + with open(Files.CONTROLLER_SETTINGS, "r", encoding="utf-8") as settings_file: + settings = json.loads(settings_file.read()) + settings = ControllerSettingsSchema(**settings).model_dump() + with open(Files.CONTROLLER_SETTINGS, "w", encoding="utf-8") as settings_file: + settings_file.write(json.dumps(settings)) + return settings + + +# ROOT +def get_version() -> dict: + with open(Files.VERSION, "r", encoding="utf-8") as settings_file: + return json.loads(settings_file.read()) + +def get_settings_data() -> dict: + return _get_settings() + +def edit_settings_data(updated_settings: dict): + settings: dict = _get_settings() + settings.update(updated_settings) + with open(Files.CONTROLLER_SETTINGS, "w", encoding="utf-8") as settings_file: + settings_file.write(json.dumps(settings)) + +def delete_settings_data(): + with open(Files.CONTROLLER_SETTINGS, "w", encoding="utf-8") as settings_file: + settings_file.write(json.dumps({})) diff --git a/app/components/ui/angular.json b/app/components/ui/angular.json index 54fd858..3a6fb06 100644 --- a/app/components/ui/angular.json +++ b/app/components/ui/angular.json @@ -33,6 +33,7 @@ ], "styles": [ "@angular/material/prebuilt-themes/azure-blue.css", + "src/theme.scss", "src/styles.scss" ], "scripts": [] diff --git a/app/components/ui/src/app/app.component.html b/app/components/ui/src/app/app.component.html index c42a7a3..5089ac9 100644 --- a/app/components/ui/src/app/app.component.html +++ b/app/components/ui/src/app/app.component.html @@ -21,8 +21,14 @@ - + @if ( uiSettings?.show_footer ) { + + } diff --git a/app/components/ui/src/app/app.component.scss b/app/components/ui/src/app/app.component.scss index 6a277db..4369619 100644 --- a/app/components/ui/src/app/app.component.scss +++ b/app/components/ui/src/app/app.component.scss @@ -31,4 +31,5 @@ height: 2rem; gap: 2rem; outline: auto; + background-color: gray; } diff --git a/app/components/ui/src/app/app.component.ts b/app/components/ui/src/app/app.component.ts index c2dbbeb..ba52487 100644 --- a/app/components/ui/src/app/app.component.ts +++ b/app/components/ui/src/app/app.component.ts @@ -1,6 +1,6 @@ // DEPENDENCIES //// Angular -import { Component, OnInit, signal } from "@angular/core"; +import { Component, inject, OnInit, signal } from "@angular/core"; import { MatButtonModule } from "@angular/material/button"; import { MatIconModule } from "@angular/material/icon"; import { MatListModule } from "@angular/material/list"; @@ -8,11 +8,14 @@ import { MatTooltipModule } from "@angular/material/tooltip"; import { MatSidenavModule } from "@angular/material/sidenav"; import { RouterModule, RouterOutlet } from "@angular/router"; import { Store } from "@ngrx/store"; -import { Observable } from "rxjs"; //// Local -import { appActions } from "./store/actions/app.actions"; -import { selectAppState } from './store/selectors/app.selectors'; -import { AppState } from './store/state/app.state'; +import { ThemeService } from "./theme"; +import { IAppVersionData } from "./models/app.models"; +import { ISettings, IUISettings } from "./models/settings.models"; +import { appActions } from "./store/app/app.actions"; +import { settingsActions } from "./store/settings/settings.actions"; +import { selectAppVersionDataState } from './store/app/app.selectors'; +import { selectSettingsState } from "./store/settings/settings.selectors"; // TYPES @@ -30,8 +33,8 @@ export type SidebarItem = { MatButtonModule, MatIconModule, MatListModule, - MatTooltipModule, MatSidenavModule, + MatTooltipModule, RouterModule, RouterOutlet ], @@ -41,6 +44,7 @@ export type SidebarItem = { export class AppComponent implements OnInit { // Variables title = "ACE"; + themeService = inject(ThemeService); sidebarItems = signal([ { name: "Home", @@ -68,16 +72,37 @@ export class AppComponent implements OnInit { route: "settings" } ]) - versionData$: Observable; - version: string = "0"; + + settings?: ISettings; + uiSettings?: IUISettings; + appVersionData?: IAppVersionData; // Initialisation - constructor(private store: Store) { - this.versionData$ = this.store.select(selectAppState); - } + constructor(private store: Store) {} ngOnInit(): void { - this.store.dispatch(appActions.getACEVersionData()); - this.versionData$.subscribe( versionData => this.version = versionData.versionData.version); + this.store.dispatch(appActions.getAppVersionData()); + this.store.select(selectAppVersionDataState).subscribe( version_data => this.appVersionData = version_data ); + this.store.dispatch(settingsActions.getSettings()); + this.store.select(selectSettingsState).subscribe( settings => { + this.settings = settings.settings; + this.uiSettings = settings.settings.ui_settings; + this.themeService.setDarkMode(this.uiSettings.dark_mode); + }) + } + + toggleDarkMode() { + if (!this.settings) { + return; + } + this.store.dispatch(settingsActions.editSettings({ + settings: { + ...this.settings, + ui_settings: { + ...this.settings.ui_settings, + dark_mode: !this.settings.ui_settings.dark_mode + } + } + })) } } diff --git a/app/components/ui/src/app/app.config.ts b/app/components/ui/src/app/app.config.ts index b918be1..5d78379 100644 --- a/app/components/ui/src/app/app.config.ts +++ b/app/components/ui/src/app/app.config.ts @@ -9,17 +9,21 @@ import { provideStoreDevtools } from '@ngrx/store-devtools'; //// Local import { routes } from './app.routes'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; -import { AppEffects } from './store/effects/app.effects'; -import { appReducer } from './store/reducers/app.reducers'; +import { AppEffects } from './store/app/app.effects'; +import { modelProviderEffects } from './store/model-provider/model-provider.effects'; +import { SettingsEffects } from './store/settings/settings.effects'; +import { appReducer } from './store/app/app.reducers'; +import { modelProviderReducer } from './store/model-provider/model-provider.reducers'; +import { settingsReducer } from './store/settings/settings.reducers'; // CONFIG export const appConfig: ApplicationConfig = { providers: [ provideAnimationsAsync(), - provideEffects([AppEffects]), + provideEffects([AppEffects, modelProviderEffects, SettingsEffects]), provideHttpClient(), - provideStore({ app_data: appReducer }), + provideStore({ app_data: appReducer, model_provider: modelProviderReducer, settings: settingsReducer }), provideStoreDevtools({ maxAge: 25, logOnly: false diff --git a/app/components/ui/src/app/constants.ts b/app/components/ui/src/app/constants.ts new file mode 100644 index 0000000..513b485 --- /dev/null +++ b/app/components/ui/src/app/constants.ts @@ -0,0 +1,9 @@ +export const Values = { + NOT_LOADED: "not_loaded", +} + +const ROOT = "/"; +export const APIRoutes = { + ROOT, + MODEL_PROVIDER: `${ROOT}model-provider/`, +} diff --git a/app/components/ui/src/app/api.urls.ts b/app/components/ui/src/app/environment.ts similarity index 75% rename from app/components/ui/src/app/api.urls.ts rename to app/components/ui/src/app/environment.ts index 38b1ad8..5aa9e8e 100644 --- a/app/components/ui/src/app/api.urls.ts +++ b/app/components/ui/src/app/environment.ts @@ -1,5 +1,5 @@ const defaultClusterURL: string = `http://127.0.0.1`; -export const serviceURLs = { +export const environmentURLs = { controller: defaultClusterURL + ":2349" }; diff --git a/app/components/ui/src/app/models/app.models.ts b/app/components/ui/src/app/models/app.models.ts index ac57ffc..a5512ed 100644 --- a/app/components/ui/src/app/models/app.models.ts +++ b/app/components/ui/src/app/models/app.models.ts @@ -1,3 +1,7 @@ -export interface IACEVersionData { +export interface IAppVersionData { version: string + authors: string[] + license: string + last_update: string + rebuild_date: string } diff --git a/app/components/ui/src/app/models/model-provider.models.ts b/app/components/ui/src/app/models/model-provider.models.ts new file mode 100644 index 0000000..c6ee086 --- /dev/null +++ b/app/components/ui/src/app/models/model-provider.models.ts @@ -0,0 +1,13 @@ +export interface ILLMModelProvider { + id: string; + model_provider: string; + name: string; + model_name: string; + default: boolean; + max_input_tokens: number; + max_output_tokens: number; + cost_per_million_input_tokens: number; + cost_per_million_output_tokens: number; + knowledge_cutoff: string; + rate_limits: string; +} diff --git a/app/components/ui/src/app/models/settings.models.ts b/app/components/ui/src/app/models/settings.models.ts new file mode 100644 index 0000000..d967bc7 --- /dev/null +++ b/app/components/ui/src/app/models/settings.models.ts @@ -0,0 +1,52 @@ +// SECTIONS +//// UI +export interface IUISettings { + dark_mode: boolean; + show_footer: boolean; +} + +//// Layers +export interface ILayerSetting { + layer_name: string; + model_type: string; +} + +//// Model Provider +////// Unique Providers +export interface IIndividualProviderSettings { + name: string; + enabled: boolean; + api_key: string; +} + +////// Model Types +export interface ILLMModelTypeSettings { + model_type: string; + model_id: string; + logical_temperature: number; + creative_temperature: number; + output_token_limit: number; +} + +////// Full +export interface IModelProviderSetting { + individual_provider_settings: IIndividualProviderSettings[]; + + three_d_model_type_settings: string[]; + audio_model_type_settings: string[]; + image_model_type_settings: string[]; + llm_model_type_settings: ILLMModelTypeSettings[]; + multimodal_model_type_settings: string[]; + rag_model_type_settings: string[]; + robotics_model_type_settings: string[]; + video_model_type_settings: string[]; +} + + +// FULL SETTINGS +export interface ISettings { + ace_name: string; + ui_settings: IUISettings; + layer_settings: ILayerSetting[]; + model_provider_settings: IModelProviderSetting; +} diff --git a/app/components/ui/src/app/pages/chat/chat.component.html b/app/components/ui/src/app/pages/chat/chat.component.html index 368564b..a0cd3a8 100644 --- a/app/components/ui/src/app/pages/chat/chat.component.html +++ b/app/components/ui/src/app/pages/chat/chat.component.html @@ -1 +1,3 @@ -

CHAT PAGE

+
+

Chat

+
diff --git a/app/components/ui/src/app/pages/dashboard/dashboard.component.html b/app/components/ui/src/app/pages/dashboard/dashboard.component.html index b4d6702..045c851 100644 --- a/app/components/ui/src/app/pages/dashboard/dashboard.component.html +++ b/app/components/ui/src/app/pages/dashboard/dashboard.component.html @@ -1 +1,3 @@ -

DASHBOARD PAGE

+
+

Dashboard

+
diff --git a/app/components/ui/src/app/pages/home/home.component.html b/app/components/ui/src/app/pages/home/home.component.html index 3afe58b..6ada622 100644 --- a/app/components/ui/src/app/pages/home/home.component.html +++ b/app/components/ui/src/app/pages/home/home.component.html @@ -1 +1,3 @@ -

HOME PAGE

+
+

Home

+
diff --git a/app/components/ui/src/app/pages/model_garden/model-garden.component.html b/app/components/ui/src/app/pages/model_garden/model-garden.component.html index 16868f3..c5e8629 100644 --- a/app/components/ui/src/app/pages/model_garden/model-garden.component.html +++ b/app/components/ui/src/app/pages/model_garden/model-garden.component.html @@ -1 +1,3 @@ -

MODEL GARDEN PAGE

+
+

Model Garden

+
diff --git a/app/components/ui/src/app/pages/settings/settings.component.html b/app/components/ui/src/app/pages/settings/settings.component.html index d93694f..dd933d4 100644 --- a/app/components/ui/src/app/pages/settings/settings.component.html +++ b/app/components/ui/src/app/pages/settings/settings.component.html @@ -1 +1,90 @@ -

SETTINGS PAGE

+
+

Settings

+ + @if (settingsForm) { +
+

General:

+
+ + Ace Name + + @if (aceNameControl?.hasError("required")) { + Please enter a name for the ace + } + @if (aceNameControl?.hasError("maxlength")) { + Max length of 32 characters + } + +
+ + + +

UI:

+
+ Dark Mode + Show Footer +
+ + + +

Model Provider:

+
+

Individual Model Providers:

+
+ @for (provider of individualModelProviderSettingsControl.controls; track provider.value?.name; let i = $index) { + @if (provider.get("name")?.value !== "ollama") { +

{{ formatSnakeCase(provider.get('name')?.value) }}

+
+ Enabled + + API Key + + +
+ } + } +
+
+ + + +

ACE Layers:

+
+ @for (layerSetting of layerSettingsControl.controls; track layerSetting; let i = $index) { +
+

{{ formatSnakeCase(layerSetting.value?.layer_name || "") }} Layer:

+ + Model Type + + @for (type of llmModelTypes; track type) { + {{ formatSnakeCase(type) }} + } + + +
+ } +
+ + + +

About:

+
+

Developed by {{ appVersionData.authors.join(", ") }}

+

Version: v{{ appVersionData.version }}

+

License: {{ appVersionData.license }}

+

Last Update: {{ appVersionData.last_update }}

+

Last Container Image Update: {{ appVersionData.rebuild_date }}

+

+ +
+
+ + + } +
diff --git a/app/components/ui/src/app/pages/settings/settings.component.scss b/app/components/ui/src/app/pages/settings/settings.component.scss index e69de29..62facf6 100644 --- a/app/components/ui/src/app/pages/settings/settings.component.scss +++ b/app/components/ui/src/app/pages/settings/settings.component.scss @@ -0,0 +1,5 @@ +.save-button { + position: sticky; + float: right; + bottom: 1rem; +} diff --git a/app/components/ui/src/app/pages/settings/settings.component.ts b/app/components/ui/src/app/pages/settings/settings.component.ts index 7b499b7..6a6a898 100644 --- a/app/components/ui/src/app/pages/settings/settings.component.ts +++ b/app/components/ui/src/app/pages/settings/settings.component.ts @@ -1,9 +1,191 @@ -import { Component } from "@angular/core"; +// DEPENDENCIES +//// Angular +import { Component, OnInit } from "@angular/core"; +import { Store } from "@ngrx/store"; +import { filter, take } from "rxjs"; +import { FormArray, FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { MatButtonModule } from "@angular/material/button"; +import { MatDividerModule } from "@angular/material/divider"; +import { MatIconModule } from "@angular/material/icon"; +import { MatInputModule } from "@angular/material/input"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatSelectModule } from "@angular/material/select"; +import { MatSlideToggleModule } from "@angular/material/slide-toggle"; +//// Local +import { IAppVersionData } from "../../models/app.models"; +import { ILLMModelProvider } from "../../models/model-provider.models"; +import { IModelProviderSetting, ISettings } from "../../models/settings.models"; +import { modelProviderActions } from "../../store/model-provider/model-provider.actions"; +import { selectAppVersionDataState } from "../../store/app/app.selectors"; +import { selectLLMModelTypes } from "../../store/model-provider/model-provider.selectors"; +import { settingsActions } from "../../store/settings/settings.actions"; +import { selectSettingsState } from "../../store/settings/settings.selectors"; @Component({ selector: "page-settings", - imports: [], + imports: [ + ReactiveFormsModule, + MatButtonModule, + MatDividerModule, + MatIconModule, + MatInputModule, + MatFormFieldModule, + MatSelectModule, + MatSlideToggleModule + ], templateUrl: "./settings.component.html", styleUrl: "./settings.component.scss" }) -export class SettingsComponent {} +export class SettingsComponent implements OnInit { + appVersionData!: IAppVersionData; + llmModels: ILLMModelProvider[] = []; + llmModelTypes: string[] = []; + selectedLLMModelType: string = ""; + settings!: ISettings; + + settingsForm!: FormGroup; + generalForm!: FormGroup; + uiSettingsForm!: FormGroup; + modelProviderForm!: FormGroup; + layerSettingsForm!: FormArray; + + changesDetected: boolean = false; + + constructor( + private formBuilder: FormBuilder, + private store: Store + ) { + this.initialiseForm(); + } + + ngOnInit(): void { + this.store.select(selectAppVersionDataState).subscribe( version_data => this.appVersionData = version_data ); + this.store.dispatch(modelProviderActions.getLLMModels()); + this.store.dispatch(modelProviderActions.getLLMModelTypes()); + this.store.select(selectLLMModelTypes).pipe( + filter((model_types: string[]): model_types is string[] => model_types.length > 0), + take(1) + ).subscribe(model_types => { + this.llmModelTypes = model_types; + this.initialiseForm(); + }); + + this.store.dispatch(settingsActions.getSettings()); + this.store.select(selectSettingsState).subscribe(settings => { + this.settings = settings.settings; + this.patchFormValues(); + }); + } + + private initialiseForm(): void { + this.generalForm = this.formBuilder.group({ + ace_name: ["", [Validators.required, Validators.maxLength(32)]] + }); + + this.uiSettingsForm = this.formBuilder.group({ + dark_mode: [true], + show_footer: [true] + }); + + this.layerSettingsForm = this.formBuilder.array([]); + + this.modelProviderForm = this.formBuilder.group({ + individual_provider_settings: this.formBuilder.array([]), + three_d_model_type_settings: this.formBuilder.array([]), + audio_model_type_settings: this.formBuilder.array([]), + image_model_type_settings: this.formBuilder.array([]), + llm_model_type_settings: this.formBuilder.array([]), + rag_model_type_settings: this.formBuilder.array([]) + }); + + this.settingsForm = this.formBuilder.group({ + ...this.generalForm.controls, + ui_settings: this.uiSettingsForm, + layer_settings: this.layerSettingsForm, + model_provider_settings: this.modelProviderForm + }); + + this.settingsForm.valueChanges.subscribe(() => { + this.changesDetected = true; + }); + } + + private patchFormValues(): void { + if (!this.settings) return; + + this.initialiseForm(); + + this.generalForm.patchValue({ + ace_name: this.settings.ace_name, + }); + + this.uiSettingsForm.patchValue({ + dark_mode: this.settings.ui_settings.dark_mode, + show_footer: this.settings.ui_settings.show_footer + }); + + const modelProviderSettings: IModelProviderSetting = this.settings.model_provider_settings; + this.individualModelProviderSettingsControl?.clear(); + + modelProviderSettings.individual_provider_settings.forEach(provider => { + this.individualModelProviderSettingsControl?.push( + this.formBuilder.group({ + name: [provider.name], + enabled: [provider.enabled], + api_key: [provider.api_key] + }) + ); + }); + + this.layerSettingsForm.clear(); + this.settings.layer_settings.forEach(layer => { + this.layerSettingsForm.push( + this.formBuilder.group({ + layer_name: [layer?.layer_name || "", Validators.required], + model_type: [layer?.model_type || "", Validators.required] + }) + ); + }); + + this.settingsForm.markAsPristine(); + this.changesDetected = false; + } + + formatSnakeCase(layerName?: string): string { + return (layerName || "") + .split("_") + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" "); + } + + onReset() { + this.store.dispatch(settingsActions.resetSettings()); + } + + onSave() { + if (!this.settingsForm.valid) { + return; + } + const updatedSettings = { + ...this.settings, + ...this.settingsForm.value + }; + + this.changesDetected = false; + this.settingsForm.markAsPristine(); + + this.store.dispatch(settingsActions.editSettings({ settings: updatedSettings })); + } + + get aceNameControl() { + return this.generalForm.get("ace_name"); + } + + get individualModelProviderSettingsControl(): FormArray { + return this.modelProviderForm.get("individual_provider_settings") as FormArray; + } + + get layerSettingsControl(): FormArray { + return this.layerSettingsForm; + } +} diff --git a/app/components/ui/src/app/pages/settings/settings.service.ts b/app/components/ui/src/app/pages/settings/settings.service.ts deleted file mode 100644 index e69de29..0000000 diff --git a/app/components/ui/src/app/pages/settings/store/settings.actions.ts b/app/components/ui/src/app/pages/settings/store/settings.actions.ts deleted file mode 100644 index e69de29..0000000 diff --git a/app/components/ui/src/app/pages/settings/store/settings.effects.ts b/app/components/ui/src/app/pages/settings/store/settings.effects.ts deleted file mode 100644 index e69de29..0000000 diff --git a/app/components/ui/src/app/pages/settings/store/settings.reducers.ts b/app/components/ui/src/app/pages/settings/store/settings.reducers.ts deleted file mode 100644 index e69de29..0000000 diff --git a/app/components/ui/src/app/pages/settings/store/settings.selectors.ts b/app/components/ui/src/app/pages/settings/store/settings.selectors.ts deleted file mode 100644 index e69de29..0000000 diff --git a/app/components/ui/src/app/pages/settings/store/settings.state.ts b/app/components/ui/src/app/pages/settings/store/settings.state.ts deleted file mode 100644 index e69de29..0000000 diff --git a/app/components/ui/src/app/services/app.service.ts b/app/components/ui/src/app/services/app.service.ts index da1a9b7..978589f 100644 --- a/app/components/ui/src/app/services/app.service.ts +++ b/app/components/ui/src/app/services/app.service.ts @@ -1,14 +1,15 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from 'rxjs/internal/Observable'; -import { serviceURLs } from "../api.urls"; +import { APIRoutes } from "../constants"; +import { environmentURLs } from "../environment"; export type HttpListResponseFailure = { status: string, message: string }; const endpoints = { - getACEVersionData: `${serviceURLs.controller}/version`, + getAppVersionData: `${environmentURLs.controller}${APIRoutes.ROOT}version`, }; @@ -18,7 +19,7 @@ const endpoints = { export class AppService { constructor(private http: HttpClient) { } - getACEVersionData(): Observable { - return this.http.get(endpoints.getACEVersionData); + getAppVersionData(): Observable { + return this.http.get(endpoints.getAppVersionData); } } diff --git a/app/components/ui/src/app/services/model-provider.service.ts b/app/components/ui/src/app/services/model-provider.service.ts new file mode 100644 index 0000000..59316f7 --- /dev/null +++ b/app/components/ui/src/app/services/model-provider.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from "@angular/core"; +import { HttpClient } from "@angular/common/http"; +import { Observable } from 'rxjs/internal/Observable'; +import { APIRoutes } from "../constants"; +import { environmentURLs } from "../environment"; + + +export type HttpListResponseFailure = { status: string, message: string }; + +const endpoints = { + getLLMModels: `${environmentURLs.controller}${APIRoutes.MODEL_PROVIDER}llm/models`, + getLLMModelTypes: `${environmentURLs.controller}${APIRoutes.MODEL_PROVIDER}llm/model-types` +}; + + +@Injectable({ + providedIn: "root" +}) +export class ModelProviderService { + constructor(private http: HttpClient) { } + + getLLMModelTypes(): Observable { + return this.http.get(endpoints.getLLMModelTypes); + } + + getLLMModels(): Observable { + return this.http.get(endpoints.getLLMModels); + } +} diff --git a/app/components/ui/src/app/services/settings.service.ts b/app/components/ui/src/app/services/settings.service.ts new file mode 100644 index 0000000..9b5ef03 --- /dev/null +++ b/app/components/ui/src/app/services/settings.service.ts @@ -0,0 +1,39 @@ +// DEPENDENCIES +//// Built-In +import { Injectable } from "@angular/core"; +import { HttpClient } from "@angular/common/http"; +import { Observable } from 'rxjs/internal/Observable'; +//// Local +import { APIRoutes } from "../constants"; +import { environmentURLs } from "../environment"; +import { ISettings } from "../models/settings.models"; + + +export type HttpListResponseFailure = { status: string, message: string }; + + +const endpoints = { + getSettings: `${environmentURLs.controller}${APIRoutes.ROOT}settings`, + editSettings: `${environmentURLs.controller}${APIRoutes.ROOT}settings`, + resetSettings: `${environmentURLs.controller}${APIRoutes.ROOT}settings` +}; + + +@Injectable({ + providedIn: "root" +}) +export class SettingsService { + constructor(private http: HttpClient) { } + + getSettings(): Observable { + return this.http.get(endpoints.getSettings); + } + + editSettings(settings: ISettings): Observable { + return this.http.post(endpoints.editSettings, settings); + } + + resetSettings(): Observable { + return this.http.delete(endpoints.resetSettings); + } +} diff --git a/app/components/ui/src/app/state/app.state.ts b/app/components/ui/src/app/state/app.state.ts new file mode 100644 index 0000000..3c77631 --- /dev/null +++ b/app/components/ui/src/app/state/app.state.ts @@ -0,0 +1,21 @@ +import { createDefaultLoadable, Loadable } from "./loadable.state"; +import { IAppVersionData } from "../models/app.models"; + +export interface AppState extends Loadable { + version_data: IAppVersionData; +} + +export function createInitialAppState(): AppState { + return { + ...createDefaultLoadable(), + version_data: { + version: "0", + authors: [], + license: "", + last_update: "", + rebuild_date: "" + } + } +} + + diff --git a/app/components/ui/src/app/store/state/loadable.state.ts b/app/components/ui/src/app/state/loadable.state.ts similarity index 100% rename from app/components/ui/src/app/store/state/loadable.state.ts rename to app/components/ui/src/app/state/loadable.state.ts diff --git a/app/components/ui/src/app/state/model-provider.state.ts b/app/components/ui/src/app/state/model-provider.state.ts new file mode 100644 index 0000000..8ec6083 --- /dev/null +++ b/app/components/ui/src/app/state/model-provider.state.ts @@ -0,0 +1,22 @@ +import { createDefaultLoadable, Loadable } from "./loadable.state"; +import { ILLMModelProvider } from "../models/model-provider.models"; + +export interface ModelProviderLLMState extends Loadable { + models: ILLMModelProvider[], + model_types: string[] +} + +export interface ModelProviderState extends Loadable { + llm: ModelProviderLLMState +} + +export function createInitialModelProviderState(): ModelProviderState { + return { + ...createDefaultLoadable(), + llm: { + ...createDefaultLoadable(), + models: [], + model_types: [] + } + } +} diff --git a/app/components/ui/src/app/state/settings.state.ts b/app/components/ui/src/app/state/settings.state.ts new file mode 100644 index 0000000..74deea8 --- /dev/null +++ b/app/components/ui/src/app/state/settings.state.ts @@ -0,0 +1,42 @@ +import { createDefaultLoadable, Loadable } from "./loadable.state"; +import { Values } from '../constants' +import { ISettings } from "../models/settings.models"; + +export interface SettingsState extends Loadable { + settings: ISettings; +} + +export function createInitialSettingsState(): SettingsState { + return { + ...createDefaultLoadable(), + settings: { + ace_name: Values.NOT_LOADED, + ui_settings: { + dark_mode: true, + show_footer: true + }, + layer_settings: [], + model_provider_settings: { + individual_provider_settings: [], + three_d_model_type_settings: [], + audio_model_type_settings: [], + image_model_type_settings: [], + llm_model_type_settings: [ + { + model_type: Values.NOT_LOADED, + model_id: Values.NOT_LOADED, + logical_temperature: 0, + creative_temperature: 0, + output_token_limit: 0 + } + ], + multimodal_model_type_settings: [], + rag_model_type_settings: [], + robotics_model_type_settings: [], + video_model_type_settings: [] + } + } + } +} + + diff --git a/app/components/ui/src/app/store/actions/app.actions.ts b/app/components/ui/src/app/store/actions/app.actions.ts deleted file mode 100644 index 35b306d..0000000 --- a/app/components/ui/src/app/store/actions/app.actions.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createActionGroup, props, emptyProps } from "@ngrx/store"; -import { IACEVersionData } from "../../models/app.models"; - -export const appActions = createActionGroup({ - source: "app", - events: { - getACEVersionData: emptyProps(), - getACEVersionDataSuccess: props<{ versionData: IACEVersionData }>(), - getACEVersionDataFailure: props<{ error: Error }>() - }, -}); diff --git a/app/components/ui/src/app/store/app/app.actions.ts b/app/components/ui/src/app/store/app/app.actions.ts new file mode 100644 index 0000000..a23f2b8 --- /dev/null +++ b/app/components/ui/src/app/store/app/app.actions.ts @@ -0,0 +1,11 @@ +import { createActionGroup, props, emptyProps } from "@ngrx/store"; +import { IAppVersionData } from "../../models/app.models"; + +export const appActions = createActionGroup({ + source: "app", + events: { + getAppVersionData: emptyProps(), + getAppVersionDataSuccess: props<{ version_data: IAppVersionData }>(), + getAppVersionDataFailure: props<{ error: Error }>() + }, +}); diff --git a/app/components/ui/src/app/store/effects/app.effects.ts b/app/components/ui/src/app/store/app/app.effects.ts similarity index 51% rename from app/components/ui/src/app/store/effects/app.effects.ts rename to app/components/ui/src/app/store/app/app.effects.ts index 8de462e..f00ed80 100644 --- a/app/components/ui/src/app/store/effects/app.effects.ts +++ b/app/components/ui/src/app/store/app/app.effects.ts @@ -1,22 +1,20 @@ import { inject, Injectable } from "@angular/core"; import { createEffect, ofType, Actions } from "@ngrx/effects"; -import { Store } from "@ngrx/store"; import { map, catchError, of, switchMap } from "rxjs"; -import { appActions } from "../actions/app.actions"; -import { IACEVersionData } from "../../models/app.models"; +import { appActions } from "./app.actions"; +import { IAppVersionData } from "../../models/app.models"; import { AppService } from "../../services/app.service"; @Injectable() export class AppEffects { private actions$ = inject(Actions); - private store$ = inject(Store); getACEVersionData$ = createEffect(() => this.actions$.pipe( - ofType(appActions.getACEVersionData), - switchMap(() => this.appService.getACEVersionData().pipe( - map((versionData: IACEVersionData) => appActions.getACEVersionDataSuccess({ versionData })), - catchError(error => of(appActions.getACEVersionDataFailure({ error }))) + ofType(appActions.getAppVersionData), + switchMap(() => this.appService.getAppVersionData().pipe( + map((version_data: IAppVersionData) => appActions.getAppVersionDataSuccess({ version_data })), + catchError(error => of(appActions.getAppVersionDataFailure({ error }))) )) )) diff --git a/app/components/ui/src/app/store/app/app.reducers.ts b/app/components/ui/src/app/store/app/app.reducers.ts new file mode 100644 index 0000000..9fba492 --- /dev/null +++ b/app/components/ui/src/app/store/app/app.reducers.ts @@ -0,0 +1,11 @@ +import { createReducer, on } from "@ngrx/store"; +import { onLoadableError, onLoadableLoad, onLoadableSuccess } from "../../state/loadable.state"; +import { appActions } from "./app.actions"; +import { createInitialAppState } from "../../state/app.state"; + +export const appReducer = createReducer( + createInitialAppState(), + on(appActions.getAppVersionData, state => ({...onLoadableLoad(state)})), + on(appActions.getAppVersionDataSuccess, (state, { version_data }) => ({...onLoadableSuccess(state), version_data: {...version_data}})), + on(appActions.getAppVersionDataFailure, (state, { error }) => ({...onLoadableError(state, error)})) +) diff --git a/app/components/ui/src/app/store/app/app.selectors.ts b/app/components/ui/src/app/store/app/app.selectors.ts new file mode 100644 index 0000000..86260c1 --- /dev/null +++ b/app/components/ui/src/app/store/app/app.selectors.ts @@ -0,0 +1,5 @@ +import { createSelector, createFeatureSelector } from "@ngrx/store"; +import { AppState } from "../../state/app.state"; + +export const selectAppState = createFeatureSelector("app_data"); +export const selectAppVersionDataState = createSelector(selectAppState, (state: AppState) => state.version_data); diff --git a/app/components/ui/src/app/store/model-provider/model-provider.actions.ts b/app/components/ui/src/app/store/model-provider/model-provider.actions.ts new file mode 100644 index 0000000..f691e8a --- /dev/null +++ b/app/components/ui/src/app/store/model-provider/model-provider.actions.ts @@ -0,0 +1,14 @@ +import { createActionGroup, props, emptyProps } from "@ngrx/store"; +import { ILLMModelProvider } from "../../models/model-provider.models"; + +export const modelProviderActions = createActionGroup({ + source: "model_provider", + events: { + getLLMModels: emptyProps(), + getLLMModelsSuccess: props<{ models: ILLMModelProvider[] }>(), + getLLMModelsFailure: props<{ error: Error }>(), + getLLMModelTypes: emptyProps(), + getLLMModelTypesSuccess: props<{ model_types: string[] }>(), + getLLMModelTypesFailure: props<{ error: Error }>() + }, +}); diff --git a/app/components/ui/src/app/store/model-provider/model-provider.effects.ts b/app/components/ui/src/app/store/model-provider/model-provider.effects.ts new file mode 100644 index 0000000..0927fb6 --- /dev/null +++ b/app/components/ui/src/app/store/model-provider/model-provider.effects.ts @@ -0,0 +1,32 @@ +import { inject, Injectable } from "@angular/core"; +import { createEffect, ofType, Actions } from "@ngrx/effects"; +import { map, catchError, of, switchMap } from "rxjs"; +import { modelProviderActions } from "./model-provider.actions"; +import { ILLMModelProvider } from "../../models/model-provider.models"; +import { ModelProviderService } from "../../services/model-provider.service"; + + +@Injectable() +export class modelProviderEffects { + private actions$ = inject(Actions); + + getLLMModels$ = createEffect(() => this.actions$.pipe( + ofType(modelProviderActions.getLLMModels), + switchMap(() => this.modelProviderService.getLLMModels().pipe( + map((models: ILLMModelProvider[]) => modelProviderActions.getLLMModelsSuccess({ models })), + catchError(error => of(modelProviderActions.getLLMModelsFailure({ error }))) + )) + )) + + getLLMModelTypes$ = createEffect(() => this.actions$.pipe( + ofType(modelProviderActions.getLLMModelTypes), + switchMap(() => this.modelProviderService.getLLMModelTypes().pipe( + map((model_types: string[]) => modelProviderActions.getLLMModelTypesSuccess({ model_types })), + catchError(error => of(modelProviderActions.getLLMModelTypesFailure({ error }))) + )) + )) + + constructor( + private modelProviderService: ModelProviderService + ) { } +} diff --git a/app/components/ui/src/app/store/model-provider/model-provider.reducers.ts b/app/components/ui/src/app/store/model-provider/model-provider.reducers.ts new file mode 100644 index 0000000..4e29710 --- /dev/null +++ b/app/components/ui/src/app/store/model-provider/model-provider.reducers.ts @@ -0,0 +1,52 @@ +import { createReducer, on } from "@ngrx/store"; +import { onLoadableError, onLoadableLoad, onLoadableSuccess } from "../../state/loadable.state"; +import { modelProviderActions } from "./model-provider.actions"; +import { createInitialModelProviderState } from "../../state/model-provider.state"; + +export const modelProviderReducer = createReducer( + createInitialModelProviderState(), + on(modelProviderActions.getLLMModels, state => ({ + ...state, + llm: { + ...state.llm, + ...onLoadableLoad(state.llm) + } + })), + on(modelProviderActions.getLLMModelsSuccess, (state, { models }) => ({ + ...state, + llm: { + ...state.llm, + ...onLoadableSuccess(state.llm), + models: models + } + })), + on(modelProviderActions.getLLMModelsFailure, (state, { error }) => ({ + ...state, + llm: { + ...state.llm, + ...onLoadableError(state.llm, error) + } + })), + on(modelProviderActions.getLLMModelTypes, state => ({ + ...state, + llm: { + ...state.llm, + ...onLoadableLoad(state.llm) + } + })), + on(modelProviderActions.getLLMModelTypesSuccess, (state, { model_types }) => ({ + ...state, + llm: { + ...state.llm, + ...onLoadableSuccess(state.llm), + model_types: model_types + } + })), + on(modelProviderActions.getLLMModelTypesFailure, (state, { error }) => ({ + ...state, + llm: { + ...state.llm, + ...onLoadableError(state.llm, error) + } + })) +) diff --git a/app/components/ui/src/app/store/model-provider/model-provider.selectors.ts b/app/components/ui/src/app/store/model-provider/model-provider.selectors.ts new file mode 100644 index 0000000..10cc020 --- /dev/null +++ b/app/components/ui/src/app/store/model-provider/model-provider.selectors.ts @@ -0,0 +1,6 @@ +import { createFeatureSelector, createSelector } from "@ngrx/store"; +import { ModelProviderState } from "../../state/model-provider.state"; + +export const selectModelProviderState = createFeatureSelector("model_provider"); +export const selectLLMModels = createSelector(selectModelProviderState, (state: ModelProviderState) => state.llm.models); +export const selectLLMModelTypes = createSelector(selectModelProviderState, (state: ModelProviderState) => state.llm.model_types); diff --git a/app/components/ui/src/app/store/reducers/app.reducers.ts b/app/components/ui/src/app/store/reducers/app.reducers.ts deleted file mode 100644 index 27458f4..0000000 --- a/app/components/ui/src/app/store/reducers/app.reducers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createReducer, on } from "@ngrx/store"; -import { onLoadableError, onLoadableLoad, onLoadableSuccess } from "../state/loadable.state"; -import { appActions } from "../actions/app.actions"; -import { createInitialAppState } from "../state/app.state"; - -export const appReducer = createReducer( - createInitialAppState(), - on(appActions.getACEVersionData, state => ({...onLoadableLoad(state)})), - on(appActions.getACEVersionDataSuccess, (state, { versionData }) => ({...onLoadableSuccess(state), versionData: {...versionData}})), - on(appActions.getACEVersionDataFailure, (state, { error }) => ({...onLoadableError(state, error)})) -) diff --git a/app/components/ui/src/app/store/selectors/app.selectors.ts b/app/components/ui/src/app/store/selectors/app.selectors.ts deleted file mode 100644 index e2b001e..0000000 --- a/app/components/ui/src/app/store/selectors/app.selectors.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createSelector, createFeatureSelector } from "@ngrx/store"; -import { AppState } from "../state/app.state"; - -export const selectAppState = createFeatureSelector("app_data"); -export const selectACEVersionData = createSelector(selectAppState, (state: AppState) => state.versionData); diff --git a/app/components/ui/src/app/store/settings/settings.actions.ts b/app/components/ui/src/app/store/settings/settings.actions.ts new file mode 100644 index 0000000..bb61a90 --- /dev/null +++ b/app/components/ui/src/app/store/settings/settings.actions.ts @@ -0,0 +1,17 @@ +import { createActionGroup, props, emptyProps } from "@ngrx/store"; +import { ISettings } from "../../models/settings.models"; + +export const settingsActions = createActionGroup({ + source: "settings", + events: { + getSettings: emptyProps(), + getSettingsSuccess: props<{ settings: ISettings }>(), + getSettingsFailure: props<{ error: Error }>(), + editSettings: props<{ settings: ISettings }>(), + editSettingsSuccess: emptyProps(), + editSettingsFailure: props<{ error: Error }>(), + resetSettings: emptyProps(), + resetSettingsSuccess: emptyProps(), + resetSettingsFailure: props<{ error: Error }>() + }, +}); diff --git a/app/components/ui/src/app/store/settings/settings.effects.ts b/app/components/ui/src/app/store/settings/settings.effects.ts new file mode 100644 index 0000000..f1faf3d --- /dev/null +++ b/app/components/ui/src/app/store/settings/settings.effects.ts @@ -0,0 +1,53 @@ +// DEPENDENCIES +//// Angular +import { inject, Injectable } from "@angular/core"; +import { createEffect, ofType, Actions } from "@ngrx/effects"; +import { map, catchError, of, switchMap } from "rxjs"; +//// Local +import { settingsActions } from "./settings.actions"; +import { ISettings } from "../../models/settings.models"; +import { SettingsService } from "../../services/settings.service"; + + +@Injectable() +export class SettingsEffects { + private actions$ = inject(Actions); + + getSettings$ = createEffect(() => this.actions$.pipe( + ofType(settingsActions.getSettings), + switchMap(() => this.settingsService.getSettings().pipe( + map((settings: ISettings) => settingsActions.getSettingsSuccess({ settings })), + catchError(error => of(settingsActions.getSettingsFailure({ error }))) + )) + )) + + editSettings$ = createEffect(() => this.actions$.pipe( + ofType(settingsActions.editSettings), + switchMap(({ settings }) => this.settingsService.editSettings(settings).pipe( + map(() => settingsActions.getSettings()), + catchError(error => of(settingsActions.editSettingsFailure({ error }))) + )) + )) + + editSettingsSuccess$ = createEffect(() => this.actions$.pipe( + ofType(settingsActions.editSettingsSuccess), + map(() => settingsActions.getSettings()) + )); + + resetSettings$ = createEffect(() => this.actions$.pipe( + ofType(settingsActions.resetSettings), + switchMap(() => this.settingsService.resetSettings().pipe( + map(() => settingsActions.resetSettingsSuccess()), + catchError(error => of(settingsActions.resetSettingsFailure({ error }))) + )) + )) + + resetSettingsSuccess$ = createEffect(() => this.actions$.pipe( + ofType(settingsActions.resetSettingsSuccess), + map(() => settingsActions.getSettings()) + )); + + constructor( + private settingsService: SettingsService + ) { } +} diff --git a/app/components/ui/src/app/store/settings/settings.reducers.ts b/app/components/ui/src/app/store/settings/settings.reducers.ts new file mode 100644 index 0000000..dd6c861 --- /dev/null +++ b/app/components/ui/src/app/store/settings/settings.reducers.ts @@ -0,0 +1,11 @@ +import { createReducer, on } from "@ngrx/store"; +import { onLoadableError, onLoadableLoad, onLoadableSuccess } from "../../state/loadable.state"; +import { settingsActions } from "./settings.actions"; +import { createInitialSettingsState } from "../../state/settings.state"; + +export const settingsReducer = createReducer( + createInitialSettingsState(), + on(settingsActions.getSettings, state => ({...onLoadableLoad(state)})), + on(settingsActions.getSettingsSuccess, (state, { settings }) => ({...onLoadableSuccess(state), settings: {...settings}})), + on(settingsActions.getSettingsFailure, (state, { error }) => ({...onLoadableError(state, error)})) +) diff --git a/app/components/ui/src/app/store/settings/settings.selectors.ts b/app/components/ui/src/app/store/settings/settings.selectors.ts new file mode 100644 index 0000000..b7eb338 --- /dev/null +++ b/app/components/ui/src/app/store/settings/settings.selectors.ts @@ -0,0 +1,4 @@ +import { createFeatureSelector, createSelector } from "@ngrx/store"; +import { SettingsState } from "../../state/settings.state"; + +export const selectSettingsState = createFeatureSelector("settings"); diff --git a/app/components/ui/src/app/store/state/app.state.ts b/app/components/ui/src/app/store/state/app.state.ts deleted file mode 100644 index 1c2859c..0000000 --- a/app/components/ui/src/app/store/state/app.state.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createDefaultLoadable, Loadable } from "./loadable.state"; -import { IACEVersionData } from "../../models/app.models"; - -export interface AppState extends Loadable { - versionData: IACEVersionData; -} - -export function createInitialAppState(): AppState { - return { - ...createDefaultLoadable(), - versionData: { - version: "0" - } - } -} - - diff --git a/app/components/ui/src/app/theme.ts b/app/components/ui/src/app/theme.ts new file mode 100644 index 0000000..dff6206 --- /dev/null +++ b/app/components/ui/src/app/theme.ts @@ -0,0 +1,19 @@ +import { Injectable, signal } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ThemeService { + private darkModeSubject = new BehaviorSubject(false); + darkMode$ = this.darkModeSubject.asObservable(); + + setDarkMode(enabled: boolean) { + this.darkModeSubject.next(enabled); + if (enabled) { + document.body.classList.add('dark'); + } else { + document.body.classList.remove('dark'); + } + } +} diff --git a/app/components/ui/src/index.html b/app/components/ui/src/index.html index 4fb474e..f85205d 100644 --- a/app/components/ui/src/index.html +++ b/app/components/ui/src/index.html @@ -9,7 +9,7 @@ - + diff --git a/app/components/ui/src/styles.scss b/app/components/ui/src/styles.scss index 7e7239a..ee87f52 100644 --- a/app/components/ui/src/styles.scss +++ b/app/components/ui/src/styles.scss @@ -1,4 +1,30 @@ /* You can add global styles to this file, and also import other style files */ -html, body { height: 100%; } +html, body { + height: 100%; + overscroll-behavior: none; +} body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } + +.main-page { + position: relative; + padding-top: 1rem; + padding-left: 3rem; + padding-right: 3rem; + padding-bottom: 1rem; +} + +.subsection { + display: flex; + padding-left: 1rem; + padding-right: 1rem; + flex-direction: column; + + &.multi-input { + row-gap: 0.75rem; + } + + .subsection-divider { + margin-top: 1rem; + } +} diff --git a/app/components/ui/src/theme.scss b/app/components/ui/src/theme.scss new file mode 100644 index 0000000..2f53fa7 --- /dev/null +++ b/app/components/ui/src/theme.scss @@ -0,0 +1,128 @@ +/* These tokens are generated using https://themes.angular-material.dev/ */ +/* Preview: https://themes.angular-material.dev/?seed-error=%23ffb4ab&seed-neutral=%23767778&seed-neutral-variant=%2371787a&seed-primary=%233D636B&seed-secondary=%23bac9cd&seed-tertiary=%23d1c0e4 */ +/* Seed Colors: primary: #3D636B, secondary: #bac9cd, tertiary: #d1c0e4, error: #ffb4ab, neutral: #767778, neutral-variant: #71787a */ + +@use "@angular/material" as mat; + + +/* Light Theme */ +:root, :host { + @include mat.theme-overrides(( + primary: #3e646c, + on-primary: #ffffff, + primary-container: #c1e9f3, + on-primary-container: #001f25, + inverse-primary: #a6cdd6, + primary-fixed: #c1e9f3, + primary-fixed-dim: #a6cdd6, + on-primary-fixed: #001f25, + on-primary-fixed-variant: #254c54, + secondary: #536164, + on-secondary: #ffffff, + secondary-container: #d6e5e9, + on-secondary-container: #101e21, + secondary-fixed: #d6e5e9, + secondary-fixed-dim: #bac9cd, + on-secondary-fixed: #101e21, + on-secondary-fixed-variant: #3b494d, + tertiary: #665978, + on-tertiary: #ffffff, + tertiary-container: #eddcff, + on-tertiary-container: #211631, + tertiary-fixed: #eddcff, + tertiary-fixed-dim: #d1c0e4, + on-tertiary-fixed: #211631, + on-tertiary-fixed-variant: #4e415f, + background: #fcf8f8, + on-background: #1c1b1b, + surface: #fcf8f8, + surface-dim: #ddd9d9, + surface-bright: #fcf8f8, + surface-container-lowest: #ffffff, + surface-container-low: #f7f3f2, + surface-container: #f1edec, + surface-container-high: #ebe7e7, + surface-container-highest: #e5e2e1, + on-surface: #1c1b1b, + shadow: #000000, + scrim: #000000, + surface-tint: #5d5e5f, + inverse-surface: #313030, + inverse-on-surface: #f4f0ef, + outline: #747779, + outline-variant: #c3c7c8, + neutral: #797776, + neutral10: #1c1b1b, + error: #ba1a1a, + error-container: #ffdad6, + on-error: #ffffff, + on-error-container: #410002, + surface-variant: #dfe3e4, + on-surface-variant: #434749, + neutral-variant: #747879, + neutral-variant20: #2d3132, + inverse-secondary: #bac9cd, + inverse-tertiary: #d1c0e4, + )); +} + +/* Dark Theme */ +.dark { + @include mat.theme-overrides(( + primary: #a6cdd6, + on-primary: #0a353d, + primary-container: #254c54, + on-primary-container: #c1e9f3, + inverse-primary: #3e646c, + primary-fixed: #c1e9f3, + primary-fixed-dim: #a6cdd6, + on-primary-fixed: #001f25, + on-primary-fixed-variant: #254c54, + secondary: #bac9cd, + on-secondary: #253336, + secondary-container: #3b494d, + on-secondary-container: #d6e5e9, + secondary-fixed: #d6e5e9, + secondary-fixed-dim: #bac9cd, + on-secondary-fixed: #101e21, + on-secondary-fixed-variant: #3b494d, + tertiary: #d1c0e4, + on-tertiary: #372b47, + tertiary-container: #4e415f, + on-tertiary-container: #eddcff, + tertiary-fixed: #eddcff, + tertiary-fixed-dim: #d1c0e4, + on-tertiary-fixed: #211631, + on-tertiary-fixed-variant: #4e415f, + background: #141313, + on-background: #e5e2e1, + surface: #141313, + surface-dim: #141313, + surface-bright: #3a3939, + surface-container-lowest: #0e0e0e, + surface-container-low: #1c1b1b, + surface-container: #201f1f, + surface-container-high: #2a2a2a, + surface-container-highest: #353434, + on-surface: #e5e2e1, + shadow: #000000, + scrim: #000000, + surface-tint: #c6c6c7, + inverse-surface: #e5e2e1, + inverse-on-surface: #313030, + outline: #8e9193, + outline-variant: #434749, + neutral: #797776, + neutral10: #1c1b1b, + error: #ffb4ab, + error-container: #93000a, + on-error: #690005, + on-error-container: #ffdad6, + surface-variant: #434749, + on-surface-variant: #c3c7c8, + neutral-variant: #747879, + neutral-variant20: #2d3132, + inverse-secondary: #536164, + inverse-tertiary: #665978, + )); +} diff --git a/app/constants/__init__.py b/app/constants/__init__.py index ac3725b..96e898a 100644 --- a/app/constants/__init__.py +++ b/app/constants/__init__.py @@ -1,3 +1,4 @@ +from .api_routes import APIRoutes from .components import Components from .container_folders import ContainerFolders from .defaults import Defaults diff --git a/app/constants/api_routes.py b/app/constants/api_routes.py new file mode 100644 index 0000000..0d0f159 --- /dev/null +++ b/app/constants/api_routes.py @@ -0,0 +1,9 @@ +# DEPENDENCIES +## Local +from .base_enum import BaseEnum + + +class APIRoutes(BaseEnum): + """Enum""" + ROOT: str = "/" + MODEL_PROVIDER: str = f"{ROOT}model-provider/" diff --git a/app/constants/model_providers.py b/app/constants/model_providers.py index e9c7531..cc109b4 100644 --- a/app/constants/model_providers.py +++ b/app/constants/model_providers.py @@ -4,7 +4,7 @@ class ModelProviders(BaseEnum): - CLAUDE: str = "claude" + ANTHROPIC: str = "anthropic" DEEPSEEK: str = "deepseek" GOOGLE_VERTEX_AI: str = "google_vertex_ai" GROK: str = "grok" diff --git a/app/containers/Containerfile b/app/containers/Containerfile index 8688adf..bf4c125 100644 --- a/app/containers/Containerfile +++ b/app/containers/Containerfile @@ -1,9 +1,9 @@ FROM python:3.13-bookworm -COPY . /home/ace +COPY ./requirements /home/requirements RUN apt-get update && apt-get install -y libffi-dev nats-server nodejs npm && \ - pip install -r /home/ace/requirements && \ + pip install -r /home/requirements && \ apt-get purge -y libffi-dev python3-dev && \ npm install -g @angular/cli && \ cd /home/ace/components/ui && \ @@ -11,4 +11,5 @@ RUN apt-get update && apt-get install -y libffi-dev nats-server nodejs npm && \ ng analytics off && \ rm -rf /var/cache/* +RUN mkdir /home/ace WORKDIR /home/ace diff --git a/app/models/api_schemas/controller.py b/app/models/api_schemas/controller.py index 4e6004c..5edbfe9 100644 --- a/app/models/api_schemas/controller.py +++ b/app/models/api_schemas/controller.py @@ -13,6 +13,10 @@ # RESPONSES class GetVersionDetailsResponse(BaseModel): version: str + authors: list[str] + license: str + last_update: str + rebuild_date: str GetSettingsResponse: type[BaseModel] = ControllerSettingsSchema diff --git a/app/models/config/controller.py b/app/models/config/controller.py index 2845931..d6fe407 100644 --- a/app/models/config/controller.py +++ b/app/models/config/controller.py @@ -3,12 +3,14 @@ from pydantic import BaseModel ## Local from constants import Defaults -from .defaults import DEFAULT_LAYER_SETTINGS, DEFAULT_MODEL_PROVIDER_SETTINGS +from .defaults import DEFAULT_LAYER_SETTINGS, DEFAULT_MODEL_PROVIDER_SETTINGS, DEFAULT_UI_SETTINGS from .layers import LayerSettings from .model_providers import ModelProviderSettings +from .ui import UISettings class ControllerSettingsSchema(BaseModel): ace_name: str = Defaults.ACE_NAME - layer_settings: list[LayerSettings] = DEFAULT_LAYER_SETTINGS + ui_settings: UISettings = DEFAULT_UI_SETTINGS model_provider_settings: ModelProviderSettings = DEFAULT_MODEL_PROVIDER_SETTINGS + layer_settings: list[LayerSettings] = DEFAULT_LAYER_SETTINGS diff --git a/app/models/config/defaults.py b/app/models/config/defaults.py index 071b3a7..2d2448d 100644 --- a/app/models/config/defaults.py +++ b/app/models/config/defaults.py @@ -7,35 +7,14 @@ ) from .layers import LayerSettings from .model_providers import ModelProviderSettings, model_types +from .ui import UISettings -# LAYERS -DEFAULT_LAYER_SETTINGS: list[LayerSettings] = [ - LayerSettings( - layer_name=LayerTypes.ASPIRATIONAL, - model_type=LLMModelTypes.REASONER - ), - LayerSettings( - layer_name=LayerTypes.GLOBAL_STRATEGY, - model_type=LLMModelTypes.REASONER - ), - LayerSettings( - layer_name=LayerTypes.AGENT_MODEL, - model_type=LLMModelTypes.GENERALIST - ), - LayerSettings( - layer_name=LayerTypes.EXECUTIVE_FUNCTION, - model_type=LLMModelTypes.GENERALIST - ), - LayerSettings( - layer_name=LayerTypes.COGNITIVE_CONTROL, - model_type=LLMModelTypes.EFFICIENT - ), - LayerSettings( - layer_name=LayerTypes.TASK_PROSECUTION, - model_type=LLMModelTypes.FUNCTION_CALLER - ) -] +# UI +DEFAULT_UI_SETTINGS: UISettings = UISettings( + dark_mode=True, + show_footer=False +) # MODEL PROVIDERS @@ -92,3 +71,32 @@ llm_model_type_settings=DEFAULT_LLM_MODEL_TYPE_SETTINGS, rag_model_type_settings=DEFAULT_RAG_MODEL_TYPE_SETTINGS ) + + +# LAYERS +DEFAULT_LAYER_SETTINGS: list[LayerSettings] = [ + LayerSettings( + layer_name=LayerTypes.ASPIRATIONAL, + model_type=LLMModelTypes.REASONER + ), + LayerSettings( + layer_name=LayerTypes.GLOBAL_STRATEGY, + model_type=LLMModelTypes.REASONER + ), + LayerSettings( + layer_name=LayerTypes.AGENT_MODEL, + model_type=LLMModelTypes.GENERALIST + ), + LayerSettings( + layer_name=LayerTypes.EXECUTIVE_FUNCTION, + model_type=LLMModelTypes.GENERALIST + ), + LayerSettings( + layer_name=LayerTypes.COGNITIVE_CONTROL, + model_type=LLMModelTypes.EFFICIENT + ), + LayerSettings( + layer_name=LayerTypes.TASK_PROSECUTION, + model_type=LLMModelTypes.FUNCTION_CALLER + ) +] diff --git a/app/models/config/model_providers/__init__.py b/app/models/config/model_providers/__init__.py index ffc476f..a2791c7 100644 --- a/app/models/config/model_providers/__init__.py +++ b/app/models/config/model_providers/__init__.py @@ -1,2 +1,2 @@ from .model_providers import ModelProviderSettings -from .indiividual_providers import IndividualProviderSettings \ No newline at end of file +from .individual_providers import IndividualProviderSettings \ No newline at end of file diff --git a/app/models/config/model_providers/indiividual_providers.py b/app/models/config/model_providers/individual_providers.py similarity index 91% rename from app/models/config/model_providers/indiividual_providers.py rename to app/models/config/model_providers/individual_providers.py index 08b18a0..0b9163a 100644 --- a/app/models/config/model_providers/indiividual_providers.py +++ b/app/models/config/model_providers/individual_providers.py @@ -4,5 +4,6 @@ class IndividualProviderSettings(BaseModel): + name: str enabled: bool = False api_key: str = "" diff --git a/app/models/config/model_providers/model_providers.py b/app/models/config/model_providers/model_providers.py index d35fe33..e79f9b5 100644 --- a/app/models/config/model_providers/model_providers.py +++ b/app/models/config/model_providers/model_providers.py @@ -2,18 +2,49 @@ ## Third-Party from pydantic import BaseModel ## Local -from .indiividual_providers import IndividualProviderSettings +from constants import ModelProviders +from .individual_providers import IndividualProviderSettings from . import model_types class ModelProviderSettings(BaseModel): - claude_settings: IndividualProviderSettings = IndividualProviderSettings() - deepseek_settings: IndividualProviderSettings = IndividualProviderSettings() - google_vertex_ai_settings: IndividualProviderSettings = IndividualProviderSettings() - grok_settings: IndividualProviderSettings = IndividualProviderSettings() - groq_settings: IndividualProviderSettings = IndividualProviderSettings() - ollama_settings: IndividualProviderSettings = IndividualProviderSettings(enabled=True) - openai_settings: IndividualProviderSettings = IndividualProviderSettings() + individual_provider_settings: list[IndividualProviderSettings] = [ + IndividualProviderSettings( + name=ModelProviders.ANTHROPIC, + enabled=False, + api_key="" + ), + IndividualProviderSettings( + name=ModelProviders.DEEPSEEK, + enabled=False, + api_key="" + ), + IndividualProviderSettings( + name=ModelProviders.GOOGLE_VERTEX_AI, + enabled=False, + api_key="" + ), + IndividualProviderSettings( + name=ModelProviders.GROK, + enabled=False, + api_key="" + ), + IndividualProviderSettings( + name=ModelProviders.GROQ, + enabled=False, + api_key="" + ), + IndividualProviderSettings( + name=ModelProviders.OLLAMA, + enabled=True, + api_key="" + ), + IndividualProviderSettings( + name=ModelProviders.OPENAI, + enabled=False, + api_key="" + ) + ] three_d_model_type_settings: list[model_types.ThreeDModelTypeSetting] = [] audio_model_type_settings: list[model_types.AudioModelTypeSetting] = [] diff --git a/app/models/config/ui.py b/app/models/config/ui.py new file mode 100644 index 0000000..50f19b0 --- /dev/null +++ b/app/models/config/ui.py @@ -0,0 +1,8 @@ +# DEPENDENCIES +## Third-Party +from pydantic import BaseModel + + +class UISettings(BaseModel): + dark_mode: bool + show_footer: bool diff --git a/app/models/data/initial.py b/app/models/data/initial.py index 6215eeb..7b552bc 100644 --- a/app/models/data/initial.py +++ b/app/models/data/initial.py @@ -11,7 +11,7 @@ # Claude LLMModelProvider( id="0194c740-6300-7184-8eff-2be664245b03", - model_provider=ModelProviders.CLAUDE, + model_provider=ModelProviders.ANTHROPIC, name="Claude 3.5 Haiku", model_name="claude-3-5-haiku-latest", default=True, @@ -24,7 +24,7 @@ ), LLMModelProvider( id="0194c740-98bb-76db-95ed-ef6cfa62839b", - model_provider=ModelProviders.CLAUDE, + model_provider=ModelProviders.ANTHROPIC, name="Claude 3.5 Sonnet", model_name="claude-3-5-sonnet-latest", default=True, diff --git a/app/shell.py b/app/shell.py index d29e0f9..e2f42fe 100644 --- a/app/shell.py +++ b/app/shell.py @@ -56,12 +56,12 @@ def execute_shell( return "".join(stdout_lines) -def exec_check_exists(check_command: str, keyword: str) -> bool: +def shell_check_exists(shell_command: str, keyword_to_find: str) -> bool: """Checks if the keyword exists in the output of the check_command""" - logger.debug(f'Checking using "{check_command}" for {keyword}...') - existing_terms = frozenset(execute_shell(check_command).split("\n")) + logger.debug(f'Checking using "{shell_command}" for {keyword_to_find}...') + existing_terms = frozenset(execute_shell(shell_command).split("\n")) logger.debug(f"Existing Terms: {existing_terms}") for entry in existing_terms: - if keyword in entry: + if keyword_to_find in entry: return True return False diff --git a/app/startup.py b/app/startup.py index 38fa6a5..806acd1 100755 --- a/app/startup.py +++ b/app/startup.py @@ -8,7 +8,7 @@ from constants import DictKeys, Files, Names, ShellCommands from constants.files import setup_user_deployment_file from logger import logger -from shell import execute_shell, exec_check_exists +from shell import execute_shell, shell_check_exists # ARGUMENTS @@ -22,17 +22,17 @@ def _get_arguments() -> dict[str, bool]: # PREPARATION -def setup_network() -> None: - if not exec_check_exists(check_command=ShellCommands.CHECK_NETWORK, keyword=Names.NETWORK): +def _setup_network() -> None: + if not shell_check_exists(check_command=ShellCommands.CHECK_NETWORK, keyword=Names.NETWORK): logger.startup("First time setting up network...") execute_shell(ShellCommands.CREATE_NETWORK) -def update() -> bool: +def _update() -> bool: """Update the git repo, returning True if container needs to be rebuilt""" logger.startup(f"Updating {Names.ACE}...") execute_shell(ShellCommands.UPDATE) -def check_if_latest_build() -> bool: +def _check_if_latest_build() -> bool: """Compares the update history and version files to check if the container needs to be rebuilt""" updates: dict[str] = {} history: dict[str] = {} @@ -50,12 +50,12 @@ def check_if_latest_build() -> bool: return True return False -def build_container(force_build: bool = False): +def _build_container_image(force_build: bool = False): """ A function which builds if image doesn't exist or if force_build flag is set """ logger.startup("Checking if build is required...") - image_exists: bool = exec_check_exists(check_command=ShellCommands.CHECK_IMAGES, keyword=Names.IMAGE) + image_exists: bool = shell_check_exists(check_command=ShellCommands.CHECK_IMAGES, keyword=Names.IMAGE) should_build: bool = not image_exists or force_build if not should_build: logger.startup("Image already exists, skipping build...") @@ -79,22 +79,22 @@ def build_container(force_build: bool = False): # DEPLOYMENT -def stop_cluster() -> None: +def _stop_cluster() -> None: """ Stops the ACE cluster if it is running """ - exists: bool = exec_check_exists(ShellCommands.CHECK_PODS, Names.ACE) + exists: bool = shell_check_exists(ShellCommands.CHECK_PODS, Names.ACE) if not exists: logger.warn(f"{Names.ACE} is not running! Cannot stop...") return logger.startup(f"Stopping {Names.ACE}...") execute_shell(ShellCommands.STOP_CLUSTER) -def start_cluster(force_restart: bool) -> None: +def _start_cluster(force_restart: bool) -> None: """ Start the ACE cluster if it isn't running, handling restarts """ - exists: bool = exec_check_exists(ShellCommands.CHECK_PODS, Names.ACE) + exists: bool = shell_check_exists(ShellCommands.CHECK_PODS, Names.ACE) if not force_restart and exists: logger.startup(f"ACE is already running... Run with --{DictKeys.RESTART} to restart!") return @@ -106,27 +106,27 @@ def start_cluster(force_restart: bool) -> None: # MAIN -def startup(): +def _startup(): arguments: dict[str, bool] = _get_arguments() dev: bool = arguments[DictKeys.DEV] force_build: bool = arguments[DictKeys.BUILD] setup_user_deployment_file(dev) - setup_network() + _setup_network() if not dev: - update() - update_build: bool = check_if_latest_build() + _update() + update_build: bool = _check_if_latest_build() if update_build: force_build = True - build_container(force_build=force_build) + _build_container_image(force_build=force_build) execute_shell(ShellCommands.CLEAR_OLD_IMAGES) if arguments[DictKeys.STOP]: - stop_cluster() + _stop_cluster() return - start_cluster(force_restart=arguments[DictKeys.RESTART]) + _start_cluster(force_restart=arguments[DictKeys.RESTART]) if __name__ == "__main__": - startup() + _startup() diff --git a/app/version b/app/version index f4db7ab..ecf10e7 100644 --- a/app/version +++ b/app/version @@ -1,4 +1,9 @@ { - "version": "0.0.0", - "rebuild_date": "2025-02-02" -} \ No newline at end of file + "version": "0.0.0", + "authors": [ + "Jayfalls" + ], + "license": "MIT", + "last_update": "2025-03-23 15:44:44", + "rebuild_date": "2025-02-02" +} diff --git a/documentation/personal.md b/documentation/personal.md new file mode 100644 index 0000000..c4d9319 --- /dev/null +++ b/documentation/personal.md @@ -0,0 +1,17 @@ +[⬆️](..) + +## Component Commands +- UI +```shell +npm10 run start-local +``` +- Controller +```shell +source ../.venv/bin/activate +ACE_LOGGER_FILE_NAME="controller" ACE_LOGGER_VERBOSE="" ./component.py --controller --dev +``` + +## Podman Weirdness +```shell +podman machine stop && podman machine start +``` \ No newline at end of file diff --git a/tests/unit/components/controller/api/test_service.py b/tests/unit/components/controller/api/test_service.py index 981999d..c83afa9 100644 --- a/tests/unit/components/controller/api/test_service.py +++ b/tests/unit/components/controller/api/test_service.py @@ -8,7 +8,7 @@ from app.models.config.controller import ControllerSettingsSchema from app.models.config.layers import LayerSettings from app.models.config.model_providers import IndividualProviderSettings, ModelProviderSettings -from app.components.controller.api.service import ( +from app.components.controller.api.services.root import ( _get_settings, edit_settings_data ) @@ -25,7 +25,6 @@ class ExistingSettings: ).model_dump() ] MODEL_PROVIDER_SETTINGS = ModelProviderSettings( - claude_settings=IndividualProviderSettings(enabled=True), llm_model_type_settings = [], rag_model_type_settings = [] ).model_dump() diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py index 766c092..79d4e09 100644 --- a/tests/unit/test_shell.py +++ b/tests/unit/test_shell.py @@ -2,7 +2,7 @@ ## Third-Party import pytest ## Local -from app.shell import execute_shell, exec_check_exists +from app.shell import execute_shell, shell_check_exists @pytest.mark.parametrize("command,expected_output", [ @@ -21,11 +21,11 @@ def test_execute_shell(command, expected_output, caplog): "/bin/sh: 1: invalid_command: not found" ]), "Error message should be printed" -def test_exec_check_exists(): - """Test the exec_check_exists function.""" - result = exec_check_exists("ls -a", ".") +def test_shell_check_exists(): + """Test the shell_check_exists function.""" + result = shell_check_exists("ls -a", ".") assert result is True, "Expected True, but got False" - result = exec_check_exists("ls -a", "non_existent_file") + result = shell_check_exists("ls -a", "non_existent_file") assert result is False, "Expected False, but got True" def test_should_print_result(capsys):