diff --git a/packages/@n8n/chat/src/components/ChatFile.vue b/packages/@n8n/chat/src/components/ChatFile.vue
index d3c3c2db7d..52b4acf789 100644
--- a/packages/@n8n/chat/src/components/ChatFile.vue
+++ b/packages/@n8n/chat/src/components/ChatFile.vue
@@ -30,22 +30,23 @@ const TypeIcon = computed(() => {
});
function onClick() {
- if (props.isRemovable) {
- emit('remove', props.file);
- }
-
if (props.isPreviewable) {
window.open(URL.createObjectURL(props.file));
}
}
+function onDelete() {
+ emit('remove', props.file);
+}
{{ file.name }}
-
-
+
+
+
+
@@ -80,12 +81,25 @@ function onClick() {
.chat-file-preview {
background: none;
border: none;
- display: none;
+ display: block;
cursor: pointer;
flex-shrink: 0;
+}
- .chat-file:hover & {
- display: block;
+.chat-file-delete {
+ position: relative;
+ &:hover {
+ color: red;
+ }
+
+ /* Increase hit area for better clickability */
+ &:before {
+ content: '';
+ position: absolute;
+ top: -10px;
+ right: -10px;
+ bottom: -10px;
+ left: -10px;
}
}
diff --git a/packages/@n8n/chat/src/components/Input.vue b/packages/@n8n/chat/src/components/Input.vue
index 545d9aa1ed..2dfd6e754f 100644
--- a/packages/@n8n/chat/src/components/Input.vue
+++ b/packages/@n8n/chat/src/components/Input.vue
@@ -1,18 +1,28 @@
@@ -20,7 +23,12 @@ defineProps<{
diff --git a/packages/editor-ui/src/components/CanvasChat/components/ChatMessagesPanel.vue b/packages/editor-ui/src/components/CanvasChat/components/ChatMessagesPanel.vue
index e90299009f..cf7604ccbc 100644
--- a/packages/editor-ui/src/components/CanvasChat/components/ChatMessagesPanel.vue
+++ b/packages/editor-ui/src/components/CanvasChat/components/ChatMessagesPanel.vue
@@ -9,7 +9,7 @@ import type { ArrowKeyDownPayload } from '@n8n/chat/components/Input.vue';
import ChatInput from '@n8n/chat/components/Input.vue';
import { useMessage } from '@/composables/useMessage';
import { MODAL_CONFIRM } from '@/constants';
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
import { useClipboard } from '@/composables/useClipboard';
import { useToast } from '@/composables/useToast';
@@ -20,6 +20,7 @@ interface Props {
}
const props = defineProps();
+
const emit = defineEmits<{
displayExecution: [id: string];
sendMessage: [message: string];
@@ -33,6 +34,12 @@ const toast = useToast();
const previousMessageIndex = ref(0);
+const inputPlaceholder = computed(() => {
+ if (props.messages.length > 0) {
+ return locale.baseText('chat.window.chat.placeholder');
+ }
+ return locale.baseText('chat.window.chat.placeholderPristine');
+});
/** Checks if message is a text message */
function isTextMessage(message: ChatMessage): message is ChatMessageText {
return message.type === 'text' || !message.type;
@@ -61,11 +68,11 @@ async function onRefreshSession() {
}
const confirmResult = await messageComposable.confirm(
- 'Are you sure you want to refresh the session? This will clear all messages and current execution data.',
+ locale.baseText('chat.window.session.reset.warning'),
{
- title: 'Confirm to refresh the session',
+ title: locale.baseText('chat.window.session.reset.title'),
type: 'warning',
- confirmButtonText: 'Refresh Session',
+ confirmButtonText: locale.baseText('chat.window.session.reset.confirm'),
showClose: true,
},
);
@@ -115,9 +122,9 @@ function copySessionId() {
- Chat
+ {{ locale.baseText('chat.window.title') }}
- Session
+ {{ locale.baseText('chat.window.session.title') }}
{{ sessionId }}
@@ -130,7 +137,7 @@ function copySessionId() {
text
size="mini"
icon="redo"
- title="Refresh session"
+ :title="locale.baseText('chat.window.session.reset.confirm')"
@click="onRefreshSession"
/>
@@ -186,7 +193,11 @@ function copySessionId() {
@click="onArrowKeyDown({ currentInputValue: '', key: 'ArrowDown' })"
/>
-
+
@@ -202,7 +213,7 @@ function copySessionId() {
--chat--message--user--color: var(--color-text-dark);
--chat--message--bot--border: none;
--chat--message--user--border: none;
- --chat--color-typing: var(--color-text-dark);
+ --chat--color-typing: var(--color-text-light);
--chat--textarea--max-height: calc(var(--panel-height) * 0.5);
height: 100%;
@@ -228,10 +239,10 @@ function copySessionId() {
align-items: center;
gap: var(--spacing-2xs);
color: var(--color-text-base);
+ max-width: 70%;
}
.sessionId {
display: inline-block;
- max-width: 5rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@@ -260,17 +271,19 @@ function copySessionId() {
}
.messagesInput {
- --input-border-color: transparent;
+ --input-border-color: var(--border-color-base);
--chat--input--border: none;
- --chat--input--border-radius: 2rem;
+ --chat--input--border-radius: 0.5rem;
--chat--input--send--button--background: transparent;
- --chat--input--send--button--color: var(--color-button-secondary-font);
- // --chat--input--send--button--color-hover: var(--color-primary);
--chat--input--send--button--color: var(--color-primary);
+ --chat--input--file--button--background: transparent;
+ --chat--input--file--button--color: var(--color-primary);
--chat--input--border-active: var(--input-focus-border-color, var(--color-secondary));
--chat--files-spacing: var(--spacing-2xs) 0;
--chat--input--background: transparent;
+ --chat--input--file--button--color: var(--color-button-secondary-font);
+ --chat--input--file--button--color-hover: var(--color-primary);
[data-theme='dark'] & {
--chat--input--text-color: var(--input-font-color, var(--color-text-dark));
@@ -280,7 +293,7 @@ function copySessionId() {
}
padding: 0 0 0 var(--spacing-xs);
- margin: var(--chat--spacing);
+ margin: 0 var(--chat--spacing) var(--chat--spacing);
flex-grow: 1;
display: flex;
background: var(--color-lm-chat-bot-background);
diff --git a/packages/editor-ui/src/components/CanvasChat/components/MessageOptionAction.vue b/packages/editor-ui/src/components/CanvasChat/components/MessageOptionAction.vue
index 23346446b2..bf1d855136 100644
--- a/packages/editor-ui/src/components/CanvasChat/components/MessageOptionAction.vue
+++ b/packages/editor-ui/src/components/CanvasChat/components/MessageOptionAction.vue
@@ -2,17 +2,14 @@
import type { PropType } from 'vue';
defineProps({
- /** Tooltip label */
label: {
type: String,
required: true,
},
- /** Icon name */
icon: {
type: String,
required: true,
},
- /** Placement of the tooltip */
placement: {
type: String as PropType<'left' | 'right' | 'top' | 'bottom'>,
default: 'top',
diff --git a/packages/editor-ui/src/components/CanvasChat/components/MessageOptionTooltip.vue b/packages/editor-ui/src/components/CanvasChat/components/MessageOptionTooltip.vue
index e443eb45dc..5a346fd929 100644
--- a/packages/editor-ui/src/components/CanvasChat/components/MessageOptionTooltip.vue
+++ b/packages/editor-ui/src/components/CanvasChat/components/MessageOptionTooltip.vue
@@ -2,7 +2,6 @@
import type { PropType } from 'vue';
defineProps({
- /** Placement of the tooltip */
placement: {
type: String as PropType<'left' | 'right' | 'top' | 'bottom'>,
default: 'top',
diff --git a/packages/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts b/packages/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts
index c0267bc908..753229fd7c 100644
--- a/packages/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts
+++ b/packages/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts
@@ -29,7 +29,7 @@ export function useChatTrigger({ router }: { router: ReturnType
- chatTriggerName.value ? workflowsStore.getNodeByName(chatTriggerName.value) : undefined,
+ chatTriggerName.value ? workflowsStore.getNodeByName(chatTriggerName.value) : null,
);
const allowFileUploads = computed(() => {
@@ -66,8 +66,6 @@ export function useChatTrigger({ router }: { router: ReturnType) {
minHeight: ref(0),
maxHeight: ref(0),
chat: ref(0), // Chat panel width
+ logs: ref(0),
height: ref(0),
};
@@ -36,6 +37,16 @@ export function useResize(container: Ref) {
'--chat-width': `${dimensions.chat.value}px`,
}));
+ const panelToContainerRatio = computed(() => {
+ const chatRatio = dimensions.chat.value / dimensions.container.value;
+ const containerRatio = dimensions.container.value / window.screen.width;
+ return {
+ chat: chatRatio.toFixed(2),
+ logs: (1 - chatRatio).toFixed(2),
+ container: containerRatio.toFixed(2),
+ };
+ });
+
/**
* Constrains height to min/max bounds and updates panel height
*/
@@ -57,6 +68,7 @@ export function useResize(container: Ref) {
const minWidth = containerWidth * MIN_WIDTH_PERCENTAGE;
dimensions.chat.value = Math.min(Math.max(width, minWidth), maxWidth);
+ dimensions.logs.value = dimensions.container.value - dimensions.chat.value;
}
function onResizeChatDebounced(data: ResizeData) {
@@ -115,10 +127,12 @@ export function useResize(container: Ref) {
return {
height: dimensions.height,
chatWidth: dimensions.chat,
+ logsWidth: dimensions.logs,
rootStyles,
onWindowResize,
onResizeDebounced,
onResizeChatDebounced,
// onResizeChat,
+ panelToContainerRatio,
};
}
diff --git a/packages/editor-ui/src/components/RunDataAi/RunDataAi.vue b/packages/editor-ui/src/components/RunDataAi/RunDataAi.vue
index d2b2e0799b..d58669fa24 100644
--- a/packages/editor-ui/src/components/RunDataAi/RunDataAi.vue
+++ b/packages/editor-ui/src/components/RunDataAi/RunDataAi.vue
@@ -25,7 +25,6 @@ interface TreeNode {
export interface Props {
node: INodeUi;
runIndex?: number;
- hideTitle?: boolean;
slim?: boolean;
workflow: Workflow;
}
@@ -309,7 +308,7 @@ watch(() => props.runIndex, selectFirst, { immediate: true });
}
.tree {
flex-shrink: 0;
- min-width: 12.8rem;
+ min-width: 8rem;
height: 100%;
padding-right: var(--spacing-xs);
@@ -364,7 +363,6 @@ watch(() => props.runIndex, selectFirst, { immediate: true });
display: inline-flex;
border-radius: var(--border-radius-base);
align-items: center;
- // gap: var(--spacing-3xs);
padding-right: var(--spacing-3xs);
margin: var(--spacing-4xs) 0;
font-size: var(--font-size-2xs);
diff --git a/packages/editor-ui/src/components/WorkflowLMChat/MessageOptionAction.vue b/packages/editor-ui/src/components/WorkflowLMChat/MessageOptionAction.vue
deleted file mode 100644
index 69bb16737d..0000000000
--- a/packages/editor-ui/src/components/WorkflowLMChat/MessageOptionAction.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
- {{ label }}
-
-
-
-
-
diff --git a/packages/editor-ui/src/components/WorkflowLMChat/MessageOptionTooltip.vue b/packages/editor-ui/src/components/WorkflowLMChat/MessageOptionTooltip.vue
deleted file mode 100644
index 008993fee5..0000000000
--- a/packages/editor-ui/src/components/WorkflowLMChat/MessageOptionTooltip.vue
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/editor-ui/src/components/WorkflowLMChat/WorkflowLMChat.vue b/packages/editor-ui/src/components/WorkflowLMChat/WorkflowLMChat.vue
deleted file mode 100644
index e76ab8241a..0000000000
--- a/packages/editor-ui/src/components/WorkflowLMChat/WorkflowLMChat.vue
+++ /dev/null
@@ -1,699 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{ locale.baseText('chat.window.chat.chatMessageOptions.executionId') }}:
- {{ message.id }}
-
-
-
-
-
-
-
-
-
{{
- locale.baseText('chat.window.logs')
- }}
-
-
-
-
-
-
-
-
-
- {{ locale.baseText('chatEmbed.infoTip.description') }}
-
- {{ locale.baseText('chatEmbed.infoTip.link') }}
-
-
-
-
-
-
-
-
diff --git a/packages/editor-ui/src/composables/useRunWorkflow.ts b/packages/editor-ui/src/composables/useRunWorkflow.ts
index d8cb68f4d8..230a95a803 100644
--- a/packages/editor-ui/src/composables/useRunWorkflow.ts
+++ b/packages/editor-ui/src/composables/useRunWorkflow.ts
@@ -171,7 +171,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType {
const isDragging = ref(false);
const lastSelectedConnection = ref();
const newNodeInsertPosition = ref(null);
- const isChatPanelOpen = ref(false);
- const isLogsPanelOpen = ref(false);
const panelHeight = ref(0);
const nodes = computed(() => workflowStore.allNodes);
@@ -326,17 +324,6 @@ export const useCanvasStore = defineStore('canvas', () => {
watch(readOnlyEnv, setReadOnly);
- function setPanelOpen(panel: 'chat' | 'logs', isOpen: boolean) {
- if (panel === 'chat') {
- isChatPanelOpen.value = isOpen;
- isLogsPanelOpen.value = isOpen;
- }
-
- if (panel === 'logs') {
- isLogsPanelOpen.value = isOpen;
- }
- }
-
function setPanelHeight(height: number) {
panelHeight.value = height;
}
@@ -350,11 +337,8 @@ export const useCanvasStore = defineStore('canvas', () => {
isLoading: loadingService.isLoading,
aiNodes,
lastSelectedConnection: lastSelectedConnectionComputed,
- isChatPanelOpen: computed(() => isChatPanelOpen.value),
- isLogsPanelOpen: computed(() => isLogsPanelOpen.value),
panelHeight: computed(() => panelHeight.value),
setPanelHeight,
- setPanelOpen,
setReadOnly,
setLastSelectedConnection,
startLoading: loadingService.startLoading,
diff --git a/packages/editor-ui/src/stores/workflows.store.ts b/packages/editor-ui/src/stores/workflows.store.ts
index 0d5bb872f5..a56a251f1c 100644
--- a/packages/editor-ui/src/stores/workflows.store.ts
+++ b/packages/editor-ui/src/stores/workflows.store.ts
@@ -139,6 +139,8 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
const nodeMetadata = ref({});
const isInDebugMode = ref(false);
const chatMessages = ref([]);
+ const isChatPanelOpen = ref(false);
+ const isLogsPanelOpen = ref(false);
const workflowName = computed(() => workflow.value.name);
@@ -1119,6 +1121,11 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
const { [node.name]: removedNodeMetadata, ...remainingNodeMetadata } = nodeMetadata.value;
nodeMetadata.value = remainingNodeMetadata;
+ // If chat trigger node is removed, close chat
+ if (node.type === CHAT_TRIGGER_NODE_TYPE) {
+ setPanelOpen('chat', false);
+ }
+
if (workflow.value.pinData && workflow.value.pinData.hasOwnProperty(node.name)) {
const { [node.name]: removedPinData, ...remainingPinData } = workflow.value.pinData;
workflow.value = {
@@ -1617,6 +1624,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
// End Canvas V2 Functions
//
+ function setPanelOpen(panel: 'chat' | 'logs', isOpen: boolean) {
+ if (panel === 'chat') {
+ isChatPanelOpen.value = isOpen;
+ }
+ // Logs panel open/close is tied to the chat panel open/close
+ isLogsPanelOpen.value = isOpen;
+ }
+
return {
workflow,
usedCredentials,
@@ -1661,6 +1676,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
getWorkflowExecution,
getTotalFinishedExecutionsCount,
getPastChatMessages,
+ isChatPanelOpen: computed(() => isChatPanelOpen.value),
+ isLogsPanelOpen: computed(() => isLogsPanelOpen.value),
+ setPanelOpen,
outgoingConnectionsByNodeName,
incomingConnectionsByNodeName,
nodeHasOutputConnection,
diff --git a/packages/editor-ui/src/views/NodeView.v2.vue b/packages/editor-ui/src/views/NodeView.v2.vue
index b7ffb60ff8..a1d2411150 100644
--- a/packages/editor-ui/src/views/NodeView.v2.vue
+++ b/packages/editor-ui/src/views/NodeView.v2.vue
@@ -248,7 +248,7 @@ const keyBindingsEnabled = computed(() => {
return !ndvStore.activeNode && uiStore.activeModals.length === 0;
});
-const isChatOpen = computed(() => canvasStore.isChatPanelOpen);
+const isChatOpen = computed(() => workflowsStore.isChatPanelOpen);
/**
* Initialization
@@ -1205,7 +1205,7 @@ const chatTriggerNodePinnedData = computed(() => {
});
async function onOpenChat() {
- canvasStore.setPanelOpen('chat', !canvasStore.isChatPanelOpen);
+ workflowsStore.setPanelOpen('chat', !workflowsStore.isChatPanelOpen);
const payload = {
workflow_id: workflowId.value,
diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue
index 3041fe830d..7eecfd597b 100644
--- a/packages/editor-ui/src/views/NodeView.vue
+++ b/packages/editor-ui/src/views/NodeView.vue
@@ -459,9 +459,6 @@ export default defineComponent({
return this.containsChatNodes && this.triggerNodes.length === 1 && !this.pinnedChatNodeData;
},
- canvasChatNode() {
- return this.nodes.find((node) => node.type === CHAT_TRIGGER_NODE_TYPE);
- },
pinnedChatNodeData() {
if (!this.canvasChatNode) return null;
@@ -513,7 +510,7 @@ export default defineComponent({
return getResourcePermissions(project?.scopes);
},
isChatOpen() {
- return this.canvasStore.isChatPanelOpen;
+ return this.workflowsStore.isChatPanelOpen;
},
},
watch: {
@@ -865,7 +862,7 @@ export default defineComponent({
};
this.$telemetry.track('User clicked chat open button', telemetryPayload);
void this.externalHooks.run('nodeView.onOpenChat', telemetryPayload);
- this.canvasStore.setPanelOpen('chat', !this.canvasStore.isChatPanelOpen);
+ this.workflowsStore.setPanelOpen('chat', !this.workflowsStore.isChatPanelOpen);
},
async onRunWorkflow() {