look and feel in the longtext parmeter changed
This commit is contained in:
parent
55418d2226
commit
3bf669180d
|
|
@ -64,6 +64,10 @@ export class BlocksService {
|
|||
return blockTypes.find((blockType) => blockType.type === typeName);
|
||||
}
|
||||
|
||||
peekBlockType(typeName: BlockTypeName) {
|
||||
return this._blockTypes().find((blockType) => blockType.type === typeName) ?? null;
|
||||
}
|
||||
|
||||
createEmptyBlock(blockType: BlockTypeName, context?: BlockDraftContext) {
|
||||
const flowId = typeof context?.flowId === 'string' && context.flowId.trim().length > 0 ? context.flowId.trim() : '';
|
||||
const cacheKey = `${String(blockType)}::${flowId}`;
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ export class ContainersService {
|
|||
return containerTypes.find((containerType) => containerType.type === typeName);
|
||||
}
|
||||
|
||||
peekContainerType(typeName: BlockTypeName) {
|
||||
return this._containerTypes().find((containerType) => containerType.type === typeName) ?? null;
|
||||
}
|
||||
|
||||
createEmptyContainer(containerType: BlockTypeName) {
|
||||
const cacheKey = String(containerType);
|
||||
const cached = this.emptyContainerCache.get(cacheKey);
|
||||
|
|
|
|||
|
|
@ -423,6 +423,16 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.container-node__param-value--clamped {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.container-node__param-edit {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
|
|
@ -445,6 +455,7 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
.container-node__dropzone {
|
||||
margin: 0 14px 14px;
|
||||
min-height: 128px;
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@
|
|||
</button>
|
||||
}
|
||||
</div>
|
||||
<span class="container-node__param-value">{{ field.type === 'boolean' ? (field.booleanValue ? 'Enabled' : 'Disabled') : field.value }}</span>
|
||||
<span class="container-node__param-value" [class.container-node__param-value--clamped]="field.expandable">{{ field.type === 'boolean' ? (field.booleanValue ? 'Enabled' : 'Disabled') : field.value }}</span>
|
||||
</div>
|
||||
}
|
||||
@for (contentField of richContentFields; track contentField.path) {
|
||||
|
|
@ -148,7 +148,7 @@
|
|||
<div class="container-node__param-head">
|
||||
<span class="container-node__param-key">{{ contentField.label }}</span>
|
||||
</div>
|
||||
<div class="container-node__param-value">
|
||||
<div class="container-node__param-value" [class.container-node__param-value--clamped]="contentField.expandable">
|
||||
@for (part of contentField.parts; track $index) {
|
||||
@if (part.isDynamicInput) {
|
||||
<span class="llm-inline-token">{{ formatDynamicInputToken(part.text) }}</span>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ type ContainerFieldView = {
|
|||
label: string;
|
||||
value: string;
|
||||
wide: boolean;
|
||||
expandable: boolean;
|
||||
enabled: boolean;
|
||||
type: ContainerFieldType;
|
||||
booleanValue: boolean;
|
||||
|
|
@ -45,6 +46,8 @@ type ContainerFieldView = {
|
|||
type RichContentView = {
|
||||
path: string;
|
||||
label: string;
|
||||
rawValue: string;
|
||||
expandable: boolean;
|
||||
parts: { text: string; isDynamicInput: boolean }[];
|
||||
};
|
||||
|
||||
|
|
@ -569,7 +572,7 @@ export class ContainerNodeComponent {
|
|||
|
||||
private async loadSchemaContext() {
|
||||
try {
|
||||
const containerType = await this.containersService.getContainerType(this.typeName);
|
||||
const containerType = this.containersService.peekContainerType(this.typeName) ?? await this.containersService.getContainerType(this.typeName);
|
||||
this.containerSchema = (containerType?.schema ?? null) as Record<string, any> | null;
|
||||
this.schemaRequirements = extractSchemaRequirements(this.containerSchema);
|
||||
this.containerFieldDefinitions = this.buildContainerFieldDefinitions(this.containerSchema);
|
||||
|
|
@ -602,11 +605,16 @@ export class ContainerNodeComponent {
|
|||
const richContentPaths = new Set(this.richContentPaths());
|
||||
this.richContentFields = this.richContentPaths()
|
||||
.filter((path) => this.isFieldVisible(path))
|
||||
.map((path) => ({
|
||||
path,
|
||||
label: this.containerFieldDefinitions.find((field) => field.path === path)?.label ?? pathToLabel(path),
|
||||
parts: this.toRichContentParts(path)
|
||||
}))
|
||||
.map((path) => {
|
||||
const rawValue = String(getValueByPath(config, path) ?? '');
|
||||
return {
|
||||
path,
|
||||
label: this.containerFieldDefinitions.find((field) => field.path === path)?.label ?? pathToLabel(path),
|
||||
rawValue,
|
||||
expandable: this.isLongTextValue(rawValue),
|
||||
parts: this.toRichContentParts(path)
|
||||
};
|
||||
})
|
||||
.filter((field) => field.parts.length > 0);
|
||||
|
||||
this.parameterFields = this.containerFieldDefinitions
|
||||
|
|
@ -617,6 +625,7 @@ export class ContainerNodeComponent {
|
|||
label: field.label,
|
||||
value: valueToDisplayString(getValueByPath(config, field.path)),
|
||||
wide: field.widget === 'textarea' || field.label.length >= 18,
|
||||
expandable: this.isLongTextValue(valueToDisplayString(getValueByPath(config, field.path))),
|
||||
enabled: this.isFieldEnabled(field.path),
|
||||
type: field.type,
|
||||
booleanValue: getValueByPath(config, field.path) === true
|
||||
|
|
@ -775,6 +784,10 @@ export class ContainerNodeComponent {
|
|||
.filter((option: NodeSettingOption | null): option is NodeSettingOption => option != null);
|
||||
}
|
||||
|
||||
private isLongTextValue(value: string): boolean {
|
||||
return String(value ?? '').trim().length > 80;
|
||||
}
|
||||
|
||||
private getEditorInitialValue(definition: ContainerFieldDefinition): string | boolean {
|
||||
const raw = getValueByPath(this.configuration ?? {}, definition.path);
|
||||
if (definition.type === 'boolean') return raw === true;
|
||||
|
|
|
|||
|
|
@ -897,6 +897,14 @@
|
|||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.llm-param-text-clamped {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.llm-inline-token {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -1004,6 +1012,17 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.llm-param-value-clamped {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
|
||||
.llm-modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@
|
|||
</button>
|
||||
}
|
||||
</div>
|
||||
<span class="llm-param-value">{{ field.type === 'boolean' ? (field.booleanValue ? 'Enabled' : 'Disabled') : field.value }}</span>
|
||||
<span class="llm-param-value" [class.llm-param-value-clamped]="field.expandable">{{ field.type === 'boolean' ? (field.booleanValue ? 'Enabled' : 'Disabled') : field.value }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
@ -242,7 +242,7 @@
|
|||
</button>
|
||||
}
|
||||
</div>
|
||||
<span class="llm-param-value">{{ field.type === 'boolean' ? (field.booleanValue ? 'Enabled' : 'Disabled') : field.value }}</span>
|
||||
<span class="llm-param-value" [class.llm-param-value-clamped]="field.expandable">{{ field.type === 'boolean' ? (field.booleanValue ? 'Enabled' : 'Disabled') : field.value }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
@ -259,7 +259,7 @@
|
|||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="llm-param-text">
|
||||
<div class="llm-param-text" [class.llm-param-text-clamped]="contentField.expandable">
|
||||
@if (!contentField.parts.length) {
|
||||
-
|
||||
} @else {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ type EditableFieldView = {
|
|||
label: string;
|
||||
value: string;
|
||||
wide: boolean;
|
||||
expandable: boolean;
|
||||
enabled: boolean;
|
||||
type: FieldType;
|
||||
booleanValue: boolean;
|
||||
|
|
@ -124,6 +125,8 @@ type EditableFieldGroupView = {
|
|||
type RichContentView = {
|
||||
path: string;
|
||||
label: string;
|
||||
rawValue: string;
|
||||
expandable: boolean;
|
||||
parts: { text: string; isDynamicInput: boolean }[];
|
||||
};
|
||||
|
||||
|
|
@ -545,7 +548,7 @@ export class GenericNodeComponent {
|
|||
}
|
||||
|
||||
try {
|
||||
const blockType = await this.blocksService.getBlockType(type);
|
||||
const blockType = this.blocksService.peekBlockType(type) ?? await this.blocksService.getBlockType(type);
|
||||
this.blockDescriptor = blockType ?? null;
|
||||
this.blockSchema = (blockType?.schema ?? null) as Record<string, any> | null;
|
||||
this.schemaRequirements = extractSchemaRequirements(this.blockSchema);
|
||||
|
|
@ -1078,6 +1081,10 @@ export class GenericNodeComponent {
|
|||
.filter((option): option is NodeSettingOption => option != null);
|
||||
}
|
||||
|
||||
private isLongTextValue(value: string): boolean {
|
||||
return String(value ?? '').trim().length > 80;
|
||||
}
|
||||
|
||||
private refreshParameterFields() {
|
||||
const config = this.blockConfiguration ?? {};
|
||||
const richContentPaths = new Set(this.richContentPaths());
|
||||
|
|
@ -1086,11 +1093,16 @@ export class GenericNodeComponent {
|
|||
|
||||
this.richContentFields = this.richContentPaths()
|
||||
.filter((path) => this.isPathVisible(path))
|
||||
.map((path) => ({
|
||||
path,
|
||||
label: this.fieldDisplayLabel(path),
|
||||
parts: this.toRichContentParts(path)
|
||||
}));
|
||||
.map((path) => {
|
||||
const rawValue = String(this.getByPath(config, path) ?? '');
|
||||
return {
|
||||
path,
|
||||
label: this.fieldDisplayLabel(path),
|
||||
rawValue,
|
||||
expandable: this.isLongTextValue(rawValue),
|
||||
parts: this.toRichContentParts(path)
|
||||
};
|
||||
});
|
||||
|
||||
this.arrayFields = this.arrayFieldDefinitions
|
||||
.filter((definition) => this.isPathVisible(definition.path))
|
||||
|
|
@ -1108,6 +1120,7 @@ export class GenericNodeComponent {
|
|||
label: definition.label,
|
||||
value: this.fieldDisplayValue(definition, value),
|
||||
wide: this.shouldRenderWideField(definition.label, definition.ui.widget === 'textarea'),
|
||||
expandable: this.isLongTextValue(this.fieldDisplayValue(definition, value)),
|
||||
enabled: this.isPathEnabled(definition.path),
|
||||
type: definition.type,
|
||||
booleanValue: value === true
|
||||
|
|
@ -1145,6 +1158,7 @@ export class GenericNodeComponent {
|
|||
label: this.fieldDisplayLabel(entry.path),
|
||||
value: valueToDisplayString(entry.value),
|
||||
wide: this.shouldRenderWideField(this.fieldDisplayLabel(entry.path), false),
|
||||
expandable: this.isLongTextValue(valueToDisplayString(entry.value)),
|
||||
enabled: this.isPathEnabled(entry.path),
|
||||
type: (typeof entry.value === 'boolean' ? 'boolean' : 'unknown') as FieldType,
|
||||
booleanValue: entry.value === true
|
||||
|
|
|
|||
|
|
@ -543,9 +543,14 @@ export class TaskStepNodeComponent {
|
|||
const nodeFamily = this.data?.data?.nodeFamily === 'container'
|
||||
? 'container'
|
||||
: 'block';
|
||||
const typeDescriptor = nodeFamily === 'container'
|
||||
? await this.containersService.getContainerType(type)
|
||||
: await this.blocksService.getBlockType(type);
|
||||
const cachedDescriptor = nodeFamily === 'container'
|
||||
? this.containersService.peekContainerType(type)
|
||||
: this.blocksService.peekBlockType(type);
|
||||
const typeDescriptor = cachedDescriptor ?? (
|
||||
nodeFamily === 'container'
|
||||
? await this.containersService.getContainerType(type)
|
||||
: await this.blocksService.getBlockType(type)
|
||||
);
|
||||
this.blockDescriptor = (typeDescriptor ?? null) as BlockType | null;
|
||||
this.blockSchema = (typeDescriptor?.schema ?? null) as Record<string, any> | null;
|
||||
const typeKey = this.nodeTypeCacheKey();
|
||||
|
|
@ -1076,7 +1081,7 @@ export class TaskStepNodeComponent {
|
|||
const normalized = String(value ?? '');
|
||||
if (!normalized.trim()) return false;
|
||||
const lineCount = normalized.split(/\r?\n/).length;
|
||||
return lineCount > 2 || normalized.length > 160;
|
||||
return lineCount > 2 || normalized.length > 80;
|
||||
}
|
||||
|
||||
private async openReadonlyTextDialog(label: string, value: string) {
|
||||
|
|
|
|||
|
|
@ -351,17 +351,49 @@ export class ReteEditor implements OnChanges, OnDestroy {
|
|||
const currentNode = currentNodes.get(String(nextNode.id));
|
||||
if (!currentNode?.data) continue;
|
||||
|
||||
currentNode.data = {
|
||||
const nextReadonlyNode = {
|
||||
...currentNode.data,
|
||||
...nextNode,
|
||||
position: nextNode.position ?? currentNode.data.position,
|
||||
__readonly: true
|
||||
};
|
||||
|
||||
if (this.hasSameReadonlyNodePayload(currentNode.data as FlowNode & Record<string, unknown>, nextReadonlyNode as FlowNode & Record<string, unknown>)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
currentNode.data = nextReadonlyNode;
|
||||
|
||||
await rete.area.update('node', currentNode.id);
|
||||
}
|
||||
}
|
||||
|
||||
private hasSameReadonlyNodePayload(currentNode: FlowNode & Record<string, unknown>, nextNode: FlowNode & Record<string, unknown>) {
|
||||
return JSON.stringify({
|
||||
id: currentNode.id,
|
||||
name: currentNode.name,
|
||||
position: currentNode.position,
|
||||
inputs: currentNode.inputs,
|
||||
outputs: currentNode.outputs,
|
||||
specificConfiguration: currentNode.specificConfiguration,
|
||||
typeName: currentNode.typeName,
|
||||
userInteractive: currentNode['userInteractive'],
|
||||
nodeFamily: currentNode.nodeFamily,
|
||||
__readonly: currentNode['__readonly']
|
||||
}) === JSON.stringify({
|
||||
id: nextNode.id,
|
||||
name: nextNode.name,
|
||||
position: nextNode.position,
|
||||
inputs: nextNode.inputs,
|
||||
outputs: nextNode.outputs,
|
||||
specificConfiguration: nextNode.specificConfiguration,
|
||||
typeName: nextNode.typeName,
|
||||
userInteractive: nextNode['userInteractive'],
|
||||
nodeFamily: nextNode.nodeFamily,
|
||||
__readonly: nextNode['__readonly']
|
||||
});
|
||||
}
|
||||
|
||||
private getDropPosition(event: DragEvent) {
|
||||
const host = this.container.nativeElement as HTMLElement;
|
||||
const rect = host.getBoundingClientRect();
|
||||
|
|
|
|||
|
|
@ -202,16 +202,8 @@
|
|||
color: #991b1b;
|
||||
}
|
||||
|
||||
.execution-log-details {
|
||||
margin: 10px 0 0;
|
||||
border: 1px solid #dbe4ee;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
color: #0f172a;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
padding: 10px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
.execution-logs-scroll {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@
|
|||
(fileInputChange)="onFileInputChange($event.input, $event.files)">
|
||||
</app-task-execution-inputs-panel>
|
||||
} @else if (activeAsideTab() === 'logs') {
|
||||
<div class="p-3 space-y-3">
|
||||
<div #logsScrollViewport class="execution-logs-scroll p-3 space-y-3">
|
||||
@if (logsLoading()) {
|
||||
<div class="text-xs text-slate-500">Loading execution log...</div>
|
||||
} @else if (logsError()) {
|
||||
|
|
@ -184,9 +184,6 @@
|
|||
{{ event.levelText }}
|
||||
</span>
|
||||
</div>
|
||||
@if (event.details) {
|
||||
<pre class="execution-log-details">{{ event.details | json }}</pre>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, effect, inject, input, OnDestroy, signal } from '@angular/core';
|
||||
import { Component, computed, effect, ElementRef, inject, input, OnDestroy, signal, ViewChild } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
|
|
@ -84,6 +84,7 @@ export class TaskExecutionViewerComponent implements OnDestroy {
|
|||
readonly executionLogs = signal<ExecutionEventLogEntry[]>([]);
|
||||
readonly logsLoading = signal(false);
|
||||
readonly logsError = signal<string | null>(null);
|
||||
@ViewChild('logsScrollViewport') private logsScrollViewport?: ElementRef<HTMLDivElement>;
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
|
|
@ -141,6 +142,12 @@ export class TaskExecutionViewerComponent implements OnDestroy {
|
|||
onCleanup(() => clearInterval(timer));
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
this.executionLogs();
|
||||
this.logsLoading();
|
||||
queueMicrotask(() => this.scrollLogsToBottom());
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
const dialogState = this.humanInteractionDialog.state();
|
||||
const execution = this.execution();
|
||||
|
|
@ -732,6 +739,12 @@ export class TaskExecutionViewerComponent implements OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
private scrollLogsToBottom() {
|
||||
const element = this.logsScrollViewport?.nativeElement;
|
||||
if (!element || this.activeAsideTab() !== 'logs') return;
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
|
||||
private async loadSimulationOptions(
|
||||
key: string,
|
||||
context: Record<string, string>,
|
||||
|
|
|
|||
Loading…
Reference in New Issue