,
+ @Inject(MAT_DIALOG_DATA) public data: ResourcesDialogData,
+ @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
+ private resourceService: ResourceService) {
+ super(store, router, dialogRef);
+
+ if (this.data.isAdd) {
+ this.isAdd = true;
+ }
+
+ if (this.data.resources) {
+ this.resources = this.data.resources;
+ }
+ }
+
+ ngAfterViewInit(): void {
+ if (this.isAdd) {
+ setTimeout(() => {
+ this.resourcesComponent.entityForm.markAsDirty();
+ }, 0);
+ }
+ }
+
+ isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {
+ const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
+ const customErrorState = !!(control && control.invalid && this.submitted);
+ return originalErrorState || customErrorState;
+ }
+
+ cancel(): void {
+ this.dialogRef.close(null);
+ }
+
+ save(): void {
+ this.submitted = true;
+ if (this.resourcesComponent.entityForm.valid) {
+ const resource = {...this.resourcesComponent.entityFormValue()};
+ if (Array.isArray(resource.data)) {
+ const resources = [];
+ resource.data.forEach((data, index) => {
+ resources.push({
+ resourceType: resource.resourceType,
+ data,
+ fileName: resource.fileName[index],
+ title: resource.title
+ });
+ });
+ this.resourceService.saveResources(resources, {resendRequest: true}).pipe(
+ map((response) => response[0])
+ ).subscribe(result => this.dialogRef.close(result));
+ } else {
+ if (resource.resourceType !== ResourceType.GENERAL) {
+ delete resource.descriptor;
+ }
+ this.resourceService.saveResource(resource).subscribe(result => this.dialogRef.close(result));
+ }
+ }
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html b/ui-ngx/src/app/modules/home/components/resources/resources-library.component.html
similarity index 56%
rename from ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html
rename to ui-ngx/src/app/modules/home/components/resources/resources-library.component.html
index ebb946ccbc..4737b75ef2 100644
--- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html
+++ b/ui-ngx/src/app/modules/home/components/resources/resources-library.component.html
@@ -15,7 +15,7 @@
limitations under the License.
-->
-
diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts
index bfba25afa1..07bcb86fdf 100644
--- a/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts
+++ b/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts
@@ -24,6 +24,8 @@ import { AiModel, AiRuleNodeResponseFormatTypeOnlyText, ResponseFormat } from '@
import { deepTrim } from '@core/utils';
import { TranslateService } from '@ngx-translate/core';
import { jsonRequired } from '@shared/components/json-object-edit.component';
+import { Resource, ResourceType } from "@shared/models/resource.models";
+import { ResourcesDialogComponent, ResourcesDialogData } from "@home/components/resources/resources-dialog.component";
@Component({
selector: 'tb-external-node-ai-config',
@@ -38,6 +40,9 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
responseFormat = ResponseFormat;
+ EntityType = EntityType;
+ ResourceType = ResourceType;
+
constructor(private fb: UntypedFormBuilder,
private translate: TranslateService,
private dialog: MatDialog) {
@@ -53,6 +58,7 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
modelId: [configuration?.modelId ?? null, [Validators.required]],
systemPrompt: [configuration?.systemPrompt ?? '', [Validators.maxLength(500_000), Validators.pattern(/.*\S.*/)]],
userPrompt: [configuration?.userPrompt ?? '', [Validators.required, Validators.maxLength(500_000), Validators.pattern(/.*\S.*/)]],
+ resourceIds: [configuration?.resourceIds ?? []],
responseFormat: this.fb.group({
type: [configuration?.responseFormat?.type ?? ResponseFormat.JSON, []],
schema: [configuration?.responseFormat?.schema ?? null, [jsonRequired]],
@@ -116,5 +122,23 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
this.aiConfigForm.get(formControl).markAsDirty();
}
});
+ };
+
+ createAiResources(name: string, formControl: string) {
+ this.dialog.open(ResourcesDialogComponent, {
+ disableClose: true,
+ panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
+ data: {
+ resources: {title: name, resourceType: ResourceType.GENERAL},
+ isAdd: true
+ }
+ }).afterClosed()
+ .subscribe((resource) => {
+ if (resource) {
+ const resourceIds = [...(this.aiConfigForm.get(formControl).value || []), resource.id.id];
+ this.aiConfigForm.get(formControl).patchValue(resourceIds);
+ this.aiConfigForm.get(formControl).markAsDirty();
+ }
+ });
}
}
diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
index 10721ade5a..60790edd74 100644
--- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
+++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
@@ -26,7 +26,6 @@ import { HomeComponentsModule } from '@modules/home/components/home-components.m
import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component';
import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component';
import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component';
-import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component';
import { ResourceTabsComponent } from '@home/pages/admin/resource/resource-tabs.component';
import { ResourcesTableHeaderComponent } from '@home/pages/admin/resource/resources-table-header.component';
import { QueueComponent } from '@home/pages/admin/queue/queue.component';
@@ -49,7 +48,6 @@ import { ResourceLibraryTabsComponent } from '@home/pages/admin/resource/resourc
SendTestSmsDialogComponent,
SecuritySettingsComponent,
HomeSettingsComponent,
- ResourcesLibraryComponent,
ResourceTabsComponent,
ResourceLibraryTabsComponent,
ResourcesTableHeaderComponent,
diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts
index f39ed9ff7b..d92355fbac 100644
--- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts
+++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts
@@ -22,7 +22,13 @@ import {
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import { Router } from '@angular/router';
-import { Resource, ResourceInfo, ResourceType, ResourceTypeTranslationMap } from '@shared/models/resource.models';
+import {
+ Resource,
+ ResourceInfo, ResourceInfoWithReferences,
+ ResourceType,
+ ResourceTypeTranslationMap,
+ toResourceDeleteResult
+} from '@shared/models/resource.models';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { DatePipe } from '@angular/common';
@@ -32,12 +38,22 @@ import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Authority } from '@shared/models/authority.enum';
-import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component';
+import { ResourcesLibraryComponent } from '@home/components/resources/resources-library.component';
import { PageLink } from '@shared/models/page/page-link';
import { EntityAction } from '@home/models/entity/entity-component.models';
-import { map } from 'rxjs/operators';
+import { catchError, map } from 'rxjs/operators';
import { ResourcesTableHeaderComponent } from '@home/pages/admin/resource/resources-table-header.component';
import { ResourceLibraryTabsComponent } from '@home/pages/admin/resource/resource-library-tabs.component';
+import { forkJoin, of } from "rxjs";
+import {
+ ResourcesInUseDialogComponent,
+ ResourcesInUseDialogData
+} from "@shared/components/resource/resources-in-use-dialog.component";
+import { parseHttpErrorMessage } from "@core/utils";
+import { ActionNotificationShow } from "@core/notification/notification.actions";
+import { ResourcesDatasource } from "@home/pages/admin/resource/resources-datasource";
+import { MatDialog } from "@angular/material/dialog";
+import { DialogService } from "@core/services/dialog.service";
@Injectable()
export class ResourcesLibraryTableConfigResolver {
@@ -49,6 +65,8 @@ export class ResourcesLibraryTableConfigResolver {
private resourceService: ResourceService,
private translate: TranslateService,
private router: Router,
+ private dialog: MatDialog,
+ private dialogService: DialogService,
private datePipe: DatePipe) {
this.config.entityType = EntityType.TB_RESOURCE;
@@ -76,19 +94,27 @@ export class ResourcesLibraryTableConfigResolver {
icon: 'file_download',
isEnabled: () => true,
onAction: ($event, entity) => this.downloadResource($event, entity)
- }
+ },
+ {
+ name: this.translate.instant('resource.delete'),
+ icon: 'delete',
+ isEnabled: (resource) => this.config.deleteEnabled(resource),
+ onAction: ($event, entity) => this.deleteResource($event, entity)
+ },
);
- this.config.deleteEntityTitle = resource => this.translate.instant('resource.delete-resource-title',
- { resourceTitle: resource.title });
- this.config.deleteEntityContent = () => this.translate.instant('resource.delete-resource-text');
- this.config.deleteEntitiesTitle = count => this.translate.instant('resource.delete-resources-title', {count});
- this.config.deleteEntitiesContent = () => this.translate.instant('resource.delete-resources-text');
+ this.config.groupActionDescriptors = [{
+ name: this.translate.instant('action.delete'),
+ icon: 'delete',
+ isEnabled: true,
+ onAction: ($event, entities) => this.deleteResources($event, entities)
+ }];
+
+ this.config.entitiesDeleteEnabled = false;
this.config.entitiesFetchFunction = pageLink => this.resourceService.getResources(pageLink, this.config.componentsData.resourceType);
this.config.loadEntity = id => this.resourceService.getResourceInfoById(id.id);
this.config.saveEntity = resource => this.saveResource(resource);
- this.config.deleteEntity = id => this.resourceService.deleteResource(id.id);
this.config.onEntityAction = action => this.onResourceAction(action);
}
@@ -147,6 +173,8 @@ export class ResourcesLibraryTableConfigResolver {
case 'downloadResource':
this.downloadResource(action.event, action.entity);
return true;
+ case 'deleteLibrary':
+ this.deleteResource(action.event, action.entity);
}
return false;
}
@@ -165,4 +193,138 @@ export class ResourcesLibraryTableConfigResolver {
return authority === Authority.SYS_ADMIN;
}
}
+
+ private deleteResource($event: Event, resource: ResourceInfo) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ this.dialogService.confirm(
+ this.translate.instant('resource.delete-resource-title', { resourceTitle: resource.title }),
+ this.translate.instant('resource.delete-resource-text'),
+ this.translate.instant('action.no'),
+ this.translate.instant('action.yes'),
+ true
+ ).subscribe((result) => {
+ if (result) {
+ this.resourceService.deleteResource(resource.id.id, false, {ignoreErrors: true}).pipe(
+ map(() => toResourceDeleteResult(resource)),
+ catchError((err) => of(toResourceDeleteResult(resource, err)))
+ ).subscribe(
+ (deleteResult) => {
+ if (deleteResult.success) {
+ if (this.config.getEntityDetailsPage()) {
+ this.config.getEntityDetailsPage().goBack();
+ } else {
+ this.config.updateData(true);
+ }
+ } else if (deleteResult.resourceIsReferencedError) {
+ const resources: ResourceInfoWithReferences[] = [{...resource, ...{references: deleteResult.references}}];
+ const data = {
+ multiple: false,
+ resources,
+ configuration: {
+ title: 'resource.resource-is-in-use',
+ message: this.translate.instant('resource.resource-is-in-use-text', {title: resources[0].title}),
+ deleteText: 'resource.delete-resource-in-use-text',
+ selectedText: 'resource.selected-resources',
+ columns: ['select', 'title', 'references']
+ }
+ };
+ this.dialog.open(ResourcesInUseDialogComponent, {
+ disableClose: true,
+ panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
+ data
+ }).afterClosed().subscribe((resources) => {
+ if (resources) {
+ this.resourceService.deleteResource(resource.id.id, true).subscribe(() => {
+ if (this.config.getEntityDetailsPage()) {
+ this.config.getEntityDetailsPage().goBack();
+ } else {
+ this.config.updateData(true);
+ }
+ });
+ }
+ });
+ } else {
+ const errorMessageWithTimeout = parseHttpErrorMessage(deleteResult.error, this.translate);
+ setTimeout(() => {
+ this.store.dispatch(new ActionNotificationShow({message: errorMessageWithTimeout.message, type: 'error'}));
+ }, errorMessageWithTimeout.timeout);
+ }
+ }
+ );
+ }
+ });
+ }
+
+ private deleteResources($event: Event, resources: ResourceInfo[]) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ if (resources && resources.length) {
+ const title = this.translate.instant('resource.delete-resources-title', {count: resources.length});
+ const content = this.translate.instant('resource.delete-resources-text');
+ this.dialogService.confirm(title, content,
+ this.translate.instant('action.no'),
+ this.translate.instant('action.yes')).subscribe((result) => {
+ if (result) {
+ const tasks = resources.map((resource) =>
+ this.resourceService.deleteResource(resource.id.id, false, {ignoreErrors: true}).pipe(
+ map(() => toResourceDeleteResult(resource)),
+ catchError((err) => of(toResourceDeleteResult(resource, err)))
+ )
+ );
+ forkJoin(tasks).subscribe(
+ (deleteResults) => {
+ const anySuccess = deleteResults.some(res => res.success);
+ const referenceErrors = deleteResults.filter(res => res.resourceIsReferencedError);
+ const otherError = deleteResults.find(res => !res.success);
+ if (anySuccess) {
+ this.config.updateData();
+ }
+ if (referenceErrors?.length) {
+ const resourcesWithReferences: ResourceInfoWithReferences[] =
+ referenceErrors.map(ref => ({...ref.resource, ...{references: ref.references}}));
+ const data = {
+ multiple: true,
+ resources: resourcesWithReferences,
+ configuration: {
+ title: 'resource.resources-are-in-use',
+ message: this.translate.instant('resource.resources-are-in-use-text'),
+ deleteText: 'resource.delete-resource-in-use-text',
+ selectedText: 'resource.selected-resources',
+ datasource: new ResourcesDatasource(this.resourceService, resourcesWithReferences, () => true),
+ columns: ['select', 'title', 'references']
+ }
+ };
+ this.dialog.open(ResourcesInUseDialogComponent, {
+ disableClose: true,
+ panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
+ data
+ }).afterClosed().subscribe((forceDeleteResources) => {
+ if (forceDeleteResources && forceDeleteResources.length) {
+ const forceDeleteTasks = forceDeleteResources.map((resource) =>
+ this.resourceService.deleteResource(resource.id.id, true)
+ );
+ forkJoin(forceDeleteTasks).subscribe(
+ () => {
+ this.config.updateData();
+ }
+ );
+ }
+ });
+ } else if (otherError) {
+ const errorMessageWithTimeout = parseHttpErrorMessage(otherError.error, this.translate);
+ setTimeout(() => {
+ this.store.dispatch(new ActionNotificationShow({message: errorMessageWithTimeout.message, type: 'error'}));
+ }, errorMessageWithTimeout.timeout);
+ }
+ }
+ );
+ }
+ });
+ }
+ }
}
diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.ts
index 80c760c6ca..136c143e3c 100644
--- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.ts
+++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.ts
@@ -28,7 +28,7 @@ import { PageLink } from '@shared/models/page/page-link';
})
export class ResourcesTableHeaderComponent extends EntityTableHeaderComponent {
- readonly resourceTypes = [ResourceType.LWM2M_MODEL, ResourceType.PKCS_12, ResourceType.JKS];
+ readonly resourceTypes = [ResourceType.LWM2M_MODEL, ResourceType.PKCS_12, ResourceType.JKS, ResourceType.GENERAL];
readonly resourceTypesTranslationMap = ResourceTypeTranslationMap;
constructor(protected store: Store) {
diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.html b/ui-ngx/src/app/shared/components/entity/entity-list.component.html
index 6bf7cdb78d..e0e7dfe3f7 100644
--- a/ui-ngx/src/app/shared/components/entity/entity-list.component.html
+++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.html
@@ -40,6 +40,12 @@
[matAutocompleteConnectedTo]="origin"
[matAutocomplete]="entityAutocomplete"
[matChipInputFor]="chipList">
+
{{ 'entity.no-entities-matching' | translate: {entity: searchText} }}
+ @if (allowCreateNew) {
+
+ entity.create-new-key
+
+ }
diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts
index 552c4f1f71..9d1de180e9 100644
--- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts
+++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts
@@ -14,7 +14,18 @@
/// limitations under the License.
///
-import { Component, ElementRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core';
+import {
+ Component,
+ ElementRef,
+ EventEmitter,
+ forwardRef,
+ Input,
+ OnChanges,
+ OnInit,
+ Output,
+ SimpleChanges,
+ ViewChild
+} from '@angular/core';
import {
ControlValueAccessor,
NG_VALIDATORS,
@@ -93,6 +104,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
}
@Input()
+ @coerceBoolean()
disabled: boolean;
@Input()
@@ -109,6 +121,13 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
@coerceBoolean()
inlineField: boolean;
+ @Input()
+ @coerceBoolean()
+ allowCreateNew: boolean;
+
+ @Output()
+ createNew = new EventEmitter();
+
@ViewChild('entityInput') entityInput: ElementRef;
@ViewChild('entityAutocomplete') matAutocomplete: MatAutocomplete;
@ViewChild('chipList', {static: true}) chipList: MatChipGrid;
@@ -136,6 +155,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
this.entityListFormGroup.get('entities').updateValueAndValidity();
}
+ createNewEntity($event: Event, searchText?: string) {
+ $event.stopPropagation();
+ this.createNew.emit(searchText);
+ }
+
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
@@ -201,6 +225,9 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
this.modelValue = null;
}
this.dirty = true;
+ if (this.entityInput) {
+ this.entityInput.nativeElement.value = '';
+ }
}
validate(): ValidationErrors | null {
diff --git a/ui-ngx/src/app/shared/components/file-input.component.ts b/ui-ngx/src/app/shared/components/file-input.component.ts
index bbe68bb9c6..6960db73dd 100644
--- a/ui-ngx/src/app/shared/components/file-input.component.ts
+++ b/ui-ngx/src/app/shared/components/file-input.component.ts
@@ -129,10 +129,15 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
@Output()
fileNameChanged = new EventEmitter();
+ @Output()
+ mediaTypeChanged = new EventEmitter();
+
fileName: string | string[];
fileContent: any;
files: File[];
+ mediaType: string;
+
@ViewChild('flow', {static: true})
flow: FlowDirective;
@@ -180,6 +185,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
this.fileContent = files[0].fileContent;
this.fileName = files[0].fileName;
this.files = files[0].files;
+ this.mediaType = files[0].mediaType;
this.updateModel();
} else if (files.length > 1) {
this.fileContent = files.map(content => content.fileContent);
@@ -203,6 +209,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
let fileName = null;
let fileContent = null;
let files = null;
+ let mediaType = null;
if (reader.readyState === reader.DONE) {
if (!this.workFromFileObj) {
fileContent = reader.result;
@@ -211,16 +218,18 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
fileContent = this.contentConvertFunction(fileContent);
}
fileName = fileContent ? file.name : null;
+ mediaType = file?.file?.type || null;
}
} else if (file.name || file.file){
files = file.file;
fileName = file.name;
+ mediaType = file.file.type || null;
}
}
- resolve({fileContent, fileName, files});
+ resolve({fileContent, fileName, files, mediaType});
};
reader.onerror = () => {
- resolve({fileContent: null, fileName: null, files: null});
+ resolve({fileContent: null, fileName: null, files: null, mediaType: null});
};
if (this.readAsBinary) {
reader.readAsBinaryString(file.file);
@@ -283,6 +292,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
this.propagateChange(this.files);
} else {
this.propagateChange(this.fileContent);
+ this.mediaTypeChanged.emit(this.mediaType);
this.fileNameChanged.emit(this.fileName);
}
}
diff --git a/ui-ngx/src/app/shared/models/ai-model.models.ts b/ui-ngx/src/app/shared/models/ai-model.models.ts
index f3161263b7..d3b70f9a74 100644
--- a/ui-ngx/src/app/shared/models/ai-model.models.ts
+++ b/ui-ngx/src/app/shared/models/ai-model.models.ts
@@ -34,6 +34,13 @@ export interface AiModel extends Omit, 'label'>, HasTenantId
region?: string;
accessKeyId?: string;
secretAccessKey?: string;
+ baseUrl?: string;
+ auth?: {
+ type: AuthenticationType;
+ username?: string;
+ password?: string;
+ token?: string
+ }
};
modelId: string;
temperature?: number;
@@ -42,6 +49,7 @@ export interface AiModel extends Omit, 'label'>, HasTenantId
frequencyPenalty?: number;
presencePenalty?: number;
maxOutputTokens?: number;
+ contextLength?: number;
}
}
@@ -57,7 +65,8 @@ export enum AiProvider {
MISTRAL_AI = 'MISTRAL_AI',
ANTHROPIC = 'ANTHROPIC',
AMAZON_BEDROCK = 'AMAZON_BEDROCK',
- GITHUB_MODELS = 'GITHUB_MODELS'
+ GITHUB_MODELS = 'GITHUB_MODELS',
+ OLLAMA = 'OLLAMA'
}
export const AiProviderTranslations = new Map(
@@ -69,7 +78,8 @@ export const AiProviderTranslations = new Map(
[AiProvider.MISTRAL_AI , 'ai-models.ai-providers.mistral-ai'],
[AiProvider.ANTHROPIC , 'ai-models.ai-providers.anthropic'],
[AiProvider.AMAZON_BEDROCK , 'ai-models.ai-providers.amazon-bedrock'],
- [AiProvider.GITHUB_MODELS , 'ai-models.ai-providers.github-models']
+ [AiProvider.GITHUB_MODELS , 'ai-models.ai-providers.github-models'],
+ [AiProvider.OLLAMA , 'ai-models.ai-providers.ollama']
]
);
@@ -84,10 +94,11 @@ export const ProviderFieldsAllList = [
'serviceVersion',
'region',
'accessKeyId',
- 'secretAccessKey'
+ 'secretAccessKey',
+ 'baseUrl'
];
-export const ModelFieldsAllList = ['temperature', 'topP', 'topK', 'frequencyPenalty', 'presencePenalty', 'maxOutputTokens'];
+export const ModelFieldsAllList = ['temperature', 'topP', 'topK', 'frequencyPenalty', 'presencePenalty', 'maxOutputTokens', 'contextLength'];
export const AiModelMap = new Map([
[
@@ -99,13 +110,16 @@ export const AiModelMap = new Map(
[ResourceType.LWM2M_MODEL, 'resource.type.lwm2m-model'],
[ResourceType.PKCS_12, 'resource.type.pkcs-12'],
[ResourceType.JKS, 'resource.type.jks'],
- [ResourceType.JS_MODULE, 'resource.type.js-module']
+ [ResourceType.JS_MODULE, 'resource.type.js-module'],
+ [ResourceType.GENERAL, 'resource.type.general'],
]
);
@@ -76,8 +78,8 @@ export interface TbResourceInfo extends Omit, 'name' |
title?: string;
resourceType: ResourceType;
resourceSubType?: ResourceSubType;
- fileName: string;
- public: boolean;
+ fileName?: string;
+ public?: boolean;
publicResourceKey?: string;
readonly link?: string;
readonly publicLink?: string;
@@ -87,7 +89,7 @@ export interface TbResourceInfo extends Omit, 'name' |
export type ResourceInfo = TbResourceInfo;
export interface Resource extends ResourceInfo {
- data: string;
+ data?: string;
name?: string;
}
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index 867b8b1300..794688ea29 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -1112,13 +1112,15 @@
"mistral-ai": "Mistral AI",
"anthropic": "Anthropic",
"amazon-bedrock": "Amazon Bedrock",
- "github-models": "GitHub Models"
+ "github-models": "GitHub Models",
+ "ollama": "Ollama"
},
"name-required": "Name is required.",
"name-max-length": "Name must be 255 characters or less.",
"provider": "Provider",
"api-key": "API key",
"api-key-required": "API key is required.",
+ "api-key-open-ai-required": "API key is required when using the official OpenAI API.",
"project-id": "Project ID",
"project-id-required": "Project ID is required",
"location": "Location",
@@ -1155,17 +1157,34 @@
"frequency-penalty": "Frequency penalty",
"frequency-penalty-hint": "Applies a penalty to a token's likelihood that increases based on its frequency in the text.",
"max-output-tokens": "Maximum output tokens",
- "max-output-tokens-min": "Must be greater than 0.",
"max-output-tokens-hint": "Sets the maximum number of tokens that the \nmodel can generate in a single response.",
+ "context-length": "Context length",
+ "context-length-hint": "Defines the size of the context window in tokens. This value sets the total memory limit for the model, including both the user's input and the generated response.",
"endpoint": "Endpoint",
"endpoint-required": "Endpoint is required.",
+ "baseurl": "Base URL",
+ "baseurl-required": "Base URL is required.",
"service-version": "Service version",
"check-connectivity": "Check connectivity",
"check-connectivity-success": "Test request was successful",
"check-connectivity-failed": "Test request failed",
"no-model-matching": "No models matching '{{entity}}' were found.",
"model-required": "Model is required.",
- "no-model-text": "No models found."
+ "no-model-text": "No models found.",
+ "authentication": "Authentication",
+ "authentication-basic-hint": "Uses standard HTTP Basic authentication. The username and password will be combined, Base64-encoded, and sent in an \"Authorization\" header with each request to the Ollama server.",
+ "authentication-token-hint": "Uses Bearer token authentication. The provided token will be sent directly in an \"Authorization\" eader with each request to the Ollama server.",
+ "authentication-type": {
+ "none": "None",
+ "basic": "Basic",
+ "token": "Token"
+ },
+ "username": "Username",
+ "username-required": "Username is required.",
+ "password": "Password",
+ "password-required": "Password is required.",
+ "token": "Token",
+ "token-required": "Token is required."
},
"confirm-on-exit": {
"message": "You have unsaved changes. Are you sure you want to leave this page?",
@@ -4488,7 +4507,8 @@
"jks": "JKS",
"js-module": "JS module",
"lwm2m-model": "LWM2M model",
- "pkcs-12": "PKCS #12"
+ "pkcs-12": "PKCS #12",
+ "general": "General"
},
"resource-sub-type": "Sub-type",
"sub-type": {
@@ -4496,7 +4516,12 @@
"scada-symbol": "Scada symbol",
"extension": "Extension",
"module": "Module"
- }
+ },
+ "resource-is-in-use": "Resource is used by other entities",
+ "resources-are-in-use": "Resources are used by other entities",
+ "resource-is-in-use-text": "The Resource '{{title}}' was not deleted because it is used by the following entities:",
+ "resources-are-in-use-text": "Not all Resources have been deleted because they are used by other entities.You can view referenced entities by clicking the References button in the corresponding resource row.If you still want to delete these resources, select them in the table below and click the Delete selected button.",
+ "delete-resource-in-use-text": "If you still want to delete the resource, click the Delete anyway button."
},
"javascript": {
"add": "Add JavaScript resource",
@@ -5467,7 +5492,8 @@
"timeout-required": "Timeout is required",
"timeout-validation": "Must be from 1 second to 10 minutes.",
"force-acknowledgement": "Force acknowledgement",
- "force-acknowledgement-hint": "If enabled, the incoming message is acknowledged immediately. The model's response is then enqueued as a separate, new message."
+ "force-acknowledgement-hint": "If enabled, the incoming message is acknowledged immediately. The model's response is then enqueued as a separate, new message.",
+ "ai-resources": "AI resources"
}
},
"timezone": {