From d508ccf6d1979cd05a3a07bc53d439407b969568 Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Fri, 27 Mar 2026 17:39:59 +0100 Subject: [PATCH] Refine container rendering and assistant runtime config --- src/app/services/assistant/assistant-call.ts | 9 +-- .../nodes/container-node/container-node.css | 26 +++++++ .../nodes/container-node/container-node.html | 31 +++++++- .../nodes/container-node/container-node.ts | 71 +++++++++++++++++-- .../nodes/task-step-node/task-step-node.ts | 15 +++- .../task-execution-viewer.css | 7 +- src/environments/environment.ts | 2 +- 7 files changed, 144 insertions(+), 17 deletions(-) diff --git a/src/app/services/assistant/assistant-call.ts b/src/app/services/assistant/assistant-call.ts index 2fb52c1..82bd518 100644 --- a/src/app/services/assistant/assistant-call.ts +++ b/src/app/services/assistant/assistant-call.ts @@ -224,13 +224,6 @@ function mapIntent(raw: unknown): AssistantIntent | null { function resolveAssistantUrl(url: string): string { if (!url) return url; if (/^https?:\/\//i.test(url)) return url; - const apiBase = environment.apiUrl; - if (/^https?:\/\//i.test(apiBase)) { - return new URL(url, `${apiBase.replace(/\/+$/, '')}/`).toString(); - } - - const normalizedBase = apiBase.startsWith('/') ? apiBase : `/${apiBase}`; - const origin = typeof window !== 'undefined' ? window.location.origin : ''; - return new URL(url, `${origin}${normalizedBase.replace(/\/+$/, '')}/`).toString(); + return `${apiBase}${url.startsWith('/') ? url : `/${url}`}`; } diff --git a/src/app/shared/nodes/container-node/container-node.css b/src/app/shared/nodes/container-node/container-node.css index eb52163..8a02870 100644 --- a/src/app/shared/nodes/container-node/container-node.css +++ b/src/app/shared/nodes/container-node/container-node.css @@ -153,6 +153,26 @@ text-overflow: ellipsis; } +.container-node__name-row { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} + +.container-node__name-edit { + border: 0; + width: 24px; + height: 24px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.16); + color: #f8fafc; + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; +} + .container-node__delete { border: 0; width: 30px; @@ -337,12 +357,18 @@ background: #fff7ed; color: #92400e; border-color: #fed7aa; + align-items: center; + justify-content: center; + text-align: center; } .container-node__port-label--dependency-output { background: #eff6ff; color: #1d4ed8; border-color: #bfdbfe; + align-items: center; + justify-content: center; + text-align: center; } .container-node__port-context { diff --git a/src/app/shared/nodes/container-node/container-node.html b/src/app/shared/nodes/container-node/container-node.html index e449da1..e01367f 100644 --- a/src/app/shared/nodes/container-node/container-node.html +++ b/src/app/shared/nodes/container-node/container-node.html @@ -8,7 +8,14 @@
{{ containerLabel }}
-
{{ name }}
+
+
{{ name }}
+ @if (!isReadonly) { + + } +
@if (missingRequiredParams.length) { @@ -42,6 +49,28 @@
+ @if (nameEditorOpen) { +
+
+
Edit Name
+
+ + +
+
+ + +
+
+
+ } +
Inputs
diff --git a/src/app/shared/nodes/container-node/container-node.ts b/src/app/shared/nodes/container-node/container-node.ts index 2e9bc34..e155317 100644 --- a/src/app/shared/nodes/container-node/container-node.ts +++ b/src/app/shared/nodes/container-node/container-node.ts @@ -1,5 +1,6 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectorRef, Component, HostBinding, Input, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { MatTooltipModule } from '@angular/material/tooltip'; import { currentFlowPortValueKind, flowValueKindLabel, FlowBlock, FlowContainer, FlowData, FLOW_DEPENDANT_PORT_KEY, FLOW_DEPENDENCY_PORT_KEY } from '@models/flow'; import { NodeSettingField, NodeSettingOption, NodeSettingsDialogService } from '@services/dialogs/node-settings-dialog'; @@ -62,7 +63,7 @@ type StructuredRetrieverConfig = { @Component({ selector: 'app-container-node', - imports: [CommonModule, ReteModule, MatTooltipModule], + imports: [CommonModule, FormsModule, ReteModule, MatTooltipModule], templateUrl: './container-node.html', styleUrl: './container-node.css', host: { @@ -86,6 +87,8 @@ export class ContainerNodeComponent { parameterFields: ContainerFieldView[] = []; richContentFields: RichContentView[] = []; schemaReady = false; + nameEditorOpen = false; + draftName = ''; @Input() data!: any; @Input() emit!: (data: any) => void; @@ -223,17 +226,22 @@ export class ContainerNodeComponent { return requiredFields .filter((field, index, fields) => fields.findIndex((candidate) => candidate.path === field.path) === index) - .filter((field) => field.path !== 'name' && field.path !== 'subFlow') + .filter((field) => field.path !== 'name') + .filter((field) => field.path !== 'subFlow') + .filter((field) => !field.path.startsWith('subFlow.')) .filter((field) => this.isFieldEnabled(field.path)) .filter((field) => this.isMissingValue(getValueByPath(config, field.path))) .map((field) => field.label) .concat( this.schemaRequirements.requiredObjects - .filter((field) => field.path !== 'name' && field.path !== 'subFlow') + .filter((field) => field.path !== 'name') + .filter((field) => field.path !== 'subFlow') + .filter((field) => !field.path.startsWith('subFlow.')) .filter((field) => this.isFieldEnabled(field.path)) .filter((field) => this.isMissingValue(getValueByPath(config, field.path))) .map((field) => field.label) ) + .concat(this.subFlow ? [] : ['Subflow']) .filter((field, index, fields) => fields.indexOf(field) === index); } @@ -353,6 +361,49 @@ export class ContainerNodeComponent { } } + openNameEditor(event?: Event) { + event?.preventDefault(); + event?.stopPropagation(); + if (this.isReadonly) return; + this.draftName = this.name; + this.nameEditorOpen = true; + } + + cancelNameEditor(event?: Event) { + event?.preventDefault(); + event?.stopPropagation(); + this.nameEditorOpen = false; + this.draftName = this.name; + } + + onDraftNameChange(value: string) { + this.draftName = value; + } + + saveNameEditor(event?: Event) { + event?.preventDefault(); + event?.stopPropagation(); + if (this.isReadonly) return; + + const nextName = this.draftName.trim().slice(0, 20); + if (!nextName || nextName === this.name) { + this.cancelNameEditor(); + return; + } + + const nextConfiguration = this.cloneConfiguration(); + nextConfiguration['name'] = nextName; + this.data.data = { + ...this.data.data, + name: nextName, + specificConfiguration: nextConfiguration + }; + this.updateCurrentFlowData(nextConfiguration); + this.refreshParameterFields(); + this.refreshView(); + this.nameEditorOpen = false; + } + async openParameterEditor(path: string, event?: Event) { event?.preventDefault(); event?.stopPropagation(); @@ -660,6 +711,7 @@ export class ContainerNodeComponent { .filter((field) => field.parts.length > 0); this.parameterFields = this.containerFieldDefinitions + .filter((field) => !this.isContainerTypeField(field.path)) .filter((field) => this.isFieldVisible(field.path)) .filter((field) => !richContentPaths.has(field.path)) .map((field) => ({ @@ -689,7 +741,7 @@ export class ContainerNodeComponent { const childResolved = resolveSchemaRef(childSchema as Record, schema); if (shouldSkipSchemaField(key, childResolved)) continue; const path = pathPrefix ? `${pathPrefix}.${key}` : key; - if (path === 'name' || path === 'subFlow') continue; + if (path === 'name' || path === 'subFlow' || this.isContainerTypeField(path)) continue; const hasChildren = !!childResolved?.properties || childResolved?.type === 'object'; if (hasChildren) { @@ -720,10 +772,21 @@ export class ContainerNodeComponent { private richContentPaths(): string[] { return this.containerFieldDefinitions + .filter((field) => !this.isContainerTypeField(field.path)) .filter((field) => field.widget === 'textarea') .map((field) => field.path); } + private isContainerTypeField(path: string): boolean { + return [ + 'type', + 'typeName', + 'containerType', + 'configurationType', + 'configurationClass' + ].some((key) => path === key || path.endsWith(`.${key}`)); + } + private toRichContentParts(path: string): { text: string; isDynamicInput: boolean }[] { const content = String(getValueByPath(this.configuration ?? {}, path) ?? '').trim(); if (!content) return []; diff --git a/src/app/shared/nodes/task-step-node/task-step-node.ts b/src/app/shared/nodes/task-step-node/task-step-node.ts index 694669c..fe6ccb6 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.ts +++ b/src/app/shared/nodes/task-step-node/task-step-node.ts @@ -890,7 +890,20 @@ export class TaskStepNodeComponent { } private shouldHideConfigPath(path: string): boolean { - return this.isContainerNode() && (path === 'subFlow' || path.startsWith('subFlow.')); + const isContainerTypePath = [ + 'type', + 'typeName', + 'containerType', + 'configurationType', + 'configurationClass' + ].some((key) => path === key || path.endsWith(`.${key}`)); + + return this.isContainerNode() + && ( + isContainerTypePath + || path === 'subFlow' + || path.startsWith('subFlow.') + ); } private toArrayFieldItems(definition: ArrayFieldDefinition, value: unknown): ArrayFieldItemView[] { diff --git a/src/app/shared/task-execution-viewer/task-execution-viewer.css b/src/app/shared/task-execution-viewer/task-execution-viewer.css index 96acd41..bc924fe 100644 --- a/src/app/shared/task-execution-viewer/task-execution-viewer.css +++ b/src/app/shared/task-execution-viewer/task-execution-viewer.css @@ -186,10 +186,13 @@ } .execution-log-icon { - width: 18px; - height: 18px; + flex: 0 0 auto; + width: 20px; + height: 20px; font-size: 18px; color: #475569; + line-height: 20px; + overflow: visible; margin-top: 1px; } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 8ad6e5e..9cdf58e 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -9,7 +9,7 @@ import { TaskExecutionsCallService } from "@services/task-executions/task-execut export const environment = { apiUrl: '/api', production: true, - assistantEnabled: false, + assistantEnabled: true, tourModeAlwaysOn: false, turnstileEnabled: true, authorizationCallService: AuthorizationCallService,