feat(editor): Add "Rendered" display mode to the logs view (#14994)

This commit is contained in:
Suguru Inoue
2025-04-30 11:36:28 +02:00
committed by GitHub
parent 1de95ead0d
commit c0b54832b3
22 changed files with 587 additions and 294 deletions

View File

@@ -95,6 +95,7 @@ import ViewSubExecution from './ViewSubExecution.vue';
import RunDataItemCount from '@/components/RunDataItemCount.vue';
import RunDataDisplayModeSelect from '@/components/RunDataDisplayModeSelect.vue';
import RunDataPaginationBar from '@/components/RunDataPaginationBar.vue';
import { parseAiContent } from '@/utils/aiUtils';
const LazyRunDataTable = defineAsyncComponent(
async () => await import('@/components/RunDataTable.vue'),
@@ -109,6 +110,9 @@ const LazyRunDataSchema = defineAsyncComponent(
const LazyRunDataHtml = defineAsyncComponent(
async () => await import('@/components/RunDataHtml.vue'),
);
const LazyRunDataAi = defineAsyncComponent(
async () => await import('@/components/RunDataParsedAiContent.vue'),
);
const LazyRunDataSearch = defineAsyncComponent(
async () => await import('@/components/RunDataSearch.vue'),
);
@@ -125,6 +129,7 @@ type Props = {
executingMessage: string;
pushRef?: string;
paneType: NodePanelType;
displayMode: IRunDataDisplayMode;
noDataInBranchMessage: string;
node?: INodeUi | null;
nodes?: IConnectedNode[];
@@ -145,6 +150,7 @@ type Props = {
compact?: boolean;
tableHeaderBgColor?: 'base' | 'light';
disableHoverHighlight?: boolean;
disableAiContent?: boolean;
};
const props = withDefaults(defineProps<Props>(), {
@@ -166,6 +172,7 @@ const props = withDefaults(defineProps<Props>(), {
compact: false,
tableHeaderBgColor: 'base',
workflowExecution: undefined,
disableAiContent: false,
});
defineSlots<{
@@ -198,6 +205,7 @@ const emit = defineEmits<{
avgRowHeight: number;
},
];
displayModeChange: [IRunDataDisplayMode];
}>();
const connectionType = ref<NodeConnectionType>(NodeConnectionTypes.Main);
@@ -236,17 +244,12 @@ const node = toRef(props, 'node');
const pinnedData = usePinnedData(node, {
runIndex: props.runIndex,
displayMode:
props.paneType === 'input' ? ndvStore.inputPanelDisplayMode : ndvStore.outputPanelDisplayMode,
displayMode: props.displayMode,
});
const { isSubNodeType } = useNodeType({
node,
});
const displayMode = computed(() =>
props.paneType === 'input' ? ndvStore.inputPanelDisplayMode : ndvStore.outputPanelDisplayMode,
);
const isReadOnlyRoute = computed(() => route.meta.readOnlyCanvas === true);
const isWaitNodeWaiting = computed(() => {
return (
@@ -263,7 +266,7 @@ const nodeType = computed(() => {
return nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion);
});
const isSchemaView = computed(() => displayMode.value === 'schema');
const isSchemaView = computed(() => props.displayMode === 'schema');
const isSearchInSchemaView = computed(() => isSchemaView.value && !!search.value);
const hasMultipleInputNodes = computed(() => props.paneType === 'input' && props.nodes.length > 0);
const displaysMultipleNodes = computed(() => isSchemaView.value && hasMultipleInputNodes.value);
@@ -617,6 +620,14 @@ const itemsCountProps = computed<InstanceType<typeof RunDataItemCount>['$props']
subExecutionsCount: activeTaskMetadata.value?.subExecutionsCount,
}));
const parsedAiContent = computed(() =>
props.disableAiContent ? [] : parseAiContent(rawInputData.value, connectionType.value),
);
const hasParsedAiContent = computed(() =>
parsedAiContent.value.some((prr) => prr.parsedContent?.parsed),
);
function setInputBranchIndex(value: number) {
if (props.paneType === 'input') {
outputIndex.value = value;
@@ -659,9 +670,9 @@ watch(jsonData, (data: IDataObject[], prevData: IDataObject[]) => {
});
watch(binaryData, (newData, prevData) => {
if (newData.length && !prevData.length && displayMode.value !== 'binary') {
if (newData.length && !prevData.length && props.displayMode !== 'binary') {
switchToBinary();
} else if (!newData.length && displayMode.value === 'binary') {
} else if (!newData.length && props.displayMode === 'binary') {
onDisplayModeChange('table');
}
});
@@ -677,6 +688,17 @@ watch(search, (newSearch) => {
emit('search', newSearch);
});
// Switch to AI display mode if it's most suitable
watch(
hasParsedAiContent,
(hasAiContent) => {
if (hasAiContent && props.displayMode !== 'ai') {
emit('displayModeChange', 'ai');
}
},
{ immediate: true },
);
onMounted(() => {
init();
@@ -869,7 +891,7 @@ function enterEditMode({ origin }: EnterEditModeArgs) {
push_ref: props.pushRef,
run_index: props.runIndex,
is_output_present: hasNodeRun.value || pinnedData.hasData.value,
view: !hasNodeRun.value && !pinnedData.hasData.value ? 'undefined' : displayMode.value,
view: !hasNodeRun.value && !pinnedData.hasData.value ? 'undefined' : props.displayMode,
is_data_pinned: pinnedData.hasData.value,
});
}
@@ -912,7 +934,7 @@ function onExitEditMode({ type }: { type: 'save' | 'cancel' }) {
node_type: activeNode.value?.type,
push_ref: props.pushRef,
run_index: props.runIndex,
view: displayMode.value,
view: props.displayMode,
type,
});
}
@@ -927,7 +949,7 @@ async function onTogglePinData({ source }: { source: PinDataSource | UnpinDataSo
node_type: activeNode.value?.type,
push_ref: props.pushRef,
run_index: props.runIndex,
view: !hasNodeRun.value && !pinnedData.hasData.value ? 'none' : displayMode.value,
view: !hasNodeRun.value && !pinnedData.hasData.value ? 'none' : props.displayMode,
};
void externalHooks.run('runData.onTogglePinData', telemetryPayload);
@@ -1046,8 +1068,8 @@ function onPageSizeChange(newPageSize: number) {
}
function onDisplayModeChange(newDisplayMode: IRunDataDisplayMode) {
const previous = displayMode.value;
ndvStore.setPanelDisplayMode({ pane: props.paneType, mode: newDisplayMode });
const previous = props.displayMode;
emit('displayModeChange', newDisplayMode);
if (!userEnabledShowData.value) updateShowData();
@@ -1193,15 +1215,9 @@ function init() {
}
connectionType.value = outputTypes.length === 0 ? NodeConnectionTypes.Main : outputTypes[0];
if (binaryData.value.length > 0) {
ndvStore.setPanelDisplayMode({
pane: props.paneType,
mode: 'binary',
});
} else if (displayMode.value === 'binary') {
ndvStore.setPanelDisplayMode({
pane: props.paneType,
mode: 'schema',
});
emit('displayModeChange', 'binary');
} else if (props.displayMode === 'binary') {
emit('displayModeChange', 'schema');
}
}
@@ -1317,10 +1333,7 @@ function setDisplayMode() {
activeNode.value.parameters.operation === 'generateHtmlTemplate';
if (shouldDisplayHtml) {
ndvStore.setPanelDisplayMode({
pane: 'output',
mode: 'html',
});
emit('displayModeChange', 'html');
}
}
@@ -1426,6 +1439,7 @@ defineExpose({ enterEditMode });
activeNode?.type === HTML_NODE_TYPE &&
activeNode.parameters.operation === 'generateHtmlTemplate'
"
:has-renderable-data="hasParsedAiContent"
@change="onDisplayModeChange"
/>
@@ -1625,7 +1639,12 @@ defineExpose({ enterEditMode });
"
:class="$style.stretchVertically"
>
<NodeErrorView :error="subworkflowExecutionError" :class="$style.errorDisplay" />
<NodeErrorView
:compact="compact"
:error="subworkflowExecutionError"
:class="$style.errorDisplay"
show-details
/>
</div>
<div v-else-if="isWaitNodeWaiting" :class="$style.center">
@@ -1687,7 +1706,7 @@ defineExpose({ enterEditMode });
v-if="workflowRunErrorAsNodeError"
:error="workflowRunErrorAsNodeError"
:class="$style.inlineError"
compact
:compact="compact"
/>
<slot name="content"></slot>
</div>
@@ -1695,6 +1714,8 @@ defineExpose({ enterEditMode });
v-else-if="workflowRunErrorAsNodeError"
:error="workflowRunErrorAsNodeError"
:class="$style.dataDisplay"
:compact="compact"
show-details
/>
</div>
@@ -1835,6 +1856,10 @@ defineExpose({ enterEditMode });
<LazyRunDataHtml :input-html="inputHtml" />
</Suspense>
<Suspense v-else-if="hasNodeRun && displayMode === 'ai'">
<LazyRunDataAi render-type="rendered" :compact="compact" :content="parsedAiContent" />
</Suspense>
<Suspense v-else-if="(hasNodeRun || hasPreviewSchema) && isSchemaView">
<LazyRunDataSchema
:nodes="nodes"