feat: bring back UI

This commit is contained in:
Mutasem Aldmour 2024-11-07 12:43:26 +01:00
parent 67e09e2988
commit 152ca97719
No known key found for this signature in database
GPG key ID: 3DFA8122BB7FD6B8
8 changed files with 132 additions and 29 deletions

View file

@ -182,6 +182,7 @@ export interface IAiDataContent {
metadata: {
executionTime: number;
startTime: number;
executionId?: string;
};
}

View file

@ -58,12 +58,13 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useNodeType } from '@/composables/useNodeType';
import { useToast } from '@/composables/useToast';
import { isEqual, isObject } from 'lodash-es';
import { get, isEqual, isObject } from 'lodash-es';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useRootStore } from '@/stores/root.store';
import RunDataPinButton from '@/components/RunDataPinButton.vue';
import { getGenericHints } from '@/utils/nodeViewUtils';
import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
const LazyRunDataTable = defineAsyncComponent(
async () => await import('@/components/RunDataTable.vue'),
@ -180,6 +181,7 @@ export default defineComponent({
const { isSubNodeType } = useNodeType({
node,
});
const { openExecutionInNewTab } = useExecutionHelpers();
return {
...useToast(),
@ -187,6 +189,7 @@ export default defineComponent({
nodeHelpers,
pinnedData,
isSubNodeType,
openExecutionInNewTab,
};
},
data() {
@ -220,6 +223,43 @@ export default defineComponent({
useSourceControlStore,
useRootStore,
),
subWorkflowData(): { executionId: string; workflowId?: string } | null {
if (!this.node) {
return null;
}
const metadata = get(this.workflowRunData, [this.node.name, this.runIndex, 'metadata'], null);
if (metadata?.executionId) {
return {
executionId: metadata?.executionId,
workflowId: metadata?.workflowId,
};
}
return null;
},
itemSubWorkflowData(): Array<{
metadata?: { executionId?: string; workflowId?: string };
itemIndex: number;
}> {
const items = this.getData(this.runIndex, this.currentOutputIndex);
return items
.map((item, itemIndex) => {
return {
metadata: item.metadata,
itemIndex,
};
})
.filter((item) => {
return !!item.metadata;
});
},
hasInputOverwrite(): boolean {
if (!this.node) {
return false;
}
const taskData = this.nodeHelpers.getNodeTaskData(this.node, this.runIndex);
if (taskData === null) return false;
return !!taskData.inputOverride;
},
isReadOnlyRoute() {
return this.$route?.meta?.readOnlyCanvas === true;
},
@ -654,6 +694,15 @@ export default defineComponent({
this.hidePinDataDiscoveryTooltip();
},
methods: {
getData(
runIndex: number,
outputIndex: number,
connectionType: NodeConnectionType = NodeConnectionType.Main,
) {
const rawInputData = this.getRawInputData(runIndex, outputIndex, connectionType);
const pinOrLiveData = this.getPinDataOrLiveData(rawInputData);
return this.getFilteredData(pinOrLiveData);
},
getResolvedNodeOutputs() {
if (this.node && this.nodeType) {
const workflowNode = this.workflow.getNode(this.node.name);
@ -1383,6 +1432,32 @@ export default defineComponent({
<n8n-text size="small" v-n8n-html="hint.message"></n8n-text>
</n8n-callout>
<div :class="$style.execution">
<n8n-button
v-if="subWorkflowData && !(paneType === 'input' && hasInputOverwrite)"
:label="`Open ${
nodeType?.group?.includes('trigger') ? 'parent' : 'sub'
} execution ${subWorkflowData.executionId}`"
type="secondary"
@click.stop="openExecutionInNewTab(subWorkflowData.executionId, subWorkflowData.workflowId)"
/>
<div v-if="itemSubWorkflowData.length">
<n8n-select
size="small"
:class="$style.longSelect"
model-value="Select exectuion"
@change="openExecutionInNewTab($event.metadata.executionId, $event.metadata.workflowId)"
>
<n8n-option
v-for="subWorkflow in itemSubWorkflowData"
:key="subWorkflow.metadata?.executionId"
:value="subWorkflow"
:label="`item#${subWorkflow.itemIndex} exec#${subWorkflow.metadata?.executionId}`"
/>
</n8n-select>
</div>
</div>
<div
v-if="maxOutputIndex > 0 && branches.length > 1 && !displaysMultipleNodes"
:class="$style.outputs"

View file

@ -61,6 +61,7 @@ function getReferencedData(
metadata: {
executionTime: taskData.executionTime,
startTime: taskData.startTime,
executionId: taskData.metadata?.executionId,
},
});
});

View file

@ -11,6 +11,7 @@ import type {
import { computed } from 'vue';
import NodeIcon from '@/components/NodeIcon.vue';
import AiRunContentBlock from './AiRunContentBlock.vue';
import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
interface RunMeta {
startTimeMs: number;
@ -18,6 +19,7 @@ interface RunMeta {
node: INodeTypeDescription | null;
type: 'input' | 'output';
connectionType: NodeConnectionType;
executionId?: string;
}
const props = defineProps<{
inputData: IAiData;
@ -27,6 +29,8 @@ const props = defineProps<{
const nodeTypesStore = useNodeTypesStore();
const workflowsStore = useWorkflowsStore();
const { openExecutionInNewTab } = useExecutionHelpers();
type TokenUsageData = {
completionTokens: number;
promptTokens: number;
@ -75,6 +79,7 @@ function extractRunMeta(run: IAiDataContent) {
node: nodeType,
type: run.inOut,
connectionType: run.type,
executionId: run.metadata.executionId,
};
return runMeta;
@ -131,6 +136,11 @@ const outputError = computed(() => {
}}
</n8n-tooltip>
</li>
<li v-if="runMeta?.executionId">
<a @click="openExecutionInNewTab(runMeta?.executionId)"
>EXECUTION ID {{ runMeta?.executionId }}</a
>
</li>
<li v-if="(consumedTokensSum?.totalTokens ?? 0) > 0" :class="$style.tokensUsage">
{{
$locale.baseText('runData.aiContentBlock.tokens', {

View file

@ -12,7 +12,6 @@ import {
CHAT_TRIGGER_NODE_TYPE,
MANUAL_CHAT_TRIGGER_NODE_TYPE,
MODAL_CONFIRM,
VIEWS,
WORKFLOW_LM_CHAT_MODAL_KEY,
} from '@/constants';
@ -58,6 +57,7 @@ import { usePinnedData } from '@/composables/usePinnedData';
import { get, last } from 'lodash-es';
import { isEmpty } from '@/utils/typesUtils';
import { chatEventBus } from '@n8n/chat/event-buses';
import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
const LazyRunDataAi = defineAsyncComponent(
async () => await import('@/components/RunDataAi/RunDataAi.vue'),
@ -82,6 +82,8 @@ const workflowsStore = useWorkflowsStore();
const nodeTypesStore = useNodeTypesStore();
const uiStore = useUIStore();
const executionHelpers = useExecutionHelpers();
const { showError } = useToast();
const messages: Ref<ChatMessage[]> = ref([]);
const currentSessionId = ref<string>(String(Date.now()));
@ -428,11 +430,7 @@ async function sendMessage(message: string, files?: File[]) {
}
function displayExecution(executionId: string) {
const route = router.resolve({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: workflow.value.id, executionId },
});
window.open(route.href, '_blank');
executionHelpers.openExecutionInNewTab(executionId, workflow.value.id);
}
function isTextMessage(message: ChatMessage): message is ChatMessageText {
return message.type === 'text' || !message.type;

View file

@ -2,7 +2,7 @@
import { ref, computed, useCssModule } from 'vue';
import type { ExecutionSummary } from 'n8n-workflow';
import { useI18n } from '@/composables/useI18n';
import { VIEWS, WAIT_TIME_UNLIMITED } from '@/constants';
import { WAIT_TIME_UNLIMITED } from '@/constants';
import { useRouter } from 'vue-router';
import { convertToDisplayDate } from '@/utils/formatters/dateFormatter';
import { i18n as locale } from '@/plugins/i18n';
@ -138,11 +138,7 @@ function formatDate(fullDate: Date | string | number) {
}
function displayExecution() {
const route = router.resolve({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: props.execution.workflowId, executionId: props.execution.id },
});
window.open(route.href, '_blank');
executionHelpers.openExecutionInNewTab(props.execution.id, props.execution.workflowId);
}
function onStopExecution() {

View file

@ -1,6 +1,8 @@
import type { ExecutionSummary } from 'n8n-workflow';
import { convertToDisplayDate } from '@/utils/formatters/dateFormatter';
import { useI18n } from '@/composables/useI18n';
import { useRouter } from 'vue-router';
import { VIEWS } from '@/constants';
export interface IExecutionUIData {
name: string;
@ -14,6 +16,7 @@ export interface IExecutionUIData {
export function useExecutionHelpers() {
const i18n = useI18n();
const router = useRouter();
function getUIDetails(execution: ExecutionSummary): IExecutionUIData {
const status = {
@ -69,9 +72,18 @@ export function useExecutionHelpers() {
return ['crashed', 'error'].includes(execution.status) && !execution.retrySuccessId;
}
function openExecutionInNewTab(executionId: string, workflowId?: string) {
const route = router.resolve({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: workflowId, executionId },
});
window.open(route.href, '_blank');
}
return {
getUIDetails,
formatDate,
isExecutionRetriable,
openExecutionInNewTab,
};
}

View file

@ -565,6 +565,29 @@ export function useNodeHelpers() {
}
}
function getNodeTaskData(node: INodeUi | null, runIndex = 0) {
if (node === null) {
return null;
}
if (workflowsStore.getWorkflowExecution === null) {
return null;
}
const executionData = workflowsStore.getWorkflowExecution.data;
if (!executionData?.resultData) {
// unknown status
return null;
}
const runData = executionData.resultData.runData;
const taskData = get(runData, [node.name, runIndex]);
if (!taskData) {
return null;
}
return taskData;
}
function getNodeInputData(
node: INodeUi | null,
runIndex = 0,
@ -582,22 +605,8 @@ export function useNodeHelpers() {
runIndex = runIndex - 1;
}
if (node === null) {
return [];
}
if (workflowsStore.getWorkflowExecution === null) {
return [];
}
const executionData = workflowsStore.getWorkflowExecution.data;
if (!executionData?.resultData) {
// unknown status
return [];
}
const runData = executionData.resultData.runData;
const taskData = get(runData, [node.name, runIndex]);
if (!taskData) {
const taskData = getNodeTaskData(node, runIndex);
if (taskData === null) {
return [];
}
@ -1281,5 +1290,6 @@ export function useNodeHelpers() {
removeConnectionByConnectionInfo,
addPinDataConnections,
removePinDataConnections,
getNodeTaskData,
};
}