diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/CanvasChat.vue b/packages/frontend/editor-ui/src/components/CanvasChat/CanvasChat.vue
index 1ffaa825fe7..93ff52d5f12 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/CanvasChat.vue
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/CanvasChat.vue
@@ -1,5 +1,5 @@
diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/components/ChatMessagesPanel.vue b/packages/frontend/editor-ui/src/components/CanvasChat/components/ChatMessagesPanel.vue
index dffdf49f946..f21936909de 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/components/ChatMessagesPanel.vue
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/components/ChatMessagesPanel.vue
@@ -7,7 +7,7 @@ import MessageOptionAction from './MessageOptionAction.vue';
import { chatEventBus } from '@n8n/chat/event-buses';
import type { ArrowKeyDownPayload } from '@n8n/chat/components/Input.vue';
import ChatInput from '@n8n/chat/components/Input.vue';
-import { computed, ref } from 'vue';
+import { watch, computed, ref } from 'vue';
import { useClipboard } from '@/composables/useClipboard';
import { useToast } from '@/composables/useToast';
import PanelHeader from '@/components/CanvasChat/future/components/PanelHeader.vue';
@@ -136,6 +136,18 @@ async function copySessionId() {
type: 'success',
});
}
+
+watch(
+ () => props.isOpen,
+ (isOpen) => {
+ if (isOpen) {
+ setTimeout(() => {
+ chatEventBus.emit('focusInput');
+ }, 0);
+ }
+ },
+ { immediate: true },
+);
diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatState.ts b/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatState.ts
index 6edd8c341d5..0499d8d1d98 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatState.ts
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatState.ts
@@ -6,7 +6,6 @@ import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useRunWorkflow } from '@/composables/useRunWorkflow';
import { VIEWS } from '@/constants';
import { type INodeUi } from '@/Interface';
-import { useCanvasStore } from '@/stores/canvas.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { ChatOptionsSymbol, ChatSymbol } from '@n8n/chat/constants';
@@ -31,11 +30,10 @@ interface ChatState {
displayExecution: (executionId: string) => void;
}
-export function useChatState(isReadOnly: boolean, onWindowResize?: () => void): ChatState {
+export function useChatState(isReadOnly: boolean): ChatState {
const locale = useI18n();
const workflowsStore = useWorkflowsStore();
const nodeTypesStore = useNodeTypesStore();
- const canvasStore = useCanvasStore();
const router = useRouter();
const nodeHelpers = useNodeHelpers();
const { runWorkflow } = useRunWorkflow({ router });
@@ -44,25 +42,16 @@ export function useChatState(isReadOnly: boolean, onWindowResize?: () => void):
const currentSessionId = ref(uuid().replace(/-/g, ''));
const previousChatMessages = computed(() => workflowsStore.getPastChatMessages);
- const canvasNodes = computed(() => workflowsStore.allNodes);
- const allConnections = computed(() => workflowsStore.allConnections);
const logsPanelState = computed(() => workflowsStore.logsPanelState);
const workflow = computed(() => workflowsStore.getCurrentWorkflow());
// Initialize features with injected dependencies
- const {
- chatTriggerNode,
- connectedNode,
- allowFileUploads,
- allowedFilesMimeTypes,
- setChatTriggerNode,
- setConnectedNode,
- } = useChatTrigger({
- workflow,
- canvasNodes,
- getNodeByName: workflowsStore.getNodeByName,
- getNodeType: nodeTypesStore.getNodeType,
- });
+ const { chatTriggerNode, connectedNode, allowFileUploads, allowedFilesMimeTypes } =
+ useChatTrigger({
+ workflow,
+ getNodeByName: workflowsStore.getNodeByName,
+ getNodeType: nodeTypesStore.getNodeType,
+ });
const { sendMessage, isLoading } = useChatMessaging({
chatTrigger: chatTriggerNode,
@@ -134,37 +123,6 @@ export function useChatState(isReadOnly: boolean, onWindowResize?: () => void):
provide(ChatSymbol, chatConfig);
provide(ChatOptionsSymbol, chatOptions);
- // Watchers
- watch(
- () => logsPanelState.value,
- (state) => {
- if (state !== LOGS_PANEL_STATE.CLOSED) {
- setChatTriggerNode();
- setConnectedNode();
-
- setTimeout(() => {
- onWindowResize?.();
- chatEventBus.emit('focusInput');
- }, 0);
- }
- },
- { immediate: true },
- );
-
- watch(
- () => allConnections.value,
- () => {
- if (canvasStore.isLoading) return;
- setTimeout(() => {
- if (!chatTriggerNode.value) {
- setChatTriggerNode();
- }
- setConnectedNode();
- }, 0);
- },
- { deep: true },
- );
-
// This function creates a promise that resolves when the workflow execution completes
// It's used to handle the loading state while waiting for the workflow to finish
async function createExecutionPromise() {
diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts b/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts
index 9081608ba1e..33e20a8b748 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts
@@ -1,11 +1,11 @@
-import type { ComputedRef, MaybeRef } from 'vue';
-import { ref, computed, unref } from 'vue';
+import type { ComputedRef } from 'vue';
+import { computed } from 'vue';
import {
CHAIN_SUMMARIZATION_LANGCHAIN_NODE_TYPE,
NodeConnectionTypes,
NodeHelpers,
} from 'n8n-workflow';
-import type { INodeTypeDescription, Workflow, INode, INodeParameters } from 'n8n-workflow';
+import type { INodeTypeDescription, Workflow, INodeParameters } from 'n8n-workflow';
import {
AI_CATEGORY_AGENTS,
AI_CATEGORY_CHAINS,
@@ -18,21 +18,12 @@ import { isChatNode } from '@/components/CanvasChat/utils';
export interface ChatTriggerDependencies {
getNodeByName: (name: string) => INodeUi | null;
getNodeType: (type: string, version: number) => INodeTypeDescription | null;
- canvasNodes: MaybeRef;
workflow: ComputedRef;
}
-export function useChatTrigger({
- getNodeByName,
- getNodeType,
- canvasNodes,
- workflow,
-}: ChatTriggerDependencies) {
- const chatTriggerName = ref(null);
- const connectedNode = ref(null);
-
- const chatTriggerNode = computed(() =>
- chatTriggerName.value ? getNodeByName(chatTriggerName.value) : null,
+export function useChatTrigger({ getNodeByName, getNodeType, workflow }: ChatTriggerDependencies) {
+ const chatTriggerNode = computed(
+ () => Object.values(workflow.value.nodes).find(isChatNode) ?? null,
);
const allowFileUploads = computed(() => {
@@ -49,22 +40,12 @@ export function useChatTrigger({
);
});
- /** Gets the chat trigger node from the workflow */
- function setChatTriggerNode() {
- const triggerNode = unref(canvasNodes).find(isChatNode);
-
- if (!triggerNode) {
- return;
- }
- chatTriggerName.value = triggerNode.name;
- }
-
/** Sets the connected node after finding the trigger */
- function setConnectedNode() {
+ const connectedNode = computed(() => {
const triggerNode = chatTriggerNode.value;
if (!triggerNode) {
- return;
+ return null;
}
const chatChildren = workflow.value.getChildNodes(triggerNode.name);
@@ -121,15 +102,14 @@ export function useChatTrigger({
const result = Boolean(isChatChild && (isAgent || isChain || isCustomChainOrAgent));
return result;
});
- connectedNode.value = chatRootNode ?? null;
- }
+
+ return chatRootNode ?? null;
+ });
return {
allowFileUploads,
allowedFilesMimeTypes,
chatTriggerNode,
- connectedNode: computed(() => connectedNode.value),
- setChatTriggerNode,
- setConnectedNode,
+ connectedNode,
};
}
diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewRow.vue b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewRow.vue
index f9319fef3f7..e8d74c0ca73 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewRow.vue
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewRow.vue
@@ -146,6 +146,17 @@ function isLastChild(level: number) {
icon="exclamation-triangle"
:class="$style.compactErrorIcon"
/>
+
-
{
it('should generate one node per execution', () => {
@@ -608,3 +610,28 @@ describe(createLogEntries, () => {
]);
});
});
+
+describe(deepToRaw, () => {
+ it('should convert reactive fields to raw in data with circular structure', () => {
+ const data = reactive({
+ foo: reactive({ bar: {} }),
+ bazz: {},
+ });
+
+ data.foo.bar = data;
+ data.bazz = data;
+
+ const raw = deepToRaw(data);
+
+ expect(isReactive(data)).toBe(true);
+ expect(isReactive(data.foo)).toBe(true);
+ expect(isReactive(data.foo.bar)).toBe(true);
+ expect(isReactive(data.bazz)).toBe(true);
+ expect(isReactive(raw)).toBe(false);
+ expect(isReactive(raw.foo)).toBe(false);
+ expect(isReactive(raw.foo.bar)).toBe(false);
+ expect(isReactive(raw.bazz)).toBe(false);
+
+ console.log(raw.foo.bar);
+ });
+});
diff --git a/packages/frontend/editor-ui/src/components/RunDataAi/utils.ts b/packages/frontend/editor-ui/src/components/RunDataAi/utils.ts
index 063eb5836a0..b53b91e336f 100644
--- a/packages/frontend/editor-ui/src/components/RunDataAi/utils.ts
+++ b/packages/frontend/editor-ui/src/components/RunDataAi/utils.ts
@@ -431,8 +431,18 @@ export function findSelectedLogEntry(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function deepToRaw(sourceObj: T): T {
+ const seen = new WeakMap();
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const objectIterator = (input: any): any => {
+ if (seen.has(input)) {
+ return input;
+ }
+
+ if (input !== null && typeof input === 'object') {
+ seen.set(input, true);
+ }
+
if (Array.isArray(input)) {
return input.map((item) => objectIterator(item));
}
diff --git a/packages/frontend/editor-ui/src/composables/usePushConnection.ts b/packages/frontend/editor-ui/src/composables/usePushConnection.ts
index 329c6c0ceac..6999a77869c 100644
--- a/packages/frontend/editor-ui/src/composables/usePushConnection.ts
+++ b/packages/frontend/editor-ui/src/composables/usePushConnection.ts
@@ -505,6 +505,13 @@ export function usePushConnection({ router }: { router: ReturnType [
+ nodeName,
+ runs.filter((run) => run.executionStatus !== 'running'),
+ ])
+ .filter(([, runs]) => runs.length > 0),
+ ),
+ },
+ },
+ };
+}
diff --git a/packages/frontend/editor-ui/src/plugins/icons/custom.ts b/packages/frontend/editor-ui/src/plugins/icons/custom.ts
index 12ceb3bfe1b..208de16091d 100644
--- a/packages/frontend/editor-ui/src/plugins/icons/custom.ts
+++ b/packages/frontend/editor-ui/src/plugins/icons/custom.ts
@@ -157,18 +157,6 @@ export const faPopOut: IconDefinition = {
],
};
-export const faTable: IconDefinition = {
- prefix: 'fas',
- iconName: 'table' as IconName,
- icon: [
- 12,
- 12,
- [],
- '',
- 'M10.875 0C11.4844 0 12 0.589286 12 1.28571V10.7143C12 11.4375 11.4844 12 10.875 12H1.125C0.492188 12 0 11.4375 0 10.7143V1.28571C0 0.589286 0.492188 0 1.125 0H10.875ZM5.25 10.2857V7.71429H1.5V10.2857H5.25ZM5.25 6V3.42857H1.5V6H5.25ZM10.5 10.2857V7.71429H6.75V10.2857H10.5ZM10.5 6V3.42857H6.75V6H10.5Z',
- ],
-};
-
export const faSchema: IconDefinition = {
prefix: 'fas',
iconName: 'schema' as IconName,