diff --git a/ace b/ace
index 1109cc1..ad3268d 100755
--- a/ace
+++ b/ace
@@ -7,6 +7,7 @@ ACE_LOGGER_VERBOSE_ENV="."
# STARTUP
run_tests() {
echo "Installing/Updating test dependencies..."
+ pip install --upgrade -r app/requirements
pip install --upgrade -r tests/requirements
python -m pytest tests/unit/ -v
diff --git a/app/components/controller/api/__init__.py b/app/components/controller/api/__init__.py
new file mode 100644
index 0000000..1c363db
--- /dev/null
+++ b/app/components/controller/api/__init__.py
@@ -0,0 +1 @@
+from .routes import controller_api
\ No newline at end of file
diff --git a/app/components/controller/api/routes.py b/app/components/controller/api/routes.py
new file mode 100644
index 0000000..8e54830
--- /dev/null
+++ b/app/components/controller/api/routes.py
@@ -0,0 +1,72 @@
+# DEPENDENCIES
+## Third-Party
+from fastapi import FastAPI, HTTPException
+from fastapi.middleware.cors import CORSMiddleware
+from http import HTTPStatus
+from pydantic import ValidationError
+## Local
+from constants import Defaults, DefaultAPIResponseSchema, Names
+from logger import logger
+from . import service
+from .schemas import (
+ EditSettingsRequest,
+ GetSettingsResponse, GetVersionDetailsResponse
+)
+
+
+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
+)
+
+
+# ROUTES
+@controller_api.get(
+ "/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()
+ except ValidationError as error:
+ logger.error(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)
+
+@controller_api.post(
+ "/settings",
+ response_model=DefaultAPIResponseSchema,
+ 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.dict())
+ return DefaultAPIResponseSchema(message="Settings data updated successfully!")
+ except ValidationError as error:
+ logger.error(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)
+
+@controller_api.get(
+ "/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()
+ except ValidationError as error:
+ logger.error(error)
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Version 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/schemas.py b/app/components/controller/api/schemas.py
new file mode 100644
index 0000000..857c996
--- /dev/null
+++ b/app/components/controller/api/schemas.py
@@ -0,0 +1,26 @@
+# DEPENDENCIES
+## Third-Party
+from pydantic import BaseModel, validator
+## Local
+from constants import Defaults
+
+
+class SettingsSchema(BaseModel):
+ # These are not required
+ ace_name: str = Defaults.ACE_NAME
+ model_provider: str = Defaults.MODEL_PROVIDER
+ temperature: float = Defaults.TEMPERATURE
+
+ @validator("temperature")
+ def validate_temperature(cls, value):
+ return min(max(0.0, value), 1.0)
+
+
+# REQUESTS
+EditSettingsRequest: type[BaseModel] = SettingsSchema
+
+# RESPONSES
+GetSettingsResponse: type[BaseModel] = SettingsSchema
+
+class GetVersionDetailsResponse(BaseModel):
+ version: str
diff --git a/app/components/controller/api/service.py b/app/components/controller/api/service.py
new file mode 100644
index 0000000..90c8be3
--- /dev/null
+++ b/app/components/controller/api/service.py
@@ -0,0 +1,34 @@
+# DEPENDENCIES
+## Built-In
+import json
+## Local
+from constants import DictKeys, Files, ModelProviders
+
+
+# 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())
+ return settings
+
+
+# ROUTES
+def get_settings_data() -> dict:
+ return _get_settings()
+
+def edit_settings_data(updated_settings: dict):
+ settings: dict = _get_settings()
+ new_model_provider: str | None = updated_settings.get(DictKeys.MODEL_PROVIDER)
+ if new_model_provider:
+ available_model_providers: dict = ModelProviders.get_frozenset()
+ if new_model_provider not in available_model_providers:
+ raise ValueError(f"Invalid model provider: {new_model_provider}")
+ settings.update(updated_settings)
+ with open(Files.CONTROLLER_SETTINGS, "w", encoding="utf-8") as settings_file:
+ settings_file.write(json.dumps(settings))
+
+def get_version_data() -> dict:
+ with open(Files.VERSION, "r", encoding="utf-8") as settings_file:
+ return json.loads(settings_file.read())
+
\ No newline at end of file
diff --git a/app/components/controller/start.py b/app/components/controller/start.py
index 1197928..733e82d 100644
--- a/app/components/controller/start.py
+++ b/app/components/controller/start.py
@@ -1,6 +1,23 @@
# DEPENDENCIES
+## Built-In
+import json
+## Third-Party
+import uvicorn
## Local
+from constants import NetworkPorts
from logger import logger
+from .api import controller_api, service
+from .api.schemas import SettingsSchema
+from constants import Files
+
+
+def _ensure_settings() -> dict:
+ settings: dict = service._get_settings()
+ settings = SettingsSchema(**settings).dict()
+ with open(Files.CONTROLLER_SETTINGS, "w", encoding="utf-8") as settings_file:
+ settings_file.write(json.dumps(settings))
def start_controller(component_type: str, dev: bool) -> None:
logger.startup(f"Starting {component_type}...")
+ _ensure_settings()
+ uvicorn.run(controller_api, host="0.0.0.0", port=int(NetworkPorts.CONTROLLER))
diff --git a/app/components/ui/package-lock.json b/app/components/ui/package-lock.json
index 5bfd994..54fac81 100644
--- a/app/components/ui/package-lock.json
+++ b/app/components/ui/package-lock.json
@@ -18,6 +18,12 @@
"@angular/platform-browser": "^19.1.0",
"@angular/platform-browser-dynamic": "^19.1.0",
"@angular/router": "^19.1.0",
+ "@ngrx/component": "^19.0.1",
+ "@ngrx/data": "^19.0.1",
+ "@ngrx/effects": "^19.0.1",
+ "@ngrx/entity": "^19.0.1",
+ "@ngrx/store": "^19.0.1",
+ "@ngrx/store-devtools": "^19.0.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
@@ -2768,6 +2774,86 @@
"node": ">= 10"
}
},
+ "node_modules/@ngrx/component": {
+ "version": "19.0.1",
+ "resolved": "https://registry.npmjs.org/@ngrx/component/-/component-19.0.1.tgz",
+ "integrity": "sha512-uuGgjB8eAcIj9IiaMUxbIqCHo++jcPPsV2zEiF0q6v8CY07wrCQnn02jxwY09eNCUnvXQiGfYosV2wIWF1gu5A==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^19.0.0",
+ "@angular/core": "^19.0.0",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
+ "node_modules/@ngrx/data": {
+ "version": "19.0.1",
+ "resolved": "https://registry.npmjs.org/@ngrx/data/-/data-19.0.1.tgz",
+ "integrity": "sha512-B8HVkBauCycRB9VlvpGovIbldnXrMz9tga0SUQa0tSTQ3IBYY3IEtHO7Q4X1LnCxxuIw8sR6guT+erBaURHDfg==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^19.0.0",
+ "@angular/core": "^19.0.0",
+ "@ngrx/effects": "19.0.1",
+ "@ngrx/entity": "19.0.1",
+ "@ngrx/store": "19.0.1",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
+ "node_modules/@ngrx/effects": {
+ "version": "19.0.1",
+ "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-19.0.1.tgz",
+ "integrity": "sha512-q+eztS1zN1247BtUZ41gxhumj4wMmvtfdSMfkFEuu6zuA57Vbx8zitEsw9boqPGtP5E4Cj5HKJLSJrl2kgwgcQ==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/core": "^19.0.0",
+ "@ngrx/store": "19.0.1",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
+ "node_modules/@ngrx/entity": {
+ "version": "19.0.1",
+ "resolved": "https://registry.npmjs.org/@ngrx/entity/-/entity-19.0.1.tgz",
+ "integrity": "sha512-Dw6UhLi7tGVWb/pLgYI81k1fPxCIbCWMztGKj08e8fLWoMvTWYTbG5tFbOJNSa9D3gPmxsBbbS8VMNqcUgl7wQ==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/core": "^19.0.0",
+ "@ngrx/store": "19.0.1",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
+ "node_modules/@ngrx/store": {
+ "version": "19.0.1",
+ "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-19.0.1.tgz",
+ "integrity": "sha512-+6eBLb+0rdJ856JRuKnvSzFxv1ISbYuX/OM12dMPf4wm+ddxjhyvi6tF8lPiNnaYb717PGNxXQzBFIGfIs4zGQ==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/core": "^19.0.0",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
+ "node_modules/@ngrx/store-devtools": {
+ "version": "19.0.1",
+ "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-19.0.1.tgz",
+ "integrity": "sha512-afvr6NHh12LpYrOH9JKKEEi/z2DHq/wJ45hRn3mrcRDArQTKpTl7V2eu0dYgjGItSRSeJ2drzzKGjccL61PPSg==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/core": "^19.0.0",
+ "@ngrx/store": "19.0.1",
+ "rxjs": "^6.5.3 || ^7.5.0"
+ }
+ },
"node_modules/@ngtools/webpack": {
"version": "19.1.5",
"dev": true,
diff --git a/app/components/ui/package.json b/app/components/ui/package.json
index 0f77025..2722ef4 100644
--- a/app/components/ui/package.json
+++ b/app/components/ui/package.json
@@ -1,5 +1,5 @@
{
- "name": "ui",
+ "name": "ACE",
"version": "0.0.0",
"scripts": {
"ng": "ng",
@@ -21,6 +21,12 @@
"@angular/platform-browser": "^19.1.0",
"@angular/platform-browser-dynamic": "^19.1.0",
"@angular/router": "^19.1.0",
+ "@ngrx/component": "^19.0.1",
+ "@ngrx/data": "^19.0.1",
+ "@ngrx/effects": "^19.0.1",
+ "@ngrx/entity": "^19.0.1",
+ "@ngrx/store": "^19.0.1",
+ "@ngrx/store-devtools": "^19.0.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
@@ -38,4 +44,4 @@
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.7.2"
}
-}
\ No newline at end of file
+}
diff --git a/app/components/ui/src/app/api.urls.ts b/app/components/ui/src/app/api.urls.ts
new file mode 100644
index 0000000..38b1ad8
--- /dev/null
+++ b/app/components/ui/src/app/api.urls.ts
@@ -0,0 +1,5 @@
+const defaultClusterURL: string = `http://127.0.0.1`;
+
+export const serviceURLs = {
+ controller: defaultClusterURL + ":2349"
+};
diff --git a/app/components/ui/src/app/app.component.html b/app/components/ui/src/app/app.component.html
index 9e8a8ca..c2c1903 100644
--- a/app/components/ui/src/app/app.component.html
+++ b/app/components/ui/src/app/app.component.html
@@ -1 +1 @@
-
+
diff --git a/app/components/ui/src/app/app.component.ts b/app/components/ui/src/app/app.component.ts
index 701580e..a1b53d1 100644
--- a/app/components/ui/src/app/app.component.ts
+++ b/app/components/ui/src/app/app.component.ts
@@ -1,15 +1,22 @@
-import { Component } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
-import { ACESidebarComponent } from './components/sidebar/sidebar.component';
+import { Component, OnInit } from "@angular/core";
+import { Store } from "@ngrx/store";
+import { appActions } from "./store/actions/app.actions";
+import { ACERootpageComponent } from "./components/rootpage/rootpage.component";
@Component({
- selector: 'app-root',
+ selector: "app-root",
imports: [
- ACESidebarComponent
+ ACERootpageComponent
],
- templateUrl: './app.component.html',
- styleUrl: './app.component.scss'
+ templateUrl: "./app.component.html",
+ styleUrl: "./app.component.scss"
})
-export class AppComponent {
- title = 'ACE';
+export class AppComponent implements OnInit {
+ title = "ACE";
+
+ constructor(private store: Store) {}
+
+ ngOnInit(): void {
+ this.store.dispatch(appActions.getACEVersionData());
+ }
}
diff --git a/app/components/ui/src/app/app.config.ts b/app/components/ui/src/app/app.config.ts
index 96116fd..f5521d8 100644
--- a/app/components/ui/src/app/app.config.ts
+++ b/app/components/ui/src/app/app.config.ts
@@ -1,9 +1,25 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
-
+import { provideEffects } from '@ngrx/effects';
+import { provideStore } from '@ngrx/store';
+import { provideStoreDevtools } from '@ngrx/store-devtools';
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';
export const appConfig: ApplicationConfig = {
- providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimationsAsync()]
+ providers: [
+ provideAnimationsAsync(),
+ provideEffects([AppEffects]),
+ provideHttpClient(),
+ provideStore({ app_data: appReducer }),
+ provideStoreDevtools({
+ maxAge: 25,
+ logOnly: false
+ }),
+ provideRouter(routes),
+ provideZoneChangeDetection({ eventCoalescing: true })
+ ]
};
diff --git a/app/components/ui/src/app/components/footer/footer.component.html b/app/components/ui/src/app/components/footer/footer.component.html
new file mode 100644
index 0000000..e7400a7
--- /dev/null
+++ b/app/components/ui/src/app/components/footer/footer.component.html
@@ -0,0 +1,6 @@
+
+
+
diff --git a/app/components/ui/src/app/components/footer/footer.component.scss b/app/components/ui/src/app/components/footer/footer.component.scss
new file mode 100644
index 0000000..e0ae1fa
--- /dev/null
+++ b/app/components/ui/src/app/components/footer/footer.component.scss
@@ -0,0 +1,8 @@
+.footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 2rem;
+ gap: 2rem;
+ outline: auto;
+}
diff --git a/app/components/ui/src/app/components/footer/footer.component.ts b/app/components/ui/src/app/components/footer/footer.component.ts
new file mode 100644
index 0000000..8c4f14b
--- /dev/null
+++ b/app/components/ui/src/app/components/footer/footer.component.ts
@@ -0,0 +1,23 @@
+import { Component, OnInit } from '@angular/core';
+import { Store } from '@ngrx/store';
+import { Observable } from 'rxjs';
+import { AppState } from '../../store/state/app.state';
+import { selectAppState } from '../../store/selectors/app.selectors';
+
+@Component({
+ selector: 'ace-footer',
+ templateUrl: './footer.component.html',
+ styleUrls: ['./footer.component.scss']
+})
+export class ACEFooterComponent implements OnInit {
+ versionData$: Observable;
+ version: string = "0";
+
+ constructor(private store: Store) {
+ this.versionData$ = this.store.select(selectAppState);
+ }
+
+ ngOnInit(): void {
+ this.versionData$.subscribe( versionData => this.version = versionData.versionData.version);
+ }
+}
diff --git a/app/components/ui/src/app/components/rootpage/rootpage.component.html b/app/components/ui/src/app/components/rootpage/rootpage.component.html
new file mode 100644
index 0000000..1b8b127
--- /dev/null
+++ b/app/components/ui/src/app/components/rootpage/rootpage.component.html
@@ -0,0 +1,25 @@
+
diff --git a/app/components/ui/src/app/components/rootpage/rootpage.component.scss b/app/components/ui/src/app/components/rootpage/rootpage.component.scss
new file mode 100644
index 0000000..859e941
--- /dev/null
+++ b/app/components/ui/src/app/components/rootpage/rootpage.component.scss
@@ -0,0 +1,25 @@
+.page-root {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+
+ .sidebar-root {
+ height: 100%;
+
+ .sidebar {
+ width: 3.5rem;
+ display: grid;
+ align-content: center;
+ outline: auto;
+ border-radius: 0;
+
+ .sidebar-item {
+ margin-bottom: 1rem;
+
+ .active-link {
+ background-color: rgba(0, 0, 0, 0.04);
+ }
+ }
+ }
+ }
+}
diff --git a/app/components/ui/src/app/components/sidebar/sidebar.component.ts b/app/components/ui/src/app/components/rootpage/rootpage.component.ts
similarity index 82%
rename from app/components/ui/src/app/components/sidebar/sidebar.component.ts
rename to app/components/ui/src/app/components/rootpage/rootpage.component.ts
index 84384c7..b031f09 100644
--- a/app/components/ui/src/app/components/sidebar/sidebar.component.ts
+++ b/app/components/ui/src/app/components/rootpage/rootpage.component.ts
@@ -5,6 +5,7 @@ import { MatListModule } from "@angular/material/list";
import { MatTooltipModule } from "@angular/material/tooltip";
import { MatSidenavModule } from "@angular/material/sidenav";
import { RouterModule, RouterOutlet } from "@angular/router";
+import { ACEFooterComponent } from "../footer/footer.component";
export type SidebarItem = {
@@ -15,10 +16,11 @@ export type SidebarItem = {
@Component({
- selector: "ace-sidebar",
- templateUrl: "sidebar.component.html",
- styleUrl: "sidebar.component.scss",
+ selector: "ace-rootpage",
+ templateUrl: "rootpage.component.html",
+ styleUrl: "rootpage.component.scss",
imports: [
+ ACEFooterComponent,
MatButtonModule,
MatIconModule,
MatListModule,
@@ -28,7 +30,7 @@ export type SidebarItem = {
RouterOutlet
],
})
-export class ACESidebarComponent {
+export class ACERootpageComponent {
sidebarItems = signal([
{
name: "Home",
diff --git a/app/components/ui/src/app/components/sidebar/sidebar.component.html b/app/components/ui/src/app/components/sidebar/sidebar.component.html
deleted file mode 100644
index ce3f6cb..0000000
--- a/app/components/ui/src/app/components/sidebar/sidebar.component.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
diff --git a/app/components/ui/src/app/components/sidebar/sidebar.component.scss b/app/components/ui/src/app/components/sidebar/sidebar.component.scss
deleted file mode 100644
index 9eab438..0000000
--- a/app/components/ui/src/app/components/sidebar/sidebar.component.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-.sidebar-root {
- height: 100%;
-}
-
-.sidebar {
- width: 3.5rem;
- display: grid;
- align-content: center;
- outline: auto;
-
- .sidebar-item {
- margin-bottom: 1rem;
-
- .active-link {
- background-color: rgba(0, 0, 0, 0.04);
- }
- }
-}
diff --git a/app/components/ui/src/app/models/app.models.ts b/app/components/ui/src/app/models/app.models.ts
new file mode 100644
index 0000000..ac57ffc
--- /dev/null
+++ b/app/components/ui/src/app/models/app.models.ts
@@ -0,0 +1,3 @@
+export interface IACEVersionData {
+ version: string
+}
diff --git a/app/components/ui/src/app/pages/settings/settings.service.ts b/app/components/ui/src/app/pages/settings/settings.service.ts
new file mode 100644
index 0000000..e69de29
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
new file mode 100644
index 0000000..e69de29
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
new file mode 100644
index 0000000..e69de29
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
new file mode 100644
index 0000000..e69de29
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
new file mode 100644
index 0000000..e69de29
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
new file mode 100644
index 0000000..e69de29
diff --git a/app/components/ui/src/app/services/app.service.ts b/app/components/ui/src/app/services/app.service.ts
new file mode 100644
index 0000000..da1a9b7
--- /dev/null
+++ b/app/components/ui/src/app/services/app.service.ts
@@ -0,0 +1,24 @@
+import { Injectable } from "@angular/core";
+import { HttpClient } from "@angular/common/http";
+import { Observable } from 'rxjs/internal/Observable';
+import { serviceURLs } from "../api.urls";
+
+
+export type HttpListResponseFailure = { status: string, message: string };
+
+
+const endpoints = {
+ getACEVersionData: `${serviceURLs.controller}/version`,
+};
+
+
+@Injectable({
+ providedIn: "root"
+})
+export class AppService {
+ constructor(private http: HttpClient) { }
+
+ getACEVersionData(): Observable {
+ return this.http.get(endpoints.getACEVersionData);
+ }
+}
diff --git a/app/components/ui/src/app/store/actions/app.actions.ts b/app/components/ui/src/app/store/actions/app.actions.ts
new file mode 100644
index 0000000..35b306d
--- /dev/null
+++ b/app/components/ui/src/app/store/actions/app.actions.ts
@@ -0,0 +1,11 @@
+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/effects/app.effects.ts b/app/components/ui/src/app/store/effects/app.effects.ts
new file mode 100644
index 0000000..8de462e
--- /dev/null
+++ b/app/components/ui/src/app/store/effects/app.effects.ts
@@ -0,0 +1,26 @@
+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 { 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 })))
+ ))
+ ))
+
+ constructor(
+ private appService: AppService
+ ) { }
+}
diff --git a/app/components/ui/src/app/store/reducers/app.reducers.ts b/app/components/ui/src/app/store/reducers/app.reducers.ts
new file mode 100644
index 0000000..27458f4
--- /dev/null
+++ b/app/components/ui/src/app/store/reducers/app.reducers.ts
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..e2b001e
--- /dev/null
+++ b/app/components/ui/src/app/store/selectors/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 selectACEVersionData = createSelector(selectAppState, (state: AppState) => state.versionData);
diff --git a/app/components/ui/src/app/store/state/app.state.ts b/app/components/ui/src/app/store/state/app.state.ts
new file mode 100644
index 0000000..1c2859c
--- /dev/null
+++ b/app/components/ui/src/app/store/state/app.state.ts
@@ -0,0 +1,17 @@
+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/store/state/loadable.state.ts b/app/components/ui/src/app/store/state/loadable.state.ts
new file mode 100644
index 0000000..920f398
--- /dev/null
+++ b/app/components/ui/src/app/store/state/loadable.state.ts
@@ -0,0 +1,35 @@
+export interface Loadable {
+ loading: boolean;
+ error: any;
+}
+
+export function createDefaultLoadable(): Loadable {
+ return {
+ loading: false,
+ error: null,
+ };
+}
+
+export function onLoadableLoad(loadable: T): T {
+ return {
+ ...(loadable as any),
+ loading: true,
+ error: null,
+ } as T;
+}
+
+export function onLoadableSuccess(loadable: T): T {
+ return {
+ ...(loadable as any),
+ loading: false,
+ error: null,
+ } as T;
+}
+
+export function onLoadableError(loadable: T, error: any): T {
+ return {
+ ...(loadable as any),
+ loading: false,
+ error: error,
+ } as T;
+}
diff --git a/app/constants/__init__.py b/app/constants/__init__.py
index ac42544..818875a 100644
--- a/app/constants/__init__.py
+++ b/app/constants/__init__.py
@@ -1,11 +1,12 @@
from .components import Components
from .container_folders import ContainerFolders
-from .defaults import Defaults
+from .defaults import Defaults, DefaultAPIResponseSchema
from .dict_keys import DictKeys
from .environment_variables import EnvironmentVariables
from .files import Files
from .folders import Folders
from .logger import CustomLogLevels, TERMINAL_COLOR_CODES
+from .model_providers import ModelProviders
from .names import Names
from .network import NetworkPorts
from .shell_commands import ShellCommands
diff --git a/app/constants/base_enum.py b/app/constants/base_enum.py
index 0d2e174..e2e06dc 100644
--- a/app/constants/base_enum.py
+++ b/app/constants/base_enum.py
@@ -7,7 +7,7 @@
# ENUMS
class BaseEnum(ABC):
"""Base Enum Class"""
- _ALLOWED_ENUM_TYPES: tuple[type, ...] = (str, int)
+ _ALLOWED_ENUM_TYPES: tuple[type, ...] = (str, int, float)
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
@@ -27,9 +27,9 @@ def get_dict(cls) -> dict[str, str]:
return base_enum_dict
@classmethod
- def get_variable_values_tuple(cls) -> tuple[str, ...]:
+ def get_tuple(cls) -> tuple[str, ...]:
return tuple(cls.get_dict().values())
@classmethod
- def get_variable_values_frozenset(cls) -> frozenset[str]:
- return frozenset(cls.get_values())
+ def get_frozenset(cls) -> frozenset[str]:
+ return frozenset(cls.get_tuple())
diff --git a/app/constants/defaults.py b/app/constants/defaults.py
index db55531..fee934a 100644
--- a/app/constants/defaults.py
+++ b/app/constants/defaults.py
@@ -1,8 +1,22 @@
# DEPENDENCIES
+## Third-Party
+from pydantic import BaseModel
## Local
from .base_enum import BaseEnum
+from .model_providers import ModelProviders
class Defaults(BaseEnum):
+ # API
+ INTERNAL_SERVER_ERROR_MESSAGE: str = "Server experienced an internal error!"
+ # Layers
+ ACE_NAME: str = "PrototypeACE"
+ # Model Provider
+ MODEL_PROVIDER: str = ModelProviders.OLLAMA
+ TEMPERATURE: float = 0.2
+ # Logger
TERMINAL_COLOR_CODE: str = "\033[0m" # Default color
SHUTDOWN_MESSAGE: str = "Shutting down logger..."
+
+class DefaultAPIResponseSchema(BaseModel):
+ message: str
diff --git a/app/constants/dict_keys.py b/app/constants/dict_keys.py
index 61c5b07..eee50a9 100644
--- a/app/constants/dict_keys.py
+++ b/app/constants/dict_keys.py
@@ -9,6 +9,7 @@ class DictKeys(BaseEnum):
FUNCTION_NAME: str = "function_name"
LEVEL: str = "level"
MESSAGE: str = "message"
+ MODEL_PROVIDER: str = "model_provider"
PROD: str = "prod"
REBUILD_DATE: str = "rebuild_date"
RESTART: str = "restart"
diff --git a/app/constants/files.py b/app/constants/files.py
index 1003a5f..6918aa2 100644
--- a/app/constants/files.py
+++ b/app/constants/files.py
@@ -1,6 +1,7 @@
# DEPENDENCIES
## Built-in
import os
+import json
## Local
from .base_enum import BaseEnum
from .components import Components
@@ -20,6 +21,8 @@ class Files(BaseEnum):
# Startup
STARTUP_HISTORY: str = f"{Folders.STORAGE}.startup_history"
VERSION: str = "version"
+ # Storage
+ CONTROLLER_SETTINGS: str = f"{Folders.CONTROLLER_STORAGE}.settings"
# INIT
@@ -91,12 +94,13 @@ def setup_user_deployment_file(dev: bool):
user_deployment_file.write(deployment_string)
user_deployment_file.close()
-_ENSURE_FILES: frozenset[str] = frozenset([
- Files.STARTUP_HISTORY
+_ENSURE_JSON_FILES: frozenset[str] = frozenset([
+ Files.STARTUP_HISTORY,
+ Files.CONTROLLER_SETTINGS
])
-def _ensure_files():
- for file in _ENSURE_FILES:
+def _ensure_json_files():
+ for file in _ENSURE_JSON_FILES:
if not os.path.isfile(file):
- with open(file, "w", encoding="utf-8"):
- pass
-_ensure_files()
\ No newline at end of file
+ with open(file, "w", encoding="utf-8") as file:
+ json.dump({}, file)
+_ensure_json_files()
diff --git a/app/constants/folders.py b/app/constants/folders.py
index 4339382..44f0b72 100644
--- a/app/constants/folders.py
+++ b/app/constants/folders.py
@@ -15,10 +15,10 @@ class Folders(BaseEnum):
# Storage
STORAGE: str = ".storage/"
_HOST_STORAGE: str = f"{os.getcwd()}/{STORAGE}"
- CONTROLLER_STORAGE: str = f"{_HOST_STORAGE}controller"
- LAYERS_STORAGE: str = f"{_HOST_STORAGE}layers"
- MODEL_PROVIDER_STORAGE: str = f"{_HOST_STORAGE}model_provider"
- OUTPUT_STORAGE: str = f"{_HOST_STORAGE}output"
+ CONTROLLER_STORAGE: str = f"{_HOST_STORAGE}controller/"
+ LAYERS_STORAGE: str = f"{_HOST_STORAGE}layers/"
+ MODEL_PROVIDER_STORAGE: str = f"{_HOST_STORAGE}model_provider/"
+ OUTPUT_STORAGE: str = f"{_HOST_STORAGE}output/"
# INIT
diff --git a/app/constants/model_providers.py b/app/constants/model_providers.py
new file mode 100644
index 0000000..1b9d9c9
--- /dev/null
+++ b/app/constants/model_providers.py
@@ -0,0 +1,13 @@
+# DEPENDENCIES
+## Local
+from .base_enum import BaseEnum
+
+
+class ModelProviders(BaseEnum):
+ CLAUDE: str = "claude"
+ DEEPSEEK: str = "deepsee"
+ GOOGLE_VERTEX_AI: str = "google_vertex_ai"
+ GROK: str = "grok"
+ GROQ: str = "groq"
+ OLLAMA: str = "ollama"
+ OPENAI: str = "openai"
diff --git a/app/requirements b/app/requirements
index e69de29..22cb80b 100644
--- a/app/requirements
+++ b/app/requirements
@@ -0,0 +1,3 @@
+fastapi==0.115.8
+pydantic==2.10.6
+uvicorn==0.34.0
\ No newline at end of file
diff --git a/app/startup.py b/app/startup.py
index aef5a20..38fa6a5 100755
--- a/app/startup.py
+++ b/app/startup.py
@@ -32,15 +32,14 @@ def update() -> bool:
logger.startup(f"Updating {Names.ACE}...")
execute_shell(ShellCommands.UPDATE)
+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] = {}
- with open(Files.STARTUP_UPDATES, "r", encoding="utf-8") as updates_file:
+ with open(Files.VERSION, "r", encoding="utf-8") as updates_file:
updates = json.load(updates_file)
with open(Files.STARTUP_HISTORY, "r", encoding="utf-8") as history_file:
- if history_file.read() == "":
- history = {}
- else:
- history = json.load(history_file)
+ history = json.loads(history_file.read())
if not history:
with open(Files.STARTUP_HISTORY, "w", encoding="utf-8") as history_file:
history[DictKeys.REBUILD_DATE] = ""
@@ -69,7 +68,7 @@ def build_container(force_build: bool = False):
updates: dict[str] = {}
history: dict[str] = {}
- with open(Files.STARTUP_UPDATES, "r", encoding="utf-8") as updates_file:
+ with open(Files.VERSION, "r", encoding="utf-8") as updates_file:
updates = json.load(updates_file)
with open(Files.STARTUP_HISTORY, "r", encoding="utf-8") as history_file:
history = json.load(history_file)
@@ -115,9 +114,10 @@ def startup():
setup_user_deployment_file(dev)
setup_network()
if not dev:
- update_build: bool = update()
- if update_build:
- force_build = True
+ update()
+ update_build: bool = check_if_latest_build()
+ if update_build:
+ force_build = True
build_container(force_build=force_build)
execute_shell(ShellCommands.CLEAR_OLD_IMAGES)