Browse Source

UI: Refactoring

pull/13371/head
Artem Dzhereleiko 1 year ago
parent
commit
b84d2fcbb0
  1. 2
      ui-ngx/src/app/modules/common/modules-map.ts
  2. 0
      ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.html
  3. 0
      ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.scss
  4. 4
      ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.ts
  5. 3
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  6. 98
      ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.html
  7. 29
      ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.ts
  8. 28
      ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.html
  9. 36
      ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts
  10. 11
      ui-ngx/src/app/modules/home/pages/ai-model/ai-model-table-config.resolve.ts
  11. 18
      ui-ngx/src/app/modules/home/pages/ai-model/ai-model-table-header.component.scss
  12. 2
      ui-ngx/src/app/modules/home/pages/ai-model/ai-model-table-header.component.ts
  13. 166
      ui-ngx/src/app/shared/components/ai-model/ai-model-dialog.component.html
  14. 54
      ui-ngx/src/app/shared/components/ai-model/ai-model-dialog.component.ts
  15. 4
      ui-ngx/src/app/shared/components/ai-model/models-list-autocomplete.component.ts
  16. 201
      ui-ngx/src/app/shared/models/ai-model.models.ts
  17. 3
      ui-ngx/src/app/shared/shared.module.ts

2
ui-ngx/src/app/modules/common/modules-map.ts

@ -337,7 +337,6 @@ import * as AggregationTypeSelectComponent from '@shared/components/time/aggrega
import * as AggregationOptionsConfigComponent from '@shared/components/time/aggregation/aggregation-options-config-panel.component'; import * as AggregationOptionsConfigComponent from '@shared/components/time/aggregation/aggregation-options-config-panel.component';
import * as IntervalOptionsConfigPanelComponent from '@shared/components/time/interval-options-config-panel.component'; import * as IntervalOptionsConfigPanelComponent from '@shared/components/time/interval-options-config-panel.component';
import * as AIModelDialogComponent from '@shared/components/ai-model/ai-model-dialog.component'; import * as AIModelDialogComponent from '@shared/components/ai-model/ai-model-dialog.component';
import * as CheckConnectivityDialogComponent from '@shared/components/ai-model/check-connectivity-dialog.component';
import * as ModelsListAutocompleteComponent from '@shared/components/ai-model/models-list-autocomplete.component'; import * as ModelsListAutocompleteComponent from '@shared/components/ai-model/models-list-autocomplete.component';
import { IModulesMap } from '@modules/common/modules-map.models'; import { IModulesMap } from '@modules/common/modules-map.models';
@ -536,7 +535,6 @@ class ModulesMap implements IModulesMap {
'@shared/components/image/multiple-gallery-image-input.component': MultipleGalleryImageInputComponent, '@shared/components/image/multiple-gallery-image-input.component': MultipleGalleryImageInputComponent,
'@shared/components/popover.service': TbPopoverService, '@shared/components/popover.service': TbPopoverService,
'@shared/components/ai-model/ai-model-dialog.component': AIModelDialogComponent, '@shared/components/ai-model/ai-model-dialog.component': AIModelDialogComponent,
'@shared/components/ai-model/check-connectivity-dialog.component': CheckConnectivityDialogComponent,
'@shared/components/ai-model/models-list-autocomplete.component': ModelsListAutocompleteComponent, '@shared/components/ai-model/models-list-autocomplete.component': ModelsListAutocompleteComponent,

0
ui-ngx/src/app/shared/components/ai-model/check-connectivity-dialog.component.html → ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.html

0
ui-ngx/src/app/shared/components/ai-model/check-connectivity-dialog.component.scss → ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.scss

4
ui-ngx/src/app/shared/components/ai-model/check-connectivity-dialog.component.ts → ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.ts

@ -20,7 +20,7 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AiModel, AiModelWithUserMsg } from '@shared/models/ai-model.models'; import { AiModel, AiModelWithUserMsg, ModelType } from '@shared/models/ai-model.models';
import { AiModelService } from '@core/http/ai-model.service'; import { AiModelService } from '@core/http/ai-model.service';
export interface AIModelDialogData { export interface AIModelDialogData {
@ -55,7 +55,7 @@ export class CheckConnectivityDialogComponent extends DialogComponent<CheckConne
] ]
}, },
chatModelConfig: { chatModelConfig: {
modelType: "CHAT", modelType: ModelType.CHAT,
provider: this.data.AIModel.configuration.provider, provider: this.data.AIModel.configuration.provider,
providerConfig: {...this.data.AIModel.configuration.providerConfig}, providerConfig: {...this.data.AIModel.configuration.providerConfig},
modelId: this.data.AIModel.configuration.modelId, modelId: this.data.AIModel.configuration.modelId,

3
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -203,6 +203,7 @@ import {
import { import {
CalculatedFieldTestArgumentsComponent CalculatedFieldTestArgumentsComponent
} from '@home/components/calculated-fields/components/test-arguments/calculated-field-test-arguments.component'; } from '@home/components/calculated-fields/components/test-arguments/calculated-field-test-arguments.component';
import { CheckConnectivityDialogComponent } from '@home/components/ai-model/check-connectivity-dialog.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -354,6 +355,7 @@ import {
CalculatedFieldDebugDialogComponent, CalculatedFieldDebugDialogComponent,
CalculatedFieldScriptTestDialogComponent, CalculatedFieldScriptTestDialogComponent,
CalculatedFieldTestArgumentsComponent, CalculatedFieldTestArgumentsComponent,
CheckConnectivityDialogComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -499,6 +501,7 @@ import {
CalculatedFieldDebugDialogComponent, CalculatedFieldDebugDialogComponent,
CalculatedFieldScriptTestDialogComponent, CalculatedFieldScriptTestDialogComponent,
CalculatedFieldTestArgumentsComponent, CalculatedFieldTestArgumentsComponent,
CheckConnectivityDialogComponent,
], ],
providers: [ providers: [
WidgetComponentService, WidgetComponentService,

98
ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.html

@ -16,72 +16,42 @@
--> -->
<section [formGroup]="timeInputForm" class="flex gap-4"> <section [formGroup]="timeInputForm" class="flex gap-4">
@if (outlined) { <mat-form-field [class]="{'number': inlineField, 'max-w-66%': !inlineField, 'flex-full': !inlineField}"
<div class="tb-form-row space-between flex-1 columns-xs"> [appearance]="inlineField ? 'outline' : appearance"
<div translate tb-hint-tooltip-icon="{{labelIconHintTooltipText}}">{{ labelText }}</div> [subscriptSizing]="inlineField ? 'dynamic' : subscriptSizing">
<div class="flex flex-row items-center justify-start gap-2"> @if (labelText && !inlineField) {
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-label>{{ labelText }}</mat-label>
<input matInput formControlName="time" type="number" min="0" step="1" }
placeholder="{{ 'widget-config.set' | translate }}"> <input type="number" min="0" step="1" matInput formControlName="time" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix <div matSuffix>
matTooltipPosition="above" <ng-content select="[matSuffix]"></ng-content>
matTooltipClass="tb-error-tooltip"
[matTooltip]="requiredText"
*ngIf="timeInputForm.get('time').hasError('required') && requiredText"
class="tb-error">
warning
</mat-icon>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="minErrorText"
*ngIf="timeInputForm.get('time').hasError('min') && minErrorText"
class="tb-error">
warning
</mat-icon>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="maxErrorText"
*ngIf="timeInputForm.get('time').hasError('max') && maxErrorText"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="timeUnit">
@for (timeUnit of timeUnits; track timeUnit) {
<mat-option [value]="timeUnit">{{ timeUnitTranslations.get(timeUnit) | translate }}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div> </div>
} @else { @if (inlineField) {
<mat-form-field class="max-w-66% flex-full" subscriptSizing="dynamic"> <mat-icon matSuffix
<mat-label *ngIf="labelText">{{ labelText }}</mat-label> matTooltipPosition="above"
<input type="number" min="0" step="1" matInput formControlName="time"> matTooltipClass="tb-error-tooltip"
<div matSuffix> [matTooltip]="hasError"
<ng-content select="[matSuffix]"></ng-content> *ngIf="hasError"
</div> class="tb-error">
warning
</mat-icon>
} @else {
<mat-hint *ngIf="subscriptSizing === 'fixed'"></mat-hint> <mat-hint *ngIf="subscriptSizing === 'fixed'"></mat-hint>
<mat-error *ngIf="timeInputForm.get('time').hasError('required') && requiredText"> <mat-error *ngIf="hasError">
{{ requiredText }} {{ hasError }}
</mat-error> </mat-error>
<mat-error *ngIf="timeInputForm.get('time').hasError('min') && minErrorText"> }
{{ minErrorText }} </mat-form-field>
</mat-error> <mat-form-field [class.h-fit.max-w-33%.flex-full]="!inlineField"
<mat-error *ngIf="timeInputForm.get('time').hasError('max') && maxErrorText"> [appearance]="inlineField ? 'outline' : appearance"
{{ maxErrorText }} [subscriptSizing]="inlineField ? 'dynamic' : subscriptSizing">
</mat-error> @if (!inlineField) {
</mat-form-field>
<mat-form-field class="h-fit max-w-33% flex-full" [subscriptSizing]="subscriptSizing">
<mat-label translate>rule-node-config.units</mat-label> <mat-label translate>rule-node-config.units</mat-label>
<mat-select formControlName="timeUnit"> }
@for (timeUnit of timeUnits; track timeUnit) { <mat-select formControlName="timeUnit">
<mat-option [value]="timeUnit">{{ timeUnitTranslations.get(timeUnit) | translate }}</mat-option> @for (timeUnit of timeUnits; track timeUnit) {
} <mat-option [value]="timeUnit">{{ timeUnitTranslations.get(timeUnit) | translate }}</mat-option>
</mat-select> }
</mat-form-field> </mat-select>
} </mat-form-field>
</section> </section>

29
ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.ts

@ -30,7 +30,7 @@ import { isDefinedAndNotNull, isNumeric } from '@core/utils';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { coerceBoolean, coerceNumber } from '@shared/decorators/coercion'; import { coerceBoolean, coerceNumber } from '@shared/decorators/coercion';
import { DAY, HOUR, MINUTE, SECOND } from '@shared/models/time/time.models'; import { DAY, HOUR, MINUTE, SECOND } from '@shared/models/time/time.models';
import { SubscriptSizing } from '@angular/material/form-field'; import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
interface TimeUnitInputModel { interface TimeUnitInputModel {
time: number; time: number;
@ -55,9 +55,6 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator,
@Input() @Input()
labelText: string; labelText: string;
@Input()
labelIconHintTooltipText: string;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
required: boolean; required: boolean;
@ -83,12 +80,11 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator,
subscriptSizing: SubscriptSizing = 'fixed'; subscriptSizing: SubscriptSizing = 'fixed';
@Input() @Input()
@coerceBoolean() appearance: MatFormFieldAppearance = 'fill';
filterTimeUnitsByMaxTime = false;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
outlined = false; inlineField: boolean;
timeUnits = Object.values(TimeUnit).filter(item => item !== TimeUnit.MILLISECONDS) as TimeUnit[]; timeUnits = Object.values(TimeUnit).filter(item => item !== TimeUnit.MILLISECONDS) as TimeUnit[];
@ -115,12 +111,13 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator,
} }
ngOnInit() { ngOnInit() {
if (this.filterTimeUnitsByMaxTime && this.maxTime) { if (this.maxTime) {
if (this.maxTime < 60) { const maxTimeMs = this.maxTime * SECOND;
if (maxTimeMs < MINUTE) {
this.timeUnits = this.timeUnits.filter(item => item !== TimeUnit.MINUTES && item !== TimeUnit.HOURS && item !== TimeUnit.DAYS); this.timeUnits = this.timeUnits.filter(item => item !== TimeUnit.MINUTES && item !== TimeUnit.HOURS && item !== TimeUnit.DAYS);
} else if (this.maxTime < 3600) { } else if (maxTimeMs < HOUR) {
this.timeUnits = this.timeUnits.filter(item => item !== TimeUnit.HOURS && item !== TimeUnit.DAYS); this.timeUnits = this.timeUnits.filter(item => item !== TimeUnit.HOURS && item !== TimeUnit.DAYS);
} else if (this.maxTime < 86400) { } else if (maxTimeMs < DAY) {
this.timeUnits = this.timeUnits.filter(item => item !== TimeUnit.DAYS); this.timeUnits = this.timeUnits.filter(item => item !== TimeUnit.DAYS);
} }
} }
@ -157,6 +154,16 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator,
}); });
} }
get hasError(): string {
if (this.timeInputForm.get('time').hasError('required') && this.requiredText) {
return this.requiredText;
} else if (this.timeInputForm.get('time').hasError('min') && this.minErrorText) {
return this.minErrorText;
} else if (this.timeInputForm.get('time').hasError('max') && this.maxErrorText) {
return this.maxErrorText;
}
}
registerOnChange(fn: any) { registerOnChange(fn: any) {
this.propagateChange = fn; this.propagateChange = fn;
} }

28
ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.html

@ -96,19 +96,21 @@
<mat-panel-title translate>rule-node-config.ai.advanced-settings</mat-panel-title> <mat-panel-title translate>rule-node-config.ai.advanced-settings</mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div class="tb-form-panel no-border" style="margin-top: 0"> <div class="tb-form-panel no-border" style="margin-top: 0">
<tb-time-unit-input <div class="tb-form-row space-between flex-1 columns-xs">
required <div translate tb-hint-tooltip-icon="{{'rule-node-config.ai.timeout-hint' | translate}}">{{ 'rule-node-config.ai.timeout' | translate }}</div>
outlined <div class="flex flex-row items-center justify-start gap-2">
filterTimeUnitsByMaxTime <tb-time-unit-input
labelText="{{ 'rule-node-config.ai.timeout' | translate }}" required
labelIconHintTooltipText="{{ 'rule-node-config.ai.timeout-hint' | translate }}" inlineField
requiredText="{{ 'rule-node-config.ai.timeout-required' | translate }}" requiredText="{{ 'rule-node-config.ai.timeout-required' | translate }}"
minErrorText="{{ 'rule-node-config.ai.timeout-validation' | translate }}" minErrorText="{{ 'rule-node-config.ai.timeout-validation' | translate }}"
maxErrorText="{{ 'rule-node-config.ai.timeout-validation' | translate }}" maxErrorText="{{ 'rule-node-config.ai.timeout-validation' | translate }}"
[maxTime]="600" [maxTime]="600"
[minTime]="1" [minTime]="1"
formControlName="timeoutSeconds"> formControlName="timeoutSeconds">
</tb-time-unit-input> </tb-time-unit-input>
</div>
</div>
</div> </div>
</mat-expansion-panel> </mat-expansion-panel>
</div> </div>

36
ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts

@ -20,15 +20,9 @@ import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/m
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { AIModelDialogComponent, AIModelDialogData } from '@shared/components/ai-model/ai-model-dialog.component'; import { AIModelDialogComponent, AIModelDialogData } from '@shared/components/ai-model/ai-model-dialog.component';
import { AiModel, AiProvider } from '@shared/models/ai-model.models'; import { AiModel, AiRuleNodeResponseFormatTypeOnlyText, ResponseFormat } from '@shared/models/ai-model.models';
import { deepTrim } from '@core/utils'; import { deepTrim } from '@core/utils';
enum ResponseFormat {
TEXT = 'TEXT',
JSON = 'JSON',
JSON_SCHEMA = 'JSON_SCHEMA'
}
@Component({ @Component({
selector: 'tb-external-node-ai-config', selector: 'tb-external-node-ai-config',
templateUrl: './ai-config.component.html', templateUrl: './ai-config.component.html',
@ -53,14 +47,14 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
protected onConfigurationSet(configuration: RuleNodeConfiguration) { protected onConfigurationSet(configuration: RuleNodeConfiguration) {
this.aiConfigForm = this.fb.group({ this.aiConfigForm = this.fb.group({
modelSettingsId: [configuration ? configuration.modelSettingsId : null, [Validators.required]], modelSettingsId: [configuration?.modelSettingsId ?? null, [Validators.required]],
systemPrompt: [configuration ? configuration.systemPrompt : '', [Validators.maxLength(10000), Validators.pattern(/.*\S.*/)]], systemPrompt: [configuration?.systemPrompt ?? '', [Validators.maxLength(10000), Validators.pattern(/.*\S.*/)]],
userPrompt: [configuration ? configuration.userPrompt : '', [Validators.required, Validators.maxLength(10000), Validators.pattern(/.*\S.*/)]], userPrompt: [configuration?.userPrompt ?? '', [Validators.required, Validators.maxLength(10000), Validators.pattern(/.*\S.*/)]],
responseFormat: this.fb.group({ responseFormat: this.fb.group({
type: [configuration ? configuration.responseFormat.type : ResponseFormat.JSON, []], type: [configuration?.responseFormat?.type ?? ResponseFormat.JSON, []],
schema: [configuration ? configuration.responseFormat.schema : null, []], schema: [configuration?.responseFormat?.schema ?? null, [Validators.required]],
}), }),
timeoutSeconds: [configuration ? configuration.timeoutSeconds : 60, []] timeoutSeconds: [configuration?.timeoutSeconds ?? 60, []]
}); });
} }
@ -69,15 +63,11 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
} }
protected updateValidators(emitEvent: boolean) { protected updateValidators(emitEvent: boolean) {
const responseFormatType = this.aiConfigForm.get('responseFormat.type').value; if (this.aiConfigForm.get('responseFormat.type').value === ResponseFormat.JSON_SCHEMA) {
if (responseFormatType === ResponseFormat.JSON_SCHEMA) { this.aiConfigForm.get('responseFormat.schema').enable({emitEvent: false});
this.aiConfigForm.get('responseFormat.schema').setValidators([Validators.required]);
this.aiConfigForm.get('responseFormat.schema').enable();
} else { } else {
this.aiConfigForm.get('responseFormat.schema').setValidators([]); this.aiConfigForm.get('responseFormat.schema').disable({emitEvent: false});
this.aiConfigForm.get('responseFormat.schema').disable();
} }
this.aiConfigForm.get('responseFormat.schema').updateValueAndValidity({emitEvent});
} }
protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration {
@ -86,9 +76,7 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
onEntityChange($event: AiModel) { onEntityChange($event: AiModel) {
if ($event) { if ($event) {
if ($event.configuration.provider === AiProvider.AMAZON_BEDROCK || if (AiRuleNodeResponseFormatTypeOnlyText.includes($event.configuration.provider)) {
$event.configuration.provider === AiProvider.ANTHROPIC ||
$event.configuration.provider === AiProvider.GITHUB_MODELS) {
this.aiConfigForm.get('responseFormat.type').patchValue(ResponseFormat.TEXT, {emitEvent: false}); this.aiConfigForm.get('responseFormat.type').patchValue(ResponseFormat.TEXT, {emitEvent: false});
this.aiConfigForm.get('responseFormat.type').disable({emitEvent: false}); this.aiConfigForm.get('responseFormat.type').disable({emitEvent: false});
} }
@ -97,7 +85,7 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
} }
} }
createModelAi(formControl: string,) { createModelAi(formControl: string) {
this.dialog.open<AIModelDialogComponent, AIModelDialogData, AiModel>(AIModelDialogComponent, { this.dialog.open<AIModelDialogComponent, AIModelDialogData, AiModel>(AIModelDialogComponent, {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],

11
ui-ngx/src/app/modules/home/pages/ai-model/ai-model-table-config.resolve.ts

@ -79,6 +79,11 @@ export class AiModelsTableConfigResolver {
this.config.entitiesFetchFunction = pageLink => this.aiModelService.getAiModels(pageLink); this.config.entitiesFetchFunction = pageLink => this.aiModelService.getAiModels(pageLink);
this.config.cellActionDescriptors = this.configureCellActions(); this.config.cellActionDescriptors = this.configureCellActions();
this.config.handleRowClick = ($event, model) => {
this.editModel(model);
return true;
};
} }
resolve(_route: ActivatedRouteSnapshot): EntityTableConfig<AiModel> { resolve(_route: ActivatedRouteSnapshot): EntityTableConfig<AiModel> {
@ -97,11 +102,7 @@ export class AiModelsTableConfigResolver {
} }
private editModel(AIModel: AiModel): void { private editModel(AIModel: AiModel): void {
this.addModel(AIModel, false).subscribe((res) => { this.addModel(AIModel, false).subscribe();
if (res) {
this.config.updateData();
}
});
} }
private addModel(AIModel: AiModel, isAdd = false): Observable<AiModel> { private addModel(AIModel: AiModel, isAdd = false): Observable<AiModel> {

18
ui-ngx/src/app/modules/home/pages/ai-model/ai-model-table-header.component.scss

@ -1,18 +0,0 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host{
width: 100%;
}

2
ui-ngx/src/app/modules/home/pages/ai-model/ai-model-table-header.component.ts

@ -23,7 +23,7 @@ import { AiModel } from '@shared/models/ai-model.models';
@Component({ @Component({
selector: 'tb-ai-model-table-header', selector: 'tb-ai-model-table-header',
templateUrl: './ai-model-table-header.component.html', templateUrl: './ai-model-table-header.component.html',
styleUrls: ['./ai-model-table-header.component.scss'] styleUrls: []
}) })
export class AiModelTableHeaderComponent extends EntityTableHeaderComponent<AiModel> { export class AiModelTableHeaderComponent extends EntityTableHeaderComponent<AiModel> {

166
ui-ngx/src/app/shared/components/ai-model/ai-model-dialog.component.html

@ -56,7 +56,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div formGroupName="providerConfig" class="tb-form-panel no-border no-padding"> <div formGroupName="providerConfig" class="tb-form-panel no-border no-padding">
@if (provider === aiProvider.GITHUB_MODELS) { @if (AiModelMap.get(provider).providerFieldsList.includes('personalAccessToken')) {
<mat-form-field class="mat-block flex-1" appearance="outline"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>ai-models.personal-access-token</mat-label> <mat-label translate>ai-models.personal-access-token</mat-label>
<input type="password" required matInput formControlName="personalAccessToken"> <input type="password" required matInput formControlName="personalAccessToken">
@ -65,24 +65,29 @@
{{ 'ai-models.personal-access-token-required' | translate }} {{ 'ai-models.personal-access-token-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
} @else if (provider === aiProvider.GOOGLE_VERTEX_AI_GEMINI) { }
<mat-form-field class="mat-block flex-1" appearance="outline"> @if (AiModelMap.get(provider).providerFieldsList.includes('projectId')) {
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>ai-models.project-id</mat-label> <mat-label translate>ai-models.project-id</mat-label>
<input matInput required formControlName="projectId"> <input matInput required formControlName="projectId">
<mat-error *ngIf="aiModelForms.get('configuration').get('providerConfig').get('projectId').hasError('required')"> <mat-error *ngIf="aiModelForms.get('configuration').get('providerConfig').get('projectId').hasError('required')">
{{ 'ai-models.project-id-required' | translate }} {{ 'ai-models.project-id-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="outline"> }
@if (AiModelMap.get(provider).providerFieldsList.includes('location')) {
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>ai-models.location</mat-label> <mat-label translate>ai-models.location</mat-label>
<input matInput required formControlName="location"> <input matInput required formControlName="location">
<mat-error *ngIf="aiModelForms.get('configuration').get('providerConfig').get('location').hasError('required')"> <mat-error *ngIf="aiModelForms.get('configuration').get('providerConfig').get('location').hasError('required')">
{{ 'ai-models.location-required' | translate }} {{ 'ai-models.location-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
}
@if (AiModelMap.get(provider).providerFieldsList.includes('serviceAccountKey')) {
<tb-file-input formControlName="serviceAccountKey" <tb-file-input formControlName="serviceAccountKey"
[existingFileName]="aiModelForms.get('configuration').get('providerConfig').get('serviceAccountKeyFileName').value" [existingFileName]="aiModelForms.get('configuration').get('providerConfig').get('fileName').value"
(fileNameChanged)="aiModelForms.get('configuration').get('providerConfig').get('serviceAccountKeyFileName').setValue($event)" (fileNameChanged)="aiModelForms.get('configuration').get('providerConfig').get('fileName').setValue($event)"
required required
requiredAsError requiredAsError
accept=".json,application/json" accept=".json,application/json"
@ -91,20 +96,23 @@
noFileText="ai-models.no-file" noFileText="ai-models.no-file"
dropLabel="{{'ai-models.drop-file' | translate}}"> dropLabel="{{'ai-models.drop-file' | translate}}">
</tb-file-input> </tb-file-input>
} @else { }
@if (provider === aiProvider.AZURE_OPENAI) { @if (AiModelMap.get(provider).providerFieldsList.includes('endpoint')) {
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>ai-models.endpoint</mat-label> <mat-label translate>ai-models.endpoint</mat-label>
<input required matInput formControlName="endpoint"> <input required matInput formControlName="endpoint">
<mat-error *ngIf="aiModelForms.get('configuration').get('providerConfig').get('endpoint').hasError('required')"> <mat-error *ngIf="aiModelForms.get('configuration').get('providerConfig').get('endpoint').hasError('required')">
{{ 'ai-models.endpoint-required' | translate }} {{ 'ai-models.endpoint-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic"> }
<mat-label translate>ai-models.service-version</mat-label> @if (AiModelMap.get(provider).providerFieldsList.includes('serviceVersion')) {
<input matInput formControlName="serviceVersion"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
</mat-form-field> <mat-label translate>ai-models.service-version</mat-label>
} <input matInput formControlName="serviceVersion">
</mat-form-field>
}
@if (AiModelMap.get(provider).providerFieldsList.includes('apiKey')) {
<mat-form-field class="mat-block flex-1" appearance="outline"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>ai-models.api-key</mat-label> <mat-label translate>ai-models.api-key</mat-label>
<input type="password" required matInput formControlName="apiKey"> <input type="password" required matInput formControlName="apiKey">
@ -128,43 +136,46 @@
[errorText]="(provider === aiProvider.AZURE_OPENAI ? 'ai-models.deployment-name-required': 'ai-models.model-id-required') | translate"> [errorText]="(provider === aiProvider.AZURE_OPENAI ? 'ai-models.deployment-name-required': 'ai-models.model-id-required') | translate">
</tb-models-list-autocomplete> </tb-models-list-autocomplete>
</section> </section>
<div class="tb-form-row space-between"> @if (AiModelMap.get(provider).modelFieldsList.includes('temperature')) {
<div tb-hint-tooltip-icon="{{ 'ai-models.temperature-hint' | translate }}"> <div class="tb-form-row space-between">
{{ 'ai-models.temperature' | translate }} <div tb-hint-tooltip-icon="{{ 'ai-models.temperature-hint' | translate }}">
</div> {{ 'ai-models.temperature' | translate }}
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> </div>
<input matInput formControlName="temperature" <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
type="number" min="0" step="0.1" placeholder="{{ 'ai-models.set' | translate }}"> <input matInput formControlName="temperature"
<mat-icon matSuffix type="number" min="0" step="0.1" placeholder="{{ 'ai-models.set' | translate }}">
matTooltipPosition="above" <mat-icon matSuffix
matTooltipClass="tb-error-tooltip" matTooltipPosition="above"
[matTooltip]="'ai-models.temperature-min' | translate" matTooltipClass="tb-error-tooltip"
*ngIf="aiModelForms.get('configuration').get('temperature').hasError('min')" [matTooltip]="'ai-models.temperature-min' | translate"
class="tb-error"> *ngIf="aiModelForms.get('configuration').get('temperature').hasError('min')"
warning class="tb-error">
</mat-icon> warning
</mat-form-field> </mat-icon>
</div> </mat-form-field>
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.top-p-hint' | translate }}">
{{ 'ai-models.top-p' | translate }}
</div> </div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> }
<input matInput formControlName="topP" @if (AiModelMap.get(provider).modelFieldsList.includes('topP')) {
type="number" min="0" step="0.1" max="1" placeholder="{{ 'ai-models.set' | translate }}"> <div class="tb-form-row space-between">
<mat-icon matSuffix <div tb-hint-tooltip-icon="{{ 'ai-models.top-p-hint' | translate }}">
matTooltipPosition="above" {{ 'ai-models.top-p' | translate }}
matTooltipClass="tb-error-tooltip" </div>
[matTooltip]="'ai-models.top-p-min-max' | translate" <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
*ngIf="aiModelForms.get('configuration').get('topP').hasError('min') || <input matInput formControlName="topP"
type="number" min="0" step="0.1" max="1" placeholder="{{ 'ai-models.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'ai-models.top-p-min-max' | translate"
*ngIf="aiModelForms.get('configuration').get('topP').hasError('min') ||
aiModelForms.get('configuration').get('topP').hasError('max')" aiModelForms.get('configuration').get('topP').hasError('max')"
class="tb-error"> class="tb-error">
warning warning
</mat-icon> </mat-icon>
</mat-form-field> </mat-form-field>
</div> </div>
}
@if (provider === aiProvider.GOOGLE_AI_GEMINI || provider === aiProvider.GOOGLE_VERTEX_AI_GEMINI || provider === aiProvider.ANTHROPIC) { @if (AiModelMap.get(provider).modelFieldsList.includes('topK')) {
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.top-k-hint' | translate }}"> <div tb-hint-tooltip-icon="{{ 'ai-models.top-k-hint' | translate }}">
{{ 'ai-models.top-k' | translate }} {{ 'ai-models.top-k' | translate }}
@ -183,45 +194,48 @@
</mat-form-field> </mat-form-field>
</div> </div>
} }
@if (AiModelMap.get(provider).modelFieldsList.includes('presencePenalty')) {
@if (provider !== aiProvider.ANTHROPIC && provider !== aiProvider.AMAZON_BEDROCK) {
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.presence-penalty-hint' | translate }}"> <div tb-hint-tooltip-icon="{{ 'ai-models.presence-penalty-hint' | translate }}">
{{ 'ai-models.presence-penalty' | translate }} {{ 'ai-models.presence-penalty' | translate }}
</div> </div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="frequencyPenalty" <input matInput formControlName="presencePenalty"
type="number" step="1" placeholder="{{ 'ai-models.set' | translate }}"> type="number" step="1" placeholder="{{ 'ai-models.set' | translate }}">
</mat-form-field> </mat-form-field>
</div> </div>
}
@if (AiModelMap.get(provider).modelFieldsList.includes('frequencyPenalty')) {
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.frequency-penalty-hint' | translate }}"> <div tb-hint-tooltip-icon="{{ 'ai-models.frequency-penalty-hint' | translate }}">
{{ 'ai-models.frequency-penalty' | translate }} {{ 'ai-models.frequency-penalty' | translate }}
</div> </div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="presencePenalty" <input matInput formControlName="frequencyPenalty"
type="number" step="1" placeholder="{{ 'ai-models.set' | translate }}"> type="number" step="1" placeholder="{{ 'ai-models.set' | translate }}">
</mat-form-field> </mat-form-field>
</div> </div>
} }
@if (AiModelMap.get(provider).modelFieldsList.includes('maxOutputTokens')) {
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.max-output-token-hint' | translate }}"> <div tb-hint-tooltip-icon="{{ 'ai-models.max-output-token-hint' | translate }}">
{{ 'ai-models.max-output-token' | translate }} {{ 'ai-models.max-output-token' | translate }}
</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="maxOutputTokens"
type="number" min="1" step="1" placeholder="{{ 'ai-models.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'ai-models.max-output-token-min' | translate"
*ngIf="aiModelForms.get('configuration').get('maxOutputTokens').hasError('min')"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div> </div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> }
<input matInput formControlName="maxOutputTokens"
type="number" min="1" step="1" placeholder="{{ 'ai-models.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'ai-models.max-output-token-min' | translate"
*ngIf="aiModelForms.get('configuration').get('maxOutputTokens').hasError('min')"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</section> </section>
</section> </section>
</div> </div>

54
ui-ngx/src/app/shared/components/ai-model/ai-model-dialog.component.ts

@ -25,9 +25,16 @@ import { StepperOrientation } from '@angular/cdk/stepper';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AiModel, AiProvider, AiProviderTranslations, AiProviderWithApiKey } from '@shared/models/ai-model.models'; import {
AiModel,
AiModelMap,
AiProvider,
AiProviderTranslations,
ModelType,
ProviderFieldsAllList
} from '@shared/models/ai-model.models';
import { AiModelService } from '@core/http/ai-model.service'; import { AiModelService } from '@core/http/ai-model.service';
import { CheckConnectivityDialogComponent } from '@shared/components/ai-model/check-connectivity-dialog.component'; import { CheckConnectivityDialogComponent } from '@home/components/ai-model/check-connectivity-dialog.component';
export interface AIModelDialogData { export interface AIModelDialogData {
AIModel?: AiModel; AIModel?: AiModel;
@ -53,7 +60,7 @@ export class AIModelDialogComponent extends DialogComponent<AIModelDialogCompone
providerMap: AiProvider[] = Object.keys(AiProvider) as AiProvider[]; providerMap: AiProvider[] = Object.keys(AiProvider) as AiProvider[];
providerTranslationMap = AiProviderTranslations; providerTranslationMap = AiProviderTranslations;
aiProviderWithApiKey: AiProvider[] = AiProviderWithApiKey; AiModelMap = AiModelMap;
provider: AiProvider = AiProvider.OPENAI; provider: AiProvider = AiProvider.OPENAI;
@ -78,7 +85,7 @@ export class AIModelDialogComponent extends DialogComponent<AIModelDialogCompone
this.aiModelForms = this.fb.group({ this.aiModelForms = this.fb.group({
name: [this.data.AIModel ? this.data.AIModel.name : '', [Validators.required, Validators.maxLength(255)]], name: [this.data.AIModel ? this.data.AIModel.name : '', [Validators.required, Validators.maxLength(255)]],
modelType: ['CHAT'], modelType: [ModelType.CHAT],
configuration: this.fb.group({ configuration: this.fb.group({
provider: [this.provider, [Validators.required]], provider: [this.provider, [Validators.required]],
providerConfig: this.fb.group({ providerConfig: this.fb.group({
@ -89,11 +96,11 @@ export class AIModelDialogComponent extends DialogComponent<AIModelDialogCompone
projectId: [this.data.AIModel ? this.data.AIModel.configuration.providerConfig?.projectId : '', [Validators.required]], projectId: [this.data.AIModel ? this.data.AIModel.configuration.providerConfig?.projectId : '', [Validators.required]],
location: [this.data.AIModel ? this.data.AIModel.configuration.providerConfig?.location : '', [Validators.required]], location: [this.data.AIModel ? this.data.AIModel.configuration.providerConfig?.location : '', [Validators.required]],
serviceAccountKey: [this.data.AIModel ? this.data.AIModel.configuration.providerConfig?.serviceAccountKey : '', [Validators.required]], serviceAccountKey: [this.data.AIModel ? this.data.AIModel.configuration.providerConfig?.serviceAccountKey : '', [Validators.required]],
serviceAccountKeyFileName: [this.data.AIModel ? this.data.AIModel.configuration.providerConfig?.serviceAccountKeyFileName : '', [Validators.required]], fileName: [this.data.AIModel ? this.data.AIModel.configuration.providerConfig?.fileName : '', [Validators.required]],
}), }),
modelId: [this.data.AIModel ? this.data.AIModel.configuration?.modelId : '', [Validators.required]], modelId: [this.data.AIModel ? this.data.AIModel.configuration?.modelId : '', [Validators.required]],
temperature: [this.data.AIModel ? this.data.AIModel.configuration?.temperature : null, [Validators.min(0)]], temperature: [this.data.AIModel ? this.data.AIModel.configuration?.temperature : null, [Validators.min(0)]],
topP: this.data.AIModel ? this.data.AIModel.configuration?.topP : [null, [Validators.min(0.1), Validators.max(1)]], topP: [this.data.AIModel ? this.data.AIModel.configuration?.topP : null, [Validators.min(0.1), Validators.max(1)]],
topK: [this.data.AIModel ? this.data.AIModel.configuration?.topK : null, [Validators.min(0)]], topK: [this.data.AIModel ? this.data.AIModel.configuration?.topK : null, [Validators.min(0)]],
frequencyPenalty: [this.data.AIModel ? this.data.AIModel.configuration?.frequencyPenalty : null], frequencyPenalty: [this.data.AIModel ? this.data.AIModel.configuration?.frequencyPenalty : null],
presencePenalty: [this.data.AIModel ? this.data.AIModel.configuration?.presencePenalty : null], presencePenalty: [this.data.AIModel ? this.data.AIModel.configuration?.presencePenalty : null],
@ -105,7 +112,7 @@ export class AIModelDialogComponent extends DialogComponent<AIModelDialogCompone
takeUntilDestroyed() takeUntilDestroyed()
).subscribe((provider: AiProvider) => { ).subscribe((provider: AiProvider) => {
this.provider = provider; this.provider = provider;
// this.aiModelForms.get('configuration').reset({}); this.aiModelForms.get('configuration.modelId').reset({});
this.aiModelForms.get('configuration.providerConfig').reset({}); this.aiModelForms.get('configuration.providerConfig').reset({});
this.updateValidation(provider); this.updateValidation(provider);
}) })
@ -114,35 +121,10 @@ export class AIModelDialogComponent extends DialogComponent<AIModelDialogCompone
} }
private updateValidation(provider: AiProvider) { private updateValidation(provider: AiProvider) {
const providerConfig = this.aiModelForms.get('configuration.providerConfig'); ProviderFieldsAllList.forEach(key =>
if (this.aiProviderWithApiKey.includes(provider)) { this.aiModelForms.get('configuration.providerConfig')
providerConfig.get('apiKey').enable(); .get(key)[AiModelMap.get(provider).providerFieldsList.includes(key) ? 'enable' : 'disable']()
} else { )
providerConfig.get('apiKey').disable();
}
if (provider === AiProvider.GITHUB_MODELS) {
providerConfig.get('personalAccessToken').enable();
} else {
providerConfig.get('personalAccessToken').disable();
}
if (provider === AiProvider.GOOGLE_VERTEX_AI_GEMINI) {
providerConfig.get('projectId').enable();
providerConfig.get('location').enable();
providerConfig.get('serviceAccountKey').enable();
providerConfig.get('serviceAccountKeyFileName').enable();
} else {
providerConfig.get('projectId').disable();
providerConfig.get('location').disable();
providerConfig.get('serviceAccountKey').disable();
providerConfig.get('serviceAccountKeyFileName').disable();
}
if (provider === AiProvider.AZURE_OPENAI) {
providerConfig.get('endpoint').enable();
providerConfig.get('serviceVersion').enable();
} else {
providerConfig.get('endpoint').disable();
providerConfig.get('serviceVersion').disable();
}
} }
cancel(): void { cancel(): void {

4
ui-ngx/src/app/shared/components/ai-model/models-list-autocomplete.component.ts

@ -99,7 +99,7 @@ export class ModelsListAutocompleteComponent implements ControlValueAccessor, On
tap(value => this.updateView(value)), tap(value => this.updateView(value)),
map(value => { map(value => {
const search = value ? value.toLowerCase() : ''; const search = value ? value.toLowerCase() : '';
const options = this.provider ? AiModelMap.get(this.provider) || [] : []; const options = this.provider ? AiModelMap.get(this.provider).modelList || [] : [];
return search ? options.filter(option => option.toLowerCase().includes(search)) : options; return search ? options.filter(option => option.toLowerCase().includes(search)) : options;
}) })
); );
@ -150,7 +150,7 @@ export class ModelsListAutocompleteComponent implements ControlValueAccessor, On
} }
clear() { clear() {
this.selectionFormControl.patchValue(null, {emitEvent: false}); this.selectionFormControl.patchValue(null, {emitEvent: true});
this.propagateChange(null); this.propagateChange(null);
this.modelValue = null; this.modelValue = null;
setTimeout(() => { setTimeout(() => {

201
ui-ngx/src/app/shared/models/ai-model.models.ts

@ -19,7 +19,7 @@ import { HasTenantId } from '@shared/models/entity.models';
import { AiModelId } from '@shared/models/id/ai-model-id'; import { AiModelId } from '@shared/models/id/ai-model-id';
export interface AiModel extends Omit<BaseData<AiModelId>, 'label'>, HasTenantId, ExportableEntity<AiModelId> { export interface AiModel extends Omit<BaseData<AiModelId>, 'label'>, HasTenantId, ExportableEntity<AiModelId> {
modelType: string; modelType: ModelType;
configuration: { configuration: {
provider: AiProvider provider: AiProvider
providerConfig: { providerConfig: {
@ -30,10 +30,10 @@ export interface AiModel extends Omit<BaseData<AiModelId>, 'label'>, HasTenantId
projectId?: string; projectId?: string;
location?: string; location?: string;
serviceAccountKey?: string; serviceAccountKey?: string;
serviceAccountKeyFileName?: string fileName?: string
}; };
modelId: string; modelId: string;
temperature?: number | null; temperature?: number;
topP?: number; topP?: number;
topK?: number; topK?: number;
frequencyPenalty?: number; frequencyPenalty?: number;
@ -42,6 +42,10 @@ export interface AiModel extends Omit<BaseData<AiModelId>, 'label'>, HasTenantId
} }
} }
export enum ModelType {
CHAT = 'CHAT'
}
export enum AiProvider { export enum AiProvider {
OPENAI = 'OPENAI', OPENAI = 'OPENAI',
AZURE_OPENAI = 'AZURE_OPENAI', AZURE_OPENAI = 'AZURE_OPENAI',
@ -53,15 +57,6 @@ export enum AiProvider {
GITHUB_MODELS = 'GITHUB_MODELS' GITHUB_MODELS = 'GITHUB_MODELS'
} }
export const AiProviderWithApiKey: AiProvider[] = [
AiProvider.OPENAI,
AiProvider.AZURE_OPENAI,
AiProvider.GOOGLE_AI_GEMINI,
AiProvider.MISTRAL_AI,
AiProvider.ANTHROPIC,
AiProvider.AMAZON_BEDROCK
]
export const AiProviderTranslations = new Map<AiProvider, string>( export const AiProviderTranslations = new Map<AiProvider, string>(
[ [
[AiProvider.OPENAI , 'ai-models.ai-providers.openai'], [AiProvider.OPENAI , 'ai-models.ai-providers.openai'],
@ -75,55 +70,130 @@ export const AiProviderTranslations = new Map<AiProvider, string>(
] ]
); );
export const AiModelMap = new Map<AiProvider, string[]>( export const ProviderFieldsAllList = [
'apiKey',
'personalAccessToken',
'projectId',
'location',
'serviceAccountKey',
'fileName',
'endpoint',
'serviceVersion'
];
export const ModelFieldsAllList = ['temperature', 'topP', 'topK', 'frequencyPenalty', 'presencePenalty', 'maxOutputTokens'];
export const AiModelMap = new Map<AiProvider, { modelList: string[], providerFieldsList: string[], modelFieldsList: string[] }>([
[ [
[AiProvider.OPENAI , [ AiProvider.OPENAI,
'o4-mini', {
'o3-pro', modelList: [
'o3', 'o4-mini',
'o3-mini', 'o3-pro',
'o1', 'o3',
'gpt-4.1', 'o3-mini',
'gpt-4.1-mini', 'o1',
'gpt-4.1-nano', 'gpt-4.1',
'gpt-4o', 'gpt-4.1-mini',
'gpt-4o-mini' 'gpt-4.1-nano',
]], 'gpt-4o',
[AiProvider.AZURE_OPENAI , []], 'gpt-4o-mini',
[AiProvider.GOOGLE_AI_GEMINI , [ ],
'gemini-2.5-pro', providerFieldsList: ['apiKey'],
'gemini-2.5-flash', modelFieldsList: ['temperature', 'topP', 'frequencyPenalty', 'presencePenalty', 'maxOutputTokens'],
'gemini-2.0-flash', },
'gemini-2.0-flash-lite', ],
]], [
[AiProvider.GOOGLE_VERTEX_AI_GEMINI , [ AiProvider.AZURE_OPENAI,
'gemini-2.5-pro', {
'gemini-2.5-flash', modelList: [],
'gemini-2.0-flash', providerFieldsList: ['apiKey', 'endpoint', 'serviceVersion'],
'gemini-2.0-flash-lite', modelFieldsList: ['temperature', 'topP', 'frequencyPenalty', 'presencePenalty', 'maxOutputTokens'],
]], },
[AiProvider.MISTRAL_AI , [ ],
'magistral-medium-latest', [
'magistral-small-latest', AiProvider.GOOGLE_AI_GEMINI,
'mistral-large-latest', {
'mistral-medium-latest', modelList: [
'mistral-small-latest', 'gemini-2.5-pro',
'pixtral-large-latest', 'gemini-2.5-flash',
'ministral-8b-latest', 'gemini-2.0-flash',
'ministral-3b-latest', 'gemini-2.0-flash-lite',
'open-mistral-nemo' ],
]], providerFieldsList: ['apiKey'],
[AiProvider.ANTHROPIC , [ modelFieldsList: ['temperature', 'topP', 'topK', 'frequencyPenalty', 'presencePenalty', 'maxOutputTokens'],
'claude-opus-4-0', },
'claude-sonnet-4-0', ],
'claude-3-7-sonnet-latest', [
'claude-3-5-sonnet-latest', AiProvider.GOOGLE_VERTEX_AI_GEMINI,
'claude-3-5-haiku-latest' {
]], modelList: [
[AiProvider.AMAZON_BEDROCK , []], 'gemini-2.5-pro',
[AiProvider.GITHUB_MODELS , []] 'gemini-2.5-flash',
] 'gemini-2.0-flash',
); 'gemini-2.0-flash-lite',
],
providerFieldsList: ['projectId', 'location', 'serviceAccountKey', 'fileName'],
modelFieldsList: ['temperature', 'topP', 'topK', 'frequencyPenalty', 'presencePenalty', 'maxOutputTokens'],
},
],
[
AiProvider.MISTRAL_AI,
{
modelList: [
'magistral-medium-latest',
'magistral-small-latest',
'mistral-large-latest',
'mistral-medium-latest',
'mistral-small-latest',
'pixtral-large-latest',
'ministral-8b-latest',
'ministral-3b-latest',
'open-mistral-nemo',
],
providerFieldsList: ['apiKey'],
modelFieldsList: ['temperature', 'topP', 'frequencyPenalty', 'presencePenalty', 'maxOutputTokens'],
},
],
[
AiProvider.ANTHROPIC,
{
modelList: [
'claude-opus-4-0',
'claude-sonnet-4-0',
'claude-3-7-sonnet-latest',
'claude-3-5-sonnet-latest',
'claude-3-5-haiku-latest',
],
providerFieldsList: ['apiKey'],
modelFieldsList: ['temperature', 'topP', 'topK', 'maxOutputTokens'],
},
],
[
AiProvider.AMAZON_BEDROCK,
{
modelList: [],
providerFieldsList: ['apiKey'],
modelFieldsList: ['temperature', 'topP', 'maxOutputTokens'],
},
],
[
AiProvider.GITHUB_MODELS,
{
modelList: [],
providerFieldsList: ['personalAccessToken'],
modelFieldsList: ['temperature', 'topP', 'frequencyPenalty', 'presencePenalty', 'maxOutputTokens'],
},
],
]);
export const AiRuleNodeResponseFormatTypeOnlyText: AiProvider[] = [AiProvider.AMAZON_BEDROCK, AiProvider.ANTHROPIC, AiProvider.GITHUB_MODELS];
export enum ResponseFormat {
TEXT = 'TEXT',
JSON = 'JSON',
JSON_SCHEMA = 'JSON_SCHEMA'
}
export interface AiModelWithUserMsg { export interface AiModelWithUserMsg {
userMessage: { userMessage: {
@ -140,17 +210,14 @@ export interface AiModelWithUserMsg {
projectId?: string; projectId?: string;
location?: string; location?: string;
serviceAccountKey?: string; serviceAccountKey?: string;
serviceAccountKeyFileName?: string fileName?: string
}; };
// chatModelConfig: { modelId: string;
modelId: string; maxRetries: number;
maxRetries: number; timeoutSeconds: number;
timeoutSeconds: number;
// }
} }
} }
export interface CheckConnectivityResult { export interface CheckConnectivityResult {
status: string; status: string;
errorDetails: string; errorDetails: string;

3
ui-ngx/src/app/shared/shared.module.ts

@ -230,7 +230,6 @@ import { DurationLeftPipe } from '@shared/pipe/duration-left.pipe';
import { MqttVersionSelectComponent } from '@shared/components/mqtt-version-select.component'; import { MqttVersionSelectComponent } from '@shared/components/mqtt-version-select.component';
import { AIModelDialogComponent } from '@shared/components/ai-model/ai-model-dialog.component'; import { AIModelDialogComponent } from '@shared/components/ai-model/ai-model-dialog.component';
import { ModelsListAutocompleteComponent } from '@shared/components/ai-model/models-list-autocomplete.component'; import { ModelsListAutocompleteComponent } from '@shared/components/ai-model/models-list-autocomplete.component';
import { CheckConnectivityDialogComponent } from '@shared/components/ai-model/check-connectivity-dialog.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService; return markedOptionsService;
@ -448,7 +447,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
MqttVersionSelectComponent, MqttVersionSelectComponent,
AIModelDialogComponent, AIModelDialogComponent,
ModelsListAutocompleteComponent, ModelsListAutocompleteComponent,
CheckConnectivityDialogComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -715,7 +713,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
MqttVersionSelectComponent, MqttVersionSelectComponent,
AIModelDialogComponent, AIModelDialogComponent,
ModelsListAutocompleteComponent, ModelsListAutocompleteComponent,
CheckConnectivityDialogComponent,
] ]
}) })
export class SharedModule { } export class SharedModule { }

Loading…
Cancel
Save