mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-26 20:02:26 -08:00
feat(editor): Retrieve previous chat message on arrow-up (#8696)
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
parent
a5e6f5928a
commit
246f8cfcc3
|
@ -1073,6 +1073,7 @@ export interface WorkflowsState {
|
||||||
workflowExecutionData: IExecutionResponse | null;
|
workflowExecutionData: IExecutionResponse | null;
|
||||||
workflowExecutionPairedItemMappings: { [itemId: string]: Set<string> };
|
workflowExecutionPairedItemMappings: { [itemId: string]: Set<string> };
|
||||||
workflowsById: IWorkflowsMap;
|
workflowsById: IWorkflowsMap;
|
||||||
|
chatMessages: string[];
|
||||||
isInDebugMode?: boolean;
|
isInDebugMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
</div>
|
</div>
|
||||||
<MessageTyping v-if="isLoading" ref="messageContainer" />
|
<MessageTyping v-if="isLoading" ref="messageContainer" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="node" class="logs-wrapper" data-test-id="lm-chat-logs">
|
<div v-if="node && messages.length" class="logs-wrapper" data-test-id="lm-chat-logs">
|
||||||
<n8n-text class="logs-title" tag="p" size="large">{{
|
<n8n-text class="logs-title" tag="p" size="large">{{
|
||||||
$locale.baseText('chat.window.logs')
|
$locale.baseText('chat.window.logs')
|
||||||
}}</n8n-text>
|
}}</n8n-text>
|
||||||
|
@ -149,8 +149,8 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import MessageTyping from '@n8n/chat/components/MessageTyping.vue';
|
import MessageTyping from '@n8n/chat/components/MessageTyping.vue';
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
|
||||||
const RunDataAi = defineAsyncComponent(
|
const RunDataAi = defineAsyncComponent(
|
||||||
|
@ -183,8 +183,8 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
mixins: [workflowRun],
|
mixins: [workflowRun],
|
||||||
setup(props, ctx) {
|
setup(props, ctx) {
|
||||||
const externalHooks = useExternalHooks();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const externalHooks = useExternalHooks();
|
||||||
const workflowHelpers = useWorkflowHelpers(router);
|
const workflowHelpers = useWorkflowHelpers(router);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -203,6 +203,7 @@ export default defineComponent({
|
||||||
modalBus: createEventBus(),
|
modalBus: createEventBus(),
|
||||||
node: null as INodeUi | null,
|
node: null as INodeUi | null,
|
||||||
WORKFLOW_LM_CHAT_MODAL_KEY,
|
WORKFLOW_LM_CHAT_MODAL_KEY,
|
||||||
|
previousMessageIndex: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -216,6 +217,8 @@ export default defineComponent({
|
||||||
this.setConnectedNode();
|
this.setConnectedNode();
|
||||||
this.messages = this.getChatMessages();
|
this.messages = this.getChatMessages();
|
||||||
this.setNode();
|
this.setNode();
|
||||||
|
|
||||||
|
setTimeout(() => this.$refs.inputField?.focus(), 0);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
displayExecution(executionId: string) {
|
displayExecution(executionId: string) {
|
||||||
|
@ -235,6 +238,20 @@ export default defineComponent({
|
||||||
inputField.focus();
|
inputField.focus();
|
||||||
},
|
},
|
||||||
updated(event: KeyboardEvent) {
|
updated(event: KeyboardEvent) {
|
||||||
|
const pastMessages = this.workflowsStore.getPastChatMessages;
|
||||||
|
if (
|
||||||
|
(this.currentMessage.length === 0 || pastMessages.includes(this.currentMessage)) &&
|
||||||
|
event.key === 'ArrowUp'
|
||||||
|
) {
|
||||||
|
const inputField = this.$refs.inputField as HTMLInputElement;
|
||||||
|
|
||||||
|
inputField?.blur();
|
||||||
|
this.currentMessage =
|
||||||
|
pastMessages[pastMessages.length - 1 - this.previousMessageIndex] ?? '';
|
||||||
|
this.previousMessageIndex = (this.previousMessageIndex + 1) % pastMessages.length;
|
||||||
|
// Refocus to move the cursor to the end of the input
|
||||||
|
setTimeout(() => inputField?.focus(), 0);
|
||||||
|
}
|
||||||
if (event.key === 'Enter' && !event.shiftKey && this.currentMessage) {
|
if (event.key === 'Enter' && !event.shiftKey && this.currentMessage) {
|
||||||
void this.sendChatMessage(this.currentMessage);
|
void this.sendChatMessage(this.currentMessage);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -255,6 +272,7 @@ export default defineComponent({
|
||||||
} as ChatMessage);
|
} as ChatMessage);
|
||||||
|
|
||||||
this.currentMessage = '';
|
this.currentMessage = '';
|
||||||
|
this.previousMessageIndex = 0;
|
||||||
await this.$nextTick();
|
await this.$nextTick();
|
||||||
this.scrollToLatestMessage();
|
this.scrollToLatestMessage();
|
||||||
await this.startWorkflowWithMessage(message);
|
await this.startWorkflowWithMessage(message);
|
||||||
|
@ -272,7 +290,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
const workflow = this.workflowHelpers.getCurrentWorkflow();
|
const workflow = this.workflowHelpers.getCurrentWorkflow();
|
||||||
|
|
||||||
const chatNode = this.workflowHelpers.getNodes().find((node: INodeUi): boolean => {
|
const chatNode = this.workflowsStore.getNodes().find((node: INodeUi): boolean => {
|
||||||
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||||
if (!nodeType) return false;
|
if (!nodeType) return false;
|
||||||
|
|
||||||
|
@ -454,6 +472,7 @@ export default defineComponent({
|
||||||
source: 'RunData.ManualChatMessage',
|
source: 'RunData.ManualChatMessage',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.workflowsStore.appendChatMessage(message);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
this.showError(
|
this.showError(
|
||||||
new Error('It was not possible to start workflow!'),
|
new Error('It was not possible to start workflow!'),
|
||||||
|
|
|
@ -140,7 +140,7 @@
|
||||||
"chat.window.logs": "Log (for last message)",
|
"chat.window.logs": "Log (for last message)",
|
||||||
"chat.window.noChatNode": "No Chat Node",
|
"chat.window.noChatNode": "No Chat Node",
|
||||||
"chat.window.noExecution": "Nothing got executed yet",
|
"chat.window.noExecution": "Nothing got executed yet",
|
||||||
"chat.window.chat.placeholder": "Type in message",
|
"chat.window.chat.placeholder": "Type a message, or press ‘up’ arrow for previous one",
|
||||||
"chat.window.chat.sendButtonText": "Send",
|
"chat.window.chat.sendButtonText": "Send",
|
||||||
"chat.window.chat.provideMessage": "Please provide a message",
|
"chat.window.chat.provideMessage": "Please provide a message",
|
||||||
"chat.window.chat.emptyChatMessage": "Empty chat message",
|
"chat.window.chat.emptyChatMessage": "Empty chat message",
|
||||||
|
|
|
@ -125,6 +125,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||||
executionWaitingForWebhook: false,
|
executionWaitingForWebhook: false,
|
||||||
nodeMetadata: {},
|
nodeMetadata: {},
|
||||||
isInDebugMode: false,
|
isInDebugMode: false,
|
||||||
|
chatMessages: [],
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
// Workflow getters
|
// Workflow getters
|
||||||
|
@ -277,6 +278,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||||
getTotalFinishedExecutionsCount(): number {
|
getTotalFinishedExecutionsCount(): number {
|
||||||
return this.finishedExecutionsCount;
|
return this.finishedExecutionsCount;
|
||||||
},
|
},
|
||||||
|
getPastChatMessages(): string[] {
|
||||||
|
return Array.from(new Set(this.chatMessages));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
getPinDataSize(pinData: Record<string, string | INodeExecutionData[]> = {}): number {
|
getPinDataSize(pinData: Record<string, string | INodeExecutionData[]> = {}): number {
|
||||||
|
@ -1436,5 +1440,13 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resetChatMessages(): void {
|
||||||
|
this.chatMessages = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
appendChatMessage(message: string): void {
|
||||||
|
this.chatMessages.push(message);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1013,6 +1013,7 @@ export default defineComponent({
|
||||||
this.instance.unbind();
|
this.instance.unbind();
|
||||||
this.instance.destroy();
|
this.instance.destroy();
|
||||||
this.uiStore.stateIsDirty = false;
|
this.uiStore.stateIsDirty = false;
|
||||||
|
this.workflowsStore.resetChatMessages();
|
||||||
window.removeEventListener('message', this.onPostMessageReceived);
|
window.removeEventListener('message', this.onPostMessageReceived);
|
||||||
nodeViewEventBus.off('newWorkflow', this.newWorkflow);
|
nodeViewEventBus.off('newWorkflow', this.newWorkflow);
|
||||||
nodeViewEventBus.off('importWorkflowData', this.onImportWorkflowDataEvent);
|
nodeViewEventBus.off('importWorkflowData', this.onImportWorkflowDataEvent);
|
||||||
|
|
Loading…
Reference in a new issue