Some more restructuring

This commit is contained in:
Oleg Ivaniv 2024-11-07 10:08:32 +01:00
parent 32b4eb523b
commit 6f4a8171ce
No known key found for this signature in database

View file

@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Ref } from 'vue';
import { provide, watch, computed, ref, watchEffect } from 'vue'; import { provide, watch, computed, ref, watchEffect } from 'vue';
import { useRouter } from 'vue-router';
import { v4 as uuid } from 'uuid';
// Constants & Symbols
import { ChatOptionsSymbol, ChatSymbol } from '@n8n/chat/constants'; import { ChatOptionsSymbol, ChatSymbol } from '@n8n/chat/constants';
import type { Router } from 'vue-router';
import { useRouter } from 'vue-router';
import { chatEventBus } from '@n8n/chat/event-buses';
import { VIEWS } from '@/constants'; import { VIEWS } from '@/constants';
import { v4 as uuid } from 'uuid';
// Components // Components
import ChatMessagesPanel from './components/ChatMessagesPanel.vue'; import ChatMessagesPanel from './components/ChatMessagesPanel.vue';
@ -19,91 +20,67 @@ import { useI18n } from '@/composables/useI18n';
import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useRunWorkflow } from '@/composables/useRunWorkflow'; import { useRunWorkflow } from '@/composables/useRunWorkflow';
// Event Bus
import { chatEventBus } from '@n8n/chat/event-buses';
// Stores
import { useCanvasStore } from '@/stores/canvas.store';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
// Types // Types
import type { Chat, ChatMessage, ChatOptions } from '@n8n/chat/types'; import type { Chat, ChatMessage, ChatOptions } from '@n8n/chat/types';
import type { RunWorkflowChatPayload } from './composables/useChatMessaging'; import type { RunWorkflowChatPayload } from './composables/useChatMessaging';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useCanvasStore } from '@/stores/canvas.store';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
// Initialize stores and composables export interface ChatProps {
const setupStores = () => { // Injected stores for testing
const router = useRouter(); workflowsStore?: ReturnType<typeof useWorkflowsStore>;
const uiStore = useUIStore(); uiStore?: ReturnType<typeof useUIStore>;
const locale = useI18n(); canvasStore?: ReturnType<typeof useCanvasStore>;
const nodeHelpers = useNodeHelpers(); nodeTypesStore?: ReturnType<typeof useNodeTypesStore>;
const workflowsStore = useWorkflowsStore(); nodeHelpers?: ReturnType<typeof useNodeHelpers>;
const canvasStore = useCanvasStore(); initialSessionId?: string;
const nodeTypesStore = useNodeTypesStore(); router?: Router;
}
return { // Props for dependency injection (makes testing easier)
router, const props = withDefaults(defineProps<ChatProps>(), {
uiStore, workflowsStore: () => useWorkflowsStore(),
locale, uiStore: () => useUIStore(),
nodeHelpers, canvasStore: () => useCanvasStore(),
workflowsStore, nodeTypesStore: () => useNodeTypesStore(),
canvasStore, nodeHelpers: () => useNodeHelpers(),
nodeTypesStore, router: () => useRouter(),
}; initialSessionId: () => uuid().replace(/-/g, ''),
}; });
// Component state // Component state
const setupState = () => { const messages = ref<ChatMessage[]>([]);
const messages = ref<ChatMessage[]>([]); const currentSessionId = ref<string>(props.initialSessionId);
const currentSessionId = ref<string>(uuid().replace(/-/g, '')); const isDisabled = ref(false);
const isDisabled = ref(false); const container = ref<HTMLElement>();
const container = ref<HTMLElement>();
return {
messages,
currentSessionId,
isDisabled,
container,
};
};
// Initialize component
const { router, uiStore, locale, nodeHelpers, workflowsStore, canvasStore, nodeTypesStore } =
setupStores();
const { messages, currentSessionId, isDisabled, container } = setupState();
// Computed properties // Computed properties
const setupComputed = () => { const workflow = computed(() => props.workflowsStore.getCurrentWorkflow());
const workflow = computed(() => workflowsStore.getCurrentWorkflow()); const isLoading = computed(() => props.uiStore.isActionActive.workflowRunning);
const isLoading = computed(() => uiStore.isActionActive.workflowRunning); const allConnections = computed(() => props.workflowsStore.allConnections);
const allConnections = computed(() => workflowsStore.allConnections); const isChatOpen = computed(() => props.workflowsStore.isChatPanelOpen);
const isChatOpen = computed(() => workflowsStore.isChatPanelOpen); const isLogsOpen = computed(() => props.workflowsStore.isLogsPanelOpen);
const isLogsOpen = computed(() => workflowsStore.isLogsPanelOpen); const previousChatMessages = computed(() => props.workflowsStore.getPastChatMessages);
const previousChatMessages = computed(() => workflowsStore.getPastChatMessages);
return { // Expose internal state for testing
workflow, defineExpose({
isLoading, messages,
allConnections, currentSessionId,
isChatOpen, isDisabled,
isLogsOpen, workflow,
previousChatMessages, isLoading,
}; });
};
const { workflow, isLoading, allConnections, isChatOpen, isLogsOpen, previousChatMessages } = const { runWorkflow } = useRunWorkflow({ router: props.router });
setupComputed();
// Initialize features
const { runWorkflow } = useRunWorkflow({ router });
// Initialize features with injected dependencies
const { chatTriggerNode, connectedNode, allowFileUploads, setChatTriggerNode, setConnectedNode } = const { chatTriggerNode, connectedNode, allowFileUploads, setChatTriggerNode, setConnectedNode } =
useChatTrigger({ useChatTrigger({
workflow, workflow,
getNodeByName: workflowsStore.getNodeByName, getNodeByName: props.workflowsStore.getNodeByName,
getNodeType: nodeTypesStore.getNodeType, getNodeType: props.nodeTypesStore.getNodeType,
}); });
const { sendMessage, getChatMessages } = useChatMessaging({ const { sendMessage, getChatMessages } = useChatMessaging({
@ -113,8 +90,8 @@ const { sendMessage, getChatMessages } = useChatMessaging({
sessionId: currentSessionId, sessionId: currentSessionId,
workflow, workflow,
isLoading, isLoading,
executionResultData: computed(() => workflowsStore.getWorkflowExecution?.data?.resultData), executionResultData: computed(() => props.workflowsStore.getWorkflowExecution?.data?.resultData),
getWorkflowResultDataByNodeName: workflowsStore.getWorkflowResultDataByNodeName, getWorkflowResultDataByNodeName: props.workflowsStore.getWorkflowResultDataByNodeName,
onRunChatWorkflow, onRunChatWorkflow,
}); });
@ -128,14 +105,22 @@ const {
onWindowResize, onWindowResize,
} = useResize(container); } = useResize(container);
// Chat configuration // Extracted pure functions for better testability
const setupChatConfig = (): { chatConfig: Chat; chatOptions: ChatOptions } => { function createChatConfig(params: {
messages: Chat['messages'];
sendMessage: Chat['sendMessage'];
currentSessionId: Chat['currentSessionId'];
isLoading: Ref<boolean>;
isDisabled: Ref<boolean>;
allowFileUploads: Ref<boolean>;
locale: ReturnType<typeof useI18n>;
}): { chatConfig: Chat; chatOptions: ChatOptions } {
const chatConfig: Chat = { const chatConfig: Chat = {
messages, messages: params.messages,
sendMessage, sendMessage: params.sendMessage,
initialMessages: ref([]), initialMessages: ref([]),
currentSessionId, currentSessionId: params.currentSessionId,
waitingForResponse: isLoading, waitingForResponse: params.isLoading,
}; };
const chatOptions: ChatOptions = { const chatOptions: ChatOptions = {
@ -144,7 +129,7 @@ const setupChatConfig = (): { chatConfig: Chat; chatOptions: ChatOptions } => {
title: '', title: '',
footer: '', footer: '',
subtitle: '', subtitle: '',
inputPlaceholder: locale.baseText('chat.window.chat.placeholder'), inputPlaceholder: params.locale.baseText('chat.window.chat.placeholder'),
getStarted: '', getStarted: '',
closeButtonTooltip: '', closeButtonTooltip: '',
}, },
@ -152,34 +137,47 @@ const setupChatConfig = (): { chatConfig: Chat; chatOptions: ChatOptions } => {
webhookUrl: '', webhookUrl: '',
mode: 'window', mode: 'window',
showWindowCloseButton: true, showWindowCloseButton: true,
disabled: isDisabled, disabled: params.isDisabled,
allowFileUploads, allowFileUploads: params.allowFileUploads,
allowedFilesMimeTypes: '', allowedFilesMimeTypes: '',
}; };
return { chatConfig, chatOptions }; return { chatConfig, chatOptions };
}; }
const { chatConfig, chatOptions } = setupChatConfig(); function displayExecution(params: { router: Router; workflowId: string; executionId: string }) {
const route = params.router.resolve({
// Methods
const displayExecution = (executionId: string) => {
const route = router.resolve({
name: VIEWS.EXECUTION_PREVIEW, name: VIEWS.EXECUTION_PREVIEW,
params: { name: workflow.value.id, executionId }, params: { name: params.workflowId, executionId: params.executionId },
}); });
window.open(route.href, '_blank'); window.open(route.href, '_blank');
}
function refreshSession(params: { messages: Ref<ChatMessage[]>; currentSessionId: Ref<string> }) {
props.workflowsStore.setWorkflowExecutionData(null);
props.nodeHelpers.updateNodesExecutionIssues();
params.messages.value = [];
params.currentSessionId.value = uuid().replace(/-/g, '');
}
// Event handlers
const handleDisplayExecution = (executionId: string) => {
displayExecution({
router: props.router,
workflowId: workflow.value.id,
executionId,
});
}; };
const refreshSession = () => { const handleRefreshSession = () => {
workflowsStore.setWorkflowExecutionData(null); refreshSession({
nodeHelpers.updateNodesExecutionIssues(); messages,
messages.value = []; currentSessionId,
currentSessionId.value = uuid().replace(/-/g, ''); });
}; };
const closeLogs = () => { const closeLogs = () => {
workflowsStore.setPanelOpen('logs', false); props.workflowsStore.setPanelOpen('logs', false);
}; };
async function onRunChatWorkflow(payload: RunWorkflowChatPayload) { async function onRunChatWorkflow(payload: RunWorkflowChatPayload) {
@ -189,10 +187,21 @@ async function onRunChatWorkflow(payload: RunWorkflowChatPayload) {
source: payload.source, source: payload.source,
}); });
workflowsStore.appendChatMessage(payload.message); props.workflowsStore.appendChatMessage(payload.message);
return response; return response;
} }
// Initialize chat config
const { chatConfig, chatOptions } = createChatConfig({
messages,
sendMessage,
currentSessionId,
isLoading,
isDisabled,
allowFileUploads,
locale: useI18n(),
});
// Provide chat context // Provide chat context
provide(ChatSymbol, chatConfig); provide(ChatSymbol, chatConfig);
provide(ChatOptionsSymbol, chatOptions); provide(ChatOptionsSymbol, chatOptions);
@ -220,7 +229,7 @@ watch(
watch( watch(
() => allConnections.value, () => allConnections.value,
() => { () => {
if (canvasStore.isLoading) return; if (props.canvasStore.isLoading) return;
setTimeout(() => { setTimeout(() => {
if (!chatTriggerNode.value) { if (!chatTriggerNode.value) {
setChatTriggerNode(); setChatTriggerNode();
@ -232,7 +241,7 @@ watch(
); );
watchEffect(() => { watchEffect(() => {
canvasStore.setPanelHeight(isChatOpen.value || isLogsOpen.value ? height.value : 0); props.canvasStore.setPanelHeight(isChatOpen.value || isLogsOpen.value ? height.value : 0);
}); });
</script> </script>
@ -261,8 +270,8 @@ watchEffect(() => {
:messages="messages" :messages="messages"
:session-id="currentSessionId" :session-id="currentSessionId"
:past-chat-messages="previousChatMessages" :past-chat-messages="previousChatMessages"
@refresh-session="refreshSession" @refresh-session="handleRefreshSession"
@display-execution="displayExecution" @display-execution="handleDisplayExecution"
@send-message="sendMessage" @send-message="sendMessage"
/> />
</div> </div>