Refine container rendering and assistant runtime config
This commit is contained in:
parent
b10e9c59e0
commit
d508ccf6d1
|
|
@ -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}`}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,14 @@
|
|||
</div>
|
||||
<div class="container-node__titles">
|
||||
<div class="container-node__eyebrow">{{ containerLabel }}</div>
|
||||
<div class="container-node__name">{{ name }}</div>
|
||||
<div class="container-node__name-row">
|
||||
<div class="container-node__name">{{ name }}</div>
|
||||
@if (!isReadonly) {
|
||||
<button type="button" class="container-node__name-edit" title="Edit name" (pointerdown)="$event.stopPropagation()" (click)="openNameEditor($event)">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-node__header-actions">
|
||||
@if (missingRequiredParams.length) {
|
||||
|
|
@ -42,6 +49,28 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if (nameEditorOpen) {
|
||||
<div class="llm-modal-backdrop" (pointerdown)="$event.stopPropagation()" (click)="cancelNameEditor($event)">
|
||||
<div class="llm-modal" (pointerdown)="$event.stopPropagation()" (click)="$event.stopPropagation()">
|
||||
<div class="llm-modal-title">Edit Name</div>
|
||||
<div class="llm-modal-field">
|
||||
<label>Name</label>
|
||||
<input
|
||||
type="text"
|
||||
maxlength="20"
|
||||
[ngModel]="draftName"
|
||||
(ngModelChange)="onDraftNameChange($event)"
|
||||
(keydown.enter)="saveNameEditor($event)"
|
||||
(pointerdown)="$event.stopPropagation()" />
|
||||
</div>
|
||||
<div class="llm-modal-actions">
|
||||
<button type="button" class="llm-btn llm-btn-ghost" (pointerdown)="$event.stopPropagation()" (click)="cancelNameEditor($event)">Cancel</button>
|
||||
<button type="button" class="llm-btn llm-btn-primary" [disabled]="!draftName.trim()" (pointerdown)="$event.stopPropagation()" (click)="saveNameEditor($event)">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="container-node__ports">
|
||||
<div class="container-node__port-column">
|
||||
<div class="container-node__port-title">Inputs</div>
|
||||
|
|
|
|||
|
|
@ -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<string, any>, 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 [];
|
||||
|
|
|
|||
|
|
@ -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[] {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue