UI: ref after review

This commit is contained in:
Artem Dzhereleiko 2025-07-10 13:32:39 +03:00
parent b84d2fcbb0
commit 64f0da3365
20 changed files with 88 additions and 259 deletions

View File

@ -794,6 +794,7 @@ const defaultUserMenuMap = new Map<Authority, MenuReference[]>([
{id: MenuId.home},
{id: MenuId.alarms},
{id: MenuId.dashboards},
{id: MenuId.ai_models},
{
id: MenuId.entities,
pages: [
@ -852,7 +853,6 @@ const defaultUserMenuMap = new Map<Authority, MenuReference[]>([
{id: MenuId.notification_rules}
]
},
{id: MenuId.ai_models},
{
id: MenuId.mobile_center,
pages: [

View File

@ -336,8 +336,7 @@ import * as DatapointsLimitComponent from '@shared/components/time/datapoints-li
import * as AggregationTypeSelectComponent from '@shared/components/time/aggregation/aggregation-type-select.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 AIModelDialogComponent from '@shared/components/ai-model/ai-model-dialog.component';
import * as ModelsListAutocompleteComponent from '@shared/components/ai-model/models-list-autocomplete.component';
import * as AIModelDialogComponent from '@home/components/ai-model/ai-model-dialog.component';
import { IModulesMap } from '@modules/common/modules-map.models';
import { Observable, of } from 'rxjs';
@ -534,8 +533,6 @@ class ModulesMap implements IModulesMap {
'@shared/components/image/gallery-image-input.component': GalleryImageInputComponent,
'@shared/components/image/multiple-gallery-image-input.component': MultipleGalleryImageInputComponent,
'@shared/components/popover.service': TbPopoverService,
'@shared/components/ai-model/ai-model-dialog.component': AIModelDialogComponent,
'@shared/components/ai-model/models-list-autocomplete.component': ModelsListAutocompleteComponent,
'@home/components/alarm/alarm-filter-config.component': AlarmFilterConfigComponent,
@ -672,7 +669,8 @@ class ModulesMap implements IModulesMap {
'@home/components/dashboard-page/dashboard-image-dialog.component': DashboardImageDialogComponent,
'@home/components/widget/widget-container.component': WidgetContainerComponent,
'@home/components/profile/queue/tenant-profile-queues.component': TenantProfileQueuesComponent,
'@home/components/queue/queue-form.component': QueueFormComponent
'@home/components/queue/queue-form.component': QueueFormComponent,
'@home/components/ai-model/ai-model-dialog.component': AIModelDialogComponent,
};
init(): Observable<any> {

View File

@ -56,7 +56,7 @@
</mat-select>
</mat-form-field>
<div formGroupName="providerConfig" class="tb-form-panel no-border no-padding">
@if (AiModelMap.get(provider).providerFieldsList.includes('personalAccessToken')) {
@if (providerFieldsList.includes('personalAccessToken')) {
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>ai-models.personal-access-token</mat-label>
<input type="password" required matInput formControlName="personalAccessToken">
@ -66,7 +66,7 @@
</mat-error>
</mat-form-field>
}
@if (AiModelMap.get(provider).providerFieldsList.includes('projectId')) {
@if (providerFieldsList.includes('projectId')) {
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>ai-models.project-id</mat-label>
<input matInput required formControlName="projectId">
@ -75,7 +75,7 @@
</mat-error>
</mat-form-field>
}
@if (AiModelMap.get(provider).providerFieldsList.includes('location')) {
@if (providerFieldsList.includes('location')) {
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>ai-models.location</mat-label>
<input matInput required formControlName="location">
@ -84,7 +84,7 @@
</mat-error>
</mat-form-field>
}
@if (AiModelMap.get(provider).providerFieldsList.includes('serviceAccountKey')) {
@if (providerFieldsList.includes('serviceAccountKey')) {
<tb-file-input formControlName="serviceAccountKey"
[existingFileName]="aiModelForms.get('configuration').get('providerConfig').get('fileName').value"
(fileNameChanged)="aiModelForms.get('configuration').get('providerConfig').get('fileName').setValue($event)"
@ -97,7 +97,7 @@
dropLabel="{{'ai-models.drop-file' | translate}}">
</tb-file-input>
}
@if (AiModelMap.get(provider).providerFieldsList.includes('endpoint')) {
@if (providerFieldsList.includes('endpoint')) {
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>ai-models.endpoint</mat-label>
<input required matInput formControlName="endpoint">
@ -106,13 +106,13 @@
</mat-error>
</mat-form-field>
}
@if (AiModelMap.get(provider).providerFieldsList.includes('serviceVersion')) {
@if (providerFieldsList.includes('serviceVersion')) {
<mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>ai-models.service-version</mat-label>
<input matInput formControlName="serviceVersion">
</mat-form-field>
}
@if (AiModelMap.get(provider).providerFieldsList.includes('apiKey')) {
@if (providerFieldsList.includes('apiKey')) {
<mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>ai-models.api-key</mat-label>
<input type="password" required matInput formControlName="apiKey">
@ -129,14 +129,17 @@
<div class="tb-form-panel-title" translate>ai-models.configuration</div>
<section class="tb-form-panel no-border no-padding">
<section class="tb-form-panel outlined no-border no-padding">
<tb-models-list-autocomplete formControlName="modelId"
[provider]="provider"
required
[label]="(provider === aiProvider.AZURE_OPENAI ? 'ai-models.deployment-name': 'ai-models.model-id') | translate"
[errorText]="(provider === aiProvider.AZURE_OPENAI ? 'ai-models.deployment-name-required': 'ai-models.model-id-required') | translate">
</tb-models-list-autocomplete>
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
additionalClass="tb-suffix-show-on-hover"
appearance="outline"
panelWidth=""
required
[label]="(provider === aiProvider.AZURE_OPENAI ? 'ai-models.deployment-name': 'ai-models.model-id') | translate"
[errorText]="(provider === aiProvider.AZURE_OPENAI ? 'ai-models.deployment-name-required': 'ai-models.model-id-required') | translate"
formControlName="modelId">
</tb-string-autocomplete>
</section>
@if (AiModelMap.get(provider).modelFieldsList.includes('temperature')) {
@if (modelFieldsList.includes('temperature')) {
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.temperature-hint' | translate }}">
{{ 'ai-models.temperature' | translate }}
@ -155,7 +158,7 @@
</mat-form-field>
</div>
}
@if (AiModelMap.get(provider).modelFieldsList.includes('topP')) {
@if (modelFieldsList.includes('topP')) {
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.top-p-hint' | translate }}">
{{ 'ai-models.top-p' | translate }}
@ -175,7 +178,7 @@
</mat-form-field>
</div>
}
@if (AiModelMap.get(provider).modelFieldsList.includes('topK')) {
@if (modelFieldsList.includes('topK')) {
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.top-k-hint' | translate }}">
{{ 'ai-models.top-k' | translate }}
@ -194,7 +197,7 @@
</mat-form-field>
</div>
}
@if (AiModelMap.get(provider).modelFieldsList.includes('presencePenalty')) {
@if (modelFieldsList.includes('presencePenalty')) {
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.presence-penalty-hint' | translate }}">
{{ 'ai-models.presence-penalty' | translate }}
@ -206,7 +209,7 @@
</div>
}
@if (AiModelMap.get(provider).modelFieldsList.includes('frequencyPenalty')) {
@if (modelFieldsList.includes('frequencyPenalty')) {
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.frequency-penalty-hint' | translate }}">
{{ 'ai-models.frequency-penalty' | translate }}
@ -217,7 +220,7 @@
</mat-form-field>
</div>
}
@if (AiModelMap.get(provider).modelFieldsList.includes('maxOutputTokens')) {
@if (modelFieldsList.includes('maxOutputTokens')) {
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'ai-models.max-output-token-hint' | translate }}">
{{ 'ai-models.max-output-token' | translate }}

View File

@ -20,7 +20,7 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { StepperOrientation } from '@angular/cdk/stepper';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { EntityType } from '@shared/models/entity-type.models';
@ -35,6 +35,7 @@ import {
} from '@shared/models/ai-model.models';
import { AiModelService } from '@core/http/ai-model.service';
import { CheckConnectivityDialogComponent } from '@home/components/ai-model/check-connectivity-dialog.component';
import { map } from 'rxjs/operators';
export interface AIModelDialogData {
AIModel?: AiModel;
@ -112,7 +113,7 @@ export class AIModelDialogComponent extends DialogComponent<AIModelDialogCompone
takeUntilDestroyed()
).subscribe((provider: AiProvider) => {
this.provider = provider;
this.aiModelForms.get('configuration.modelId').reset({});
this.aiModelForms.get('configuration.modelId').reset('');
this.aiModelForms.get('configuration.providerConfig').reset({});
this.updateValidation(provider);
})
@ -120,11 +121,28 @@ export class AIModelDialogComponent extends DialogComponent<AIModelDialogCompone
this.updateValidation(this.provider);
}
fetchOptions(searchText: string): Observable<Array<string>> {
const search = searchText ? searchText?.toLowerCase() : '';
return of(this.provider ? AiModelMap.get(this.provider).modelList || [] : []).pipe(
map(name => name?.filter(option => option.toLowerCase().includes(search))),
);
}
private updateValidation(provider: AiProvider) {
ProviderFieldsAllList.forEach(key =>
this.aiModelForms.get('configuration.providerConfig')
.get(key)[AiModelMap.get(provider).providerFieldsList.includes(key) ? 'enable' : 'disable']()
)
ProviderFieldsAllList.forEach(key => {
if (AiModelMap.get(provider).providerFieldsList.includes(key)) {
this.aiModelForms.get('configuration.providerConfig').get(key).enable();
} else {
this.aiModelForms.get('configuration.providerConfig').get(key).disable();
}
})
}
get providerFieldsList(): string[] {
return AiModelMap.get(this.provider).providerFieldsList;
}
get modelFieldsList(): string[] {
return AiModelMap.get(this.provider).modelFieldsList;
}
cancel(): void {

View File

@ -15,15 +15,15 @@
limitations under the License.
-->
<div class="flex h-6 flex-row">
<h2 class="connectivity-title" translate>ai-models.check-connectivity</h2>
<mat-toolbar class="transparent">
<h2 translate>ai-models.check-connectivity</h2>
<span class="flex-1"></span>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</div>
</mat-toolbar>
<div mat-dialog-content>
<div class="flex h-full flex-1 flex-col items-center justify-center">
<mat-progress-spinner color="warn" mode="indeterminate"
@ -47,8 +47,7 @@
</div>
</div>
</div>
<div mat-dialog-actions>
<span class="flex-1"></span>
<div mat-dialog-actions class="justify-end">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"

View File

@ -21,11 +21,8 @@
max-height: 100vh;
display: grid;
.connectivity-title {
font-size: 18px;
font-weight: 500;
margin: 0;
padding-left: 16px;
.transparent {
background-color: transparent;
}
.connection-status {

View File

@ -71,7 +71,11 @@ export class CheckConnectivityDialogComponent extends DialogComponent<CheckConne
if (result.status === 'SUCCESS') {
this.showCheckSuccess = true;
} else {
this.checkErrMsg = JSON.parse(result.errorDetails);
try {
this.checkErrMsg = JSON.parse(result.errorDetails);
} catch (e) {
this.checkErrMsg = result.errorDetails;
}
}
},
error: err => this.checkErrMsg = err.error.message

View File

@ -204,6 +204,7 @@ import {
CalculatedFieldTestArgumentsComponent
} from '@home/components/calculated-fields/components/test-arguments/calculated-field-test-arguments.component';
import { CheckConnectivityDialogComponent } from '@home/components/ai-model/check-connectivity-dialog.component';
import { AIModelDialogComponent } from '@home/components/ai-model/ai-model-dialog.component';
@NgModule({
declarations:
@ -356,6 +357,7 @@ import { CheckConnectivityDialogComponent } from '@home/components/ai-model/chec
CalculatedFieldScriptTestDialogComponent,
CalculatedFieldTestArgumentsComponent,
CheckConnectivityDialogComponent,
AIModelDialogComponent,
],
imports: [
CommonModule,
@ -502,6 +504,7 @@ import { CheckConnectivityDialogComponent } from '@home/components/ai-model/chec
CalculatedFieldScriptTestDialogComponent,
CalculatedFieldTestArgumentsComponent,
CheckConnectivityDialogComponent,
AIModelDialogComponent,
],
providers: [
WidgetComponentService,

View File

@ -29,8 +29,8 @@
labelText="ai-models.ai-model"
(entityChanged)="onEntityChange($event)"
[entityType]="entityType.AI_MODEL"
(createNew)="createModelAi('modelSettingsId')"
formControlName="modelSettingsId">
(createNew)="createModelAi('modelId')"
formControlName="modelId">
</tb-entity-autocomplete>
</section>
</section>
@ -84,6 +84,7 @@
@if (aiConfigForm.get('responseFormat.type').value === responseFormat.JSON_SCHEMA) {
<tb-json-object-edit
jsonRequired
iconHint="{{ 'rule-node-config.ai.response-json-schema-hint' | translate }}"
label="{{ 'rule-node-config.ai.response-json-schema' | translate }}"
formControlName="schema">
</tb-json-object-edit>

View File

@ -19,7 +19,7 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms
import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models';
import { EntityType } from '@shared/models/entity-type.models';
import { MatDialog } from '@angular/material/dialog';
import { AIModelDialogComponent, AIModelDialogData } from '@shared/components/ai-model/ai-model-dialog.component';
import { AIModelDialogComponent, AIModelDialogData } from '@home/components/ai-model/ai-model-dialog.component';
import { AiModel, AiRuleNodeResponseFormatTypeOnlyText, ResponseFormat } from '@shared/models/ai-model.models';
import { deepTrim } from '@core/utils';
@ -47,7 +47,7 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
protected onConfigurationSet(configuration: RuleNodeConfiguration) {
this.aiConfigForm = this.fb.group({
modelSettingsId: [configuration?.modelSettingsId ?? null, [Validators.required]],
modelId: [configuration?.modelId ?? null, [Validators.required]],
systemPrompt: [configuration?.systemPrompt ?? '', [Validators.maxLength(10000), Validators.pattern(/.*\S.*/)]],
userPrompt: [configuration?.userPrompt ?? '', [Validators.required, Validators.maxLength(10000), Validators.pattern(/.*\S.*/)]],
responseFormat: this.fb.group({

View File

@ -31,7 +31,7 @@ import { Observable } from 'rxjs';
import { AiModel, AiProviderTranslations } from '@shared/models/ai-model.models';
import { AiModelService } from '@core/http/ai-model.service';
import { AiModelTableHeaderComponent } from '@home/pages/ai-model/ai-model-table-header.component';
import { AIModelDialogComponent, AIModelDialogData } from '@shared/components/ai-model/ai-model-dialog.component';
import { AIModelDialogComponent, AIModelDialogData } from '@home/components/ai-model/ai-model-dialog.component';
import { map } from 'rxjs/operators';
@Injectable()
@ -81,7 +81,7 @@ export class AiModelsTableConfigResolver {
this.config.cellActionDescriptors = this.configureCellActions();
this.config.handleRowClick = ($event, model) => {
this.editModel(model);
this.editModel($event, model);
return true;
};
}
@ -96,12 +96,13 @@ export class AiModelsTableConfigResolver {
name: this.translate.instant('action.edit'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.editModel(entity)
onAction: ($event, entity) => this.editModel($event, entity)
}
];
}
private editModel(AIModel: AiModel): void {
private editModel($event, AIModel: AiModel): void {
$event?.stopPropagation();
this.addModel(AIModel, false).subscribe();
}

View File

@ -1,41 +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.
-->
<mat-form-field [appearance]="appearance" [subscriptSizing]="subscriptSizing" style="width: 100%">
<mat-label *ngIf="label">{{label}}</mat-label>
<input matInput #nameInput [formControl]="selectionFormControl"
[placeholder]="placeholderText"
(focusin)="onFocus()"
[matAutocomplete]="optionsAutocomplete">
<button *ngIf="selectionFormControl.value && !disabled"
type="button"
class="tb-icon-24 mr-2"
matSuffix mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-error *ngIf="selectionFormControl.hasError('required')">
{{errorText}}
</mat-error>
<mat-autocomplete
#optionsAutocomplete="matAutocomplete"
class="tb-autocomplete tb-options-input-autocomplete">
<mat-option *ngFor="let option of filteredOptions$ | async" [value]="option">
<span class="tb-option-name flex-1" [innerHTML]="option | highlight:searchText:true:'ig'"></span>
</mat-option>
</mat-autocomplete>
</mat-form-field>

View File

@ -1,161 +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.
///
import { Component, ElementRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { coerceBoolean } from '@shared/decorators/coercion';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
import { AiModelMap, AiProvider } from '@shared/models/ai-model.models';
@Component({
selector: 'tb-models-list-autocomplete',
templateUrl: './models-list-autocomplete.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ModelsListAutocompleteComponent),
multi: true
}
]
})
export class ModelsListAutocompleteComponent implements ControlValueAccessor, OnInit, OnChanges {
@ViewChild('nameInput', {static: true}) nameInput: ElementRef;
@Input()
disabled: boolean;
@Input()
@coerceBoolean()
required = false;
@Input()
provider: AiProvider;
@Input()
placeholderText: string = this.translate.instant('widget-config.set');
@Input()
subscriptSizing: SubscriptSizing = 'dynamic';
@Input()
appearance: MatFormFieldAppearance = 'outline';
@Input()
label: string;
@Input()
errorText: string;
selectionFormControl: FormControl;
modelValue: string | null;
filteredOptions$: Observable<Array<string>>;
searchText = '';
private dirty = false;
private propagateChange = (_val: any) => {};
constructor(private fb: FormBuilder,
private translate: TranslateService) {
}
ngOnInit() {
this.selectionFormControl = this.fb.control('', this.required ? [Validators.required] : []);
this.setupFilteredOptions();
}
ngOnChanges(changes: SimpleChanges) {
if (changes.provider && !changes.provider.isFirstChange()) {
this.setupFilteredOptions();
this.selectionFormControl.setValue(null, {emitEvent: false});
this.modelValue = null;
this.propagateChange(null);
}
}
private setupFilteredOptions() {
this.filteredOptions$ = this.selectionFormControl.valueChanges.pipe(
startWith(''),
tap(value => this.updateView(value)),
map(value => {
const search = value ? value.toLowerCase() : '';
const options = this.provider ? AiModelMap.get(this.provider).modelList || [] : [];
return search ? options.filter(option => option.toLowerCase().includes(search)) : options;
})
);
}
writeValue(option?: string): void {
this.searchText = '';
this.modelValue = option ? option : null;
if (option) {
this.selectionFormControl.patchValue(option, { emitEvent: false });
this.dirty = true;
} else {
this.selectionFormControl.patchValue(null, { emitEvent: false });
this.dirty = true;
}
}
onFocus() {
if (this.dirty) {
this.selectionFormControl.updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
updateView(value: string) {
this.searchText = value ? value : '';
if (this.modelValue !== value && value) {
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.selectionFormControl.disable({emitEvent: false});
} else {
this.selectionFormControl.enable({emitEvent: false});
}
}
clear() {
this.selectionFormControl.patchValue(null, {emitEvent: true});
this.propagateChange(null);
this.modelValue = null;
setTimeout(() => {
this.nameInput.nativeElement.blur();
this.nameInput.nativeElement.focus();
}, 0);
}
}

View File

@ -32,6 +32,13 @@
mat-button *ngIf="!readonly && !disabled" class="tidy" (click)="minifyJSON()">
{{'js-func.mini' | translate }}
</button>
@if (iconHint) {
<button mat-icon-button class="tb-mat-32"
matTooltip="{{ iconHint }}"
matTooltipPosition="above">
<mat-icon class="material-icons">info</mat-icon>
</button>
}
<button mat-icon-button class="tb-mat-32" (click)="fullscreen = !fullscreen"
matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above">

View File

@ -74,6 +74,8 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
@Input() label: string;
@Input() iconHint: string;
@Input() disabled: boolean;
@Input() fillHeight: boolean;

View File

@ -43,7 +43,7 @@
<mat-autocomplete
#optionsAutocomplete="matAutocomplete"
class="tb-autocomplete tb-options-input-autocomplete"
panelWidth="fit-content">
[panelWidth]="panelWidth">
<mat-option *ngFor="let option of filteredOptions$ | async" [value]="option">
<span class="tb-option-name flex-1" [innerHTML]="option | highlight:searchText:true:'ig'"></span>
</mat-option>

View File

@ -76,6 +76,9 @@ export class StringAutocompleteComponent implements ControlValueAccessor, OnInit
@Input()
label: string;
@Input()
panelWidth: string = 'fit-content';
@Input()
tooltipClass = 'tb-error-tooltip';

View File

@ -228,8 +228,6 @@ import { JsFuncModuleRowComponent } from '@shared/components/js-func-module-row.
import { EntityKeyAutocompleteComponent } from '@shared/components/entity/entity-key-autocomplete.component';
import { DurationLeftPipe } from '@shared/pipe/duration-left.pipe';
import { MqttVersionSelectComponent } from '@shared/components/mqtt-version-select.component';
import { AIModelDialogComponent } from '@shared/components/ai-model/ai-model-dialog.component';
import { ModelsListAutocompleteComponent } from '@shared/components/ai-model/models-list-autocomplete.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@ -445,8 +443,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ScadaSymbolInputComponent,
EntityKeyAutocompleteComponent,
MqttVersionSelectComponent,
AIModelDialogComponent,
ModelsListAutocompleteComponent,
],
imports: [
CommonModule,
@ -711,8 +707,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ScadaSymbolInputComponent,
EntityKeyAutocompleteComponent,
MqttVersionSelectComponent,
AIModelDialogComponent,
ModelsListAutocompleteComponent,
]
})
export class SharedModule { }

View File

@ -5447,6 +5447,7 @@
"response-text": "Text",
"response-json": "JSON",
"response-json-schema": "JSON Schema",
"response-json-schema-hint": "While any valid JSON Schema can be entered, this rule node only supports a limited subset of its features. See node documentation for details.",
"response-json-schema-required": "JSON Schema is required",
"advanced-settings": "Advanced settings",
"timeout": "Timeout",