301 lines
10 KiB
HTML
301 lines
10 KiB
HTML
<div class="llm-node"
|
|
[class.llm-node--human]="isHumanNode()"
|
|
[class.llm-node-skipped]="isSkipped() && !hasExecutionErrors() && !hasExecutionWarnings()"
|
|
[class.llm-node-running]="isRunning() && !hasExecutionErrors()"
|
|
[class.llm-node-completed]="isCompleted() && !needsAttention() && !hasExecutionErrors() && !hasExecutionWarnings()"
|
|
[class.llm-node-attention]="needsAttention()"
|
|
[class.llm-node-error]="hasExecutionErrors()"
|
|
[class.llm-node-warning]="hasExecutionWarnings() && !hasExecutionErrors()">
|
|
@if (needsAttention() && isHumanNode()) {
|
|
<div class="llm-attention-wrap" (pointerdown)="$event.stopPropagation()" (click)="$event.stopPropagation()">
|
|
<button
|
|
type="button"
|
|
class="llm-attention-trigger"
|
|
aria-label="Open waiting step details"
|
|
[disabled]="interactionSubmitting"
|
|
(click)="openInteractionModal($event)">
|
|
<i class="bi bi-hand-index-thumb-fill"></i>
|
|
</button>
|
|
</div>
|
|
}
|
|
@if (hasExecutionErrors()) {
|
|
<div class="llm-error-alert-wrap">
|
|
<div class="llm-error-alert">
|
|
<i class="bi bi-exclamation-triangle-fill"></i>
|
|
</div>
|
|
<div class="llm-error-tooltip">
|
|
<div class="llm-error-title">Execution errors</div>
|
|
@for (error of executionErrors(); track $index) {
|
|
<div class="llm-error-item">{{ error }}</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
@if (hasExecutionWarnings()) {
|
|
<div class="llm-warning-alert-wrap">
|
|
<div class="llm-warning-alert">
|
|
<i class="bi bi-exclamation-circle-fill"></i>
|
|
</div>
|
|
<div class="llm-warning-list-tooltip">
|
|
<div class="llm-warning-list-title">Execution warnings</div>
|
|
@for (warning of executionWarnings(); track $index) {
|
|
<div class="llm-warning-list-item">{{ warning }}</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="llm-header">
|
|
<div class="llm-icon">
|
|
@if (isHumanNode()) {
|
|
<i class="bi bi-person-check-fill"></i>
|
|
} @else {
|
|
<img src="llm_node.png" alt="LLM" class="llm-icon-image" />
|
|
}
|
|
</div>
|
|
<div class="llm-header-content">
|
|
<span class="llm-title">{{ nodeTitle() }}</span>
|
|
<div class="llm-subtitle-row">
|
|
<span class="llm-subtitle">{{ name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-body">
|
|
<div class="llm-column">
|
|
<div class="llm-column-title">Inputs</div>
|
|
@for (input of inputs; track input.key) {
|
|
<div class="llm-row llm-row-input">
|
|
@if (isInputConnected(input.key)) {
|
|
<span class="llm-socket-wrap llm-socket-wrap-input">
|
|
<div
|
|
refComponent
|
|
class="llm-socket"
|
|
[data]="{
|
|
type: 'socket',
|
|
side: 'input',
|
|
key: input.key,
|
|
nodeId: data.id,
|
|
__readonly: true,
|
|
payload: input.socket
|
|
}"
|
|
[emit]="emit">
|
|
</div>
|
|
@if (showInputTooltip() && executionInputTooltip(input.key); as inputTooltip) {
|
|
<span class="llm-io-tooltip llm-io-tooltip-input">{{ inputTooltip }}</span>
|
|
}
|
|
</span>
|
|
} @else {
|
|
<span class="llm-input-value-wrap" (pointerdown)="$event.stopPropagation()" (click)="$event.stopPropagation()">
|
|
<button
|
|
type="button"
|
|
class="llm-input-value-icon"
|
|
aria-label="Show input value">
|
|
<i class="bi bi-box-arrow-in-right"></i>
|
|
</button>
|
|
@if (showInputTooltip()) {
|
|
<span class="llm-io-tooltip llm-io-tooltip-input">{{ inputValueTooltip(input.key) }}</span>
|
|
}
|
|
</span>
|
|
}
|
|
<span class="llm-pill llm-pill-input">
|
|
<span class="llm-pill-meta">
|
|
@if (inputDisplayLabelParts(input.key).context; as context) {
|
|
<span class="llm-pill-context">{{ context }}</span>
|
|
}
|
|
<span class="llm-pill-name">{{ inputDisplayLabelParts(input.key).name }}</span>
|
|
<span class="llm-pill-kind">{{ inputKindLabel(input.key) }}</span>
|
|
</span>
|
|
</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="llm-column">
|
|
<div class="llm-column-title llm-column-title-right">{{ outputsTitle() }}</div>
|
|
@for (output of outputs; track output.key) {
|
|
<div class="llm-row llm-row-output">
|
|
<span class="llm-pill llm-pill-output" [ngClass]="outputPillClass(output.key)">
|
|
<span class="llm-pill-meta llm-pill-meta-output">
|
|
@if (outputDisplayLabelParts(output.key).context; as context) {
|
|
<span class="llm-pill-context">{{ context }}</span>
|
|
}
|
|
<span class="llm-pill-name">{{ outputDisplayLabelParts(output.key).name }}</span>
|
|
<span class="llm-pill-kind">{{ outputKindLabel(output.key) }}</span>
|
|
</span>
|
|
</span>
|
|
@if (isOutputConnected(output.key)) {
|
|
<span class="llm-socket-wrap llm-socket-wrap-output">
|
|
<div
|
|
refComponent
|
|
class="llm-socket llm-socket-right"
|
|
[data]="{
|
|
type: 'socket',
|
|
side: 'output',
|
|
key: output.key,
|
|
nodeId: data.id,
|
|
__readonly: true,
|
|
payload: output.socket
|
|
}"
|
|
[emit]="emit">
|
|
</div>
|
|
@if (showOutputTooltip() && executionOutputTooltip(output.key); as outputTooltip) {
|
|
<span class="llm-io-tooltip llm-io-tooltip-output">{{ outputTooltip }}</span>
|
|
}
|
|
</span>
|
|
} @else {
|
|
<span class="llm-output-value-wrap" (pointerdown)="$event.stopPropagation()" (click)="$event.stopPropagation()">
|
|
<button
|
|
type="button"
|
|
class="llm-output-value-icon"
|
|
aria-label="Show output result">
|
|
<i class="bi bi-box-arrow-right"></i>
|
|
</button>
|
|
@if (showOutputTooltip()) {
|
|
<span class="llm-io-tooltip llm-io-tooltip-output">{{ outputValueTooltip(output.key) }}</span>
|
|
}
|
|
</span>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-params llm-params-shell">
|
|
@if (!schemaReady) {
|
|
<div class="llm-skeleton-overlay" aria-hidden="true">
|
|
<div class="llm-skeleton-stack">
|
|
<div class="llm-skeleton-line llm-skeleton-line-title"></div>
|
|
<div class="llm-skeleton-line llm-skeleton-line-short"></div>
|
|
<div class="llm-skeleton-grid">
|
|
<div class="llm-skeleton-pill"></div>
|
|
<div class="llm-skeleton-pill"></div>
|
|
<div class="llm-skeleton-pill llm-skeleton-pill-wide"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
@if (parameterFieldGroups.length) {
|
|
<div class="llm-param-groups">
|
|
@for (group of parameterFieldGroups; track group.key) {
|
|
<fieldset class="llm-param-fieldset">
|
|
<legend class="llm-param-legend">{{ group.legend }}</legend>
|
|
<div class="llm-param-grid">
|
|
@for (field of group.fields; track field.path) {
|
|
<div class="llm-param-chip" [class.llm-param-chip-wide]="field.wide">
|
|
<div class="llm-param-row-head">
|
|
<span class="llm-param-key">{{ field.label }}</span>
|
|
@if (field.expandable) {
|
|
<button
|
|
type="button"
|
|
class="llm-param-view-btn"
|
|
aria-label="View full value"
|
|
(pointerdown)="$event.stopPropagation()"
|
|
(click)="openFieldPreview(field, $event)">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
}
|
|
</div>
|
|
<span
|
|
class="llm-param-value"
|
|
[class.llm-param-value-wide]="field.wide"
|
|
[class.llm-param-value-clamped]="field.expandable">
|
|
{{ field.value }}
|
|
</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
</fieldset>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@if (parameterFields.length) {
|
|
<div class="llm-param-grid">
|
|
@for (field of parameterFields; track field.path) {
|
|
<div class="llm-param-chip" [class.llm-param-chip-wide]="field.wide">
|
|
<div class="llm-param-row-head">
|
|
<span class="llm-param-key">{{ field.label }}</span>
|
|
@if (field.expandable) {
|
|
<button
|
|
type="button"
|
|
class="llm-param-view-btn"
|
|
aria-label="View full value"
|
|
(pointerdown)="$event.stopPropagation()"
|
|
(click)="openFieldPreview(field, $event)">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
}
|
|
</div>
|
|
<span
|
|
class="llm-param-value"
|
|
[class.llm-param-value-wide]="field.wide"
|
|
[class.llm-param-value-clamped]="field.expandable">
|
|
{{ field.value }}
|
|
</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@if (hasMainContent()) {
|
|
@for (contentField of mainContentFields; track contentField.path) {
|
|
<div class="llm-param-block">
|
|
<div class="llm-param-row-head">
|
|
<div class="llm-param-key">{{ contentField.label }}</div>
|
|
@if (contentField.expandable) {
|
|
<button
|
|
type="button"
|
|
class="llm-param-view-btn"
|
|
aria-label="View full content"
|
|
(pointerdown)="$event.stopPropagation()"
|
|
(click)="openMainContentPreview(contentField, $event)">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
}
|
|
</div>
|
|
<div class="llm-param-text" [class.llm-param-text-clamped]="contentField.expandable">
|
|
@for (part of contentField.parts; track $index) {
|
|
@if (part.isDynamicInput) {
|
|
<span class="llm-inline-token">{{ formatDynamicInputToken(part.text) }}</span>
|
|
} @else {
|
|
<span>{{ part.text }}</span>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
@if (arrayFields.length) {
|
|
<div class="llm-array-sections">
|
|
@for (arrayField of arrayFields; track arrayField.path) {
|
|
<div class="llm-array-block">
|
|
<div class="llm-param-row-head">
|
|
<div class="llm-param-key">{{ arrayField.label }}</div>
|
|
</div>
|
|
@if (!arrayField.items.length) {
|
|
<div class="llm-array-empty">No items</div>
|
|
} @else {
|
|
@for (item of arrayField.items; track item.index) {
|
|
<div class="llm-array-item">
|
|
<span class="llm-array-item-summary">{{ item.summary }}</span>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@if (hasViewableSubflow()) {
|
|
<div class="llm-subflow-section">
|
|
<button type="button" class="llm-subflow-view" (pointerdown)="$event.stopPropagation()" (click)="openSubflowPreview($event)">
|
|
View Subflow
|
|
</button>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
</div>
|