mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
refactor(core): Remove Ask AI HTTP request feature (no-changelog) (#9931)
This commit is contained in:
committed by
GitHub
parent
cef177455e
commit
86018aa6e0
@@ -26,9 +26,6 @@
|
||||
<component :is="Component" v-else />
|
||||
</router-view>
|
||||
</div>
|
||||
<div id="chat" :class="{ [$style.chat]: true, [$style.open]: aiStore.assistantChatOpen }">
|
||||
<AIAssistantChat v-if="aiStore.assistantChatOpen" />
|
||||
</div>
|
||||
<Modals />
|
||||
<Telemetry />
|
||||
</div>
|
||||
@@ -60,8 +57,6 @@ import { useUsageStore } from '@/stores/usage.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { useHistoryHelper } from '@/composables/useHistoryHelper';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAIStore } from './stores/ai.store';
|
||||
import AIAssistantChat from './components/AIAssistantChat/AIAssistantChat.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
@@ -70,7 +65,6 @@ export default defineComponent({
|
||||
LoadingView,
|
||||
Telemetry,
|
||||
Modals,
|
||||
AIAssistantChat,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
@@ -91,7 +85,6 @@ export default defineComponent({
|
||||
useSourceControlStore,
|
||||
useCloudPlanStore,
|
||||
useUsageStore,
|
||||
useAIStore,
|
||||
),
|
||||
defaultLocale(): string {
|
||||
return this.rootStore.defaultLocale;
|
||||
@@ -134,10 +127,10 @@ export default defineComponent({
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'banners banners banners'
|
||||
'sidebar header chat'
|
||||
'sidebar content chat';
|
||||
grid-auto-columns: fit-content($sidebar-expanded-width) 1fr fit-content($chat-width);
|
||||
'banners banners'
|
||||
'sidebar header'
|
||||
'sidebar content';
|
||||
grid-auto-columns: fit-content($sidebar-expanded-width) 1fr;
|
||||
grid-template-rows: auto fit-content($header-height) 1fr;
|
||||
height: 100vh;
|
||||
}
|
||||
@@ -171,15 +164,4 @@ export default defineComponent({
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
.chat {
|
||||
grid-area: chat;
|
||||
z-index: 999;
|
||||
height: 100%;
|
||||
width: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&.open {
|
||||
width: $chat-width;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1856,10 +1856,6 @@ export type NewConnectionInfo = {
|
||||
endpointUuid?: string;
|
||||
};
|
||||
|
||||
export type AIAssistantConnectionInfo = NewConnectionInfo & {
|
||||
stepName?: string;
|
||||
};
|
||||
|
||||
export type EnterpriseEditionFeatureKey =
|
||||
| 'AdvancedExecutionFilters'
|
||||
| 'Sharing'
|
||||
|
||||
@@ -114,10 +114,6 @@ export const defaultSettings: IN8nUISettings = {
|
||||
},
|
||||
ai: {
|
||||
enabled: false,
|
||||
provider: '',
|
||||
features: {
|
||||
generateCurl: false,
|
||||
},
|
||||
},
|
||||
workflowHistory: {
|
||||
pruneTime: 0,
|
||||
|
||||
@@ -2,16 +2,6 @@ import type { IRestApiContext, Schema } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
export interface GenerateCurlPayload {
|
||||
service: string;
|
||||
request: string;
|
||||
}
|
||||
|
||||
export interface GenerateCurlResponse {
|
||||
curl: string;
|
||||
metadata: object;
|
||||
}
|
||||
|
||||
export async function generateCodeForPrompt(
|
||||
ctx: IRestApiContext,
|
||||
{
|
||||
@@ -38,15 +28,3 @@ export async function generateCodeForPrompt(
|
||||
n8nVersion,
|
||||
} as IDataObject);
|
||||
}
|
||||
|
||||
export const generateCurl = async (
|
||||
context: IRestApiContext,
|
||||
payload: GenerateCurlPayload,
|
||||
): Promise<GenerateCurlResponse> => {
|
||||
return await makeRestApiRequest(
|
||||
context,
|
||||
'POST',
|
||||
'/ai/generate-curl',
|
||||
payload as unknown as IDataObject,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import ChatComponent from '@n8n/chat/components/Chat.vue';
|
||||
import { ChatOptionsSymbol, ChatSymbol } from '@n8n/chat/constants';
|
||||
import type { Chat, ChatMessage, ChatOptions } from '@n8n/chat/types';
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, provide, ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import QuickReplies from './QuickReplies.vue';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useAIStore } from '@/stores/ai.store';
|
||||
import { chatEventBus } from '@n8n/chat/event-buses';
|
||||
import {
|
||||
AI_ASSISTANT_EXPERIMENT_URLS,
|
||||
AI_ASSISTANT_LOCAL_STORAGE_KEY,
|
||||
MODAL_CONFIRM,
|
||||
} from '@/constants';
|
||||
import { useStorage } from '@/composables/useStorage';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
|
||||
const locale = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const { confirm } = useMessage();
|
||||
|
||||
const usersStore = useUsersStore();
|
||||
const aiStore = useAIStore();
|
||||
|
||||
const messages: Ref<ChatMessage[]> = ref([]);
|
||||
const waitingForResponse = ref(false);
|
||||
const currentSessionId = ref<string>(String(Date.now()));
|
||||
const disableChat = ref(false);
|
||||
|
||||
const userName = computed(() => usersStore.currentUser?.firstName ?? 'there');
|
||||
const latestConnectionInfo = computed(() => aiStore.latestConnectionInfo);
|
||||
|
||||
const chatTitle = locale.baseText('aiAssistantChat.title');
|
||||
const nowMilliseconds = () => String(DateTime.now().toMillis());
|
||||
const nowIsoString = () => new Date().toISOString();
|
||||
const thanksResponses: ChatMessage[] = [
|
||||
{
|
||||
id: nowMilliseconds(),
|
||||
sender: 'bot',
|
||||
text: locale.baseText('aiAssistantChat.response.message1'),
|
||||
createdAt: nowIsoString(),
|
||||
},
|
||||
{
|
||||
id: nowMilliseconds(),
|
||||
sender: 'bot',
|
||||
text: locale.baseText('aiAssistantChat.response.message2'),
|
||||
createdAt: nowIsoString(),
|
||||
},
|
||||
{
|
||||
id: nowMilliseconds(),
|
||||
sender: 'bot',
|
||||
text: '🙏',
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: nowMilliseconds(),
|
||||
type: 'component',
|
||||
key: 'QuickReplies',
|
||||
sender: 'user',
|
||||
createdAt: nowIsoString(),
|
||||
transparent: true,
|
||||
arguments: {
|
||||
suggestions: [
|
||||
{ label: locale.baseText('aiAssistantChat.response.quickReply.close'), key: 'close' },
|
||||
{
|
||||
label: locale.baseText('aiAssistantChat.response.quickReply.giveFeedback'),
|
||||
key: 'give_feedback',
|
||||
},
|
||||
{
|
||||
label: locale.baseText('aiAssistantChat.response.quickReply.signUp'),
|
||||
key: 'sign_up',
|
||||
},
|
||||
],
|
||||
onReplySelected: ({ key }: { key: string; label: string }) => {
|
||||
switch (key) {
|
||||
case 'give_feedback':
|
||||
window.open(AI_ASSISTANT_EXPERIMENT_URLS.FEEDBACK_FORM, '_blank');
|
||||
break;
|
||||
case 'sign_up':
|
||||
window.open(AI_ASSISTANT_EXPERIMENT_URLS.SIGN_UP, '_blank');
|
||||
break;
|
||||
}
|
||||
aiStore.assistantChatOpen = false;
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const initialMessageText = computed(() => {
|
||||
if (latestConnectionInfo.value?.stepName) {
|
||||
return locale.baseText('aiAssistantChat.initialMessage.nextStep', {
|
||||
interpolate: { currentAction: latestConnectionInfo.value.stepName },
|
||||
});
|
||||
}
|
||||
|
||||
return locale.baseText('aiAssistantChat.initialMessage.firstStep');
|
||||
});
|
||||
|
||||
const initialMessages: Ref<ChatMessage[]> = ref([
|
||||
{
|
||||
id: '1',
|
||||
type: 'text',
|
||||
sender: 'bot',
|
||||
createdAt: new Date().toISOString(),
|
||||
text: `${locale.baseText('aiAssistantChat.greeting', { interpolate: { username: userName.value ?? 'there' } })} ${initialMessageText.value}`,
|
||||
},
|
||||
]);
|
||||
|
||||
const sendMessage = async (message: string) => {
|
||||
disableChat.value = true;
|
||||
waitingForResponse.value = true;
|
||||
messages.value.push({
|
||||
id: String(messages.value.length + 1),
|
||||
sender: 'user',
|
||||
text: message,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
trackUserMessage(message);
|
||||
thanksResponses.forEach((response, index) => {
|
||||
// Push each response with a delay of 1500ms
|
||||
setTimeout(
|
||||
() => {
|
||||
messages.value.push(response);
|
||||
chatEventBus.emit('scrollToBottom');
|
||||
if (index === thanksResponses.length - 1) {
|
||||
waitingForResponse.value = false;
|
||||
// Once last message is sent, disable the experiment
|
||||
useStorage(AI_ASSISTANT_LOCAL_STORAGE_KEY).value = 'true';
|
||||
}
|
||||
},
|
||||
1500 * (index + 1),
|
||||
);
|
||||
});
|
||||
chatEventBus.emit('scrollToBottom');
|
||||
};
|
||||
|
||||
const trackUserMessage = (message: string) => {
|
||||
telemetry.track('User responded in AI chat', {
|
||||
prompt: message,
|
||||
chatMode: 'nextStepAssistant',
|
||||
initialMessage: initialMessageText.value,
|
||||
});
|
||||
};
|
||||
|
||||
const chatOptions: ChatOptions = {
|
||||
i18n: {
|
||||
en: {
|
||||
title: chatTitle,
|
||||
footer: '',
|
||||
subtitle: '',
|
||||
inputPlaceholder: locale.baseText('aiAssistantChat.chatPlaceholder'),
|
||||
getStarted: locale.baseText('aiAssistantChat.getStarted'),
|
||||
closeButtonTooltip: locale.baseText('aiAssistantChat.closeButtonTooltip'),
|
||||
},
|
||||
},
|
||||
webhookUrl: 'https://webhook.url',
|
||||
mode: 'window',
|
||||
showWindowCloseButton: true,
|
||||
messageComponents: {
|
||||
QuickReplies,
|
||||
},
|
||||
disabled: disableChat,
|
||||
};
|
||||
|
||||
const chatConfig: Chat = {
|
||||
messages,
|
||||
sendMessage,
|
||||
initialMessages,
|
||||
currentSessionId,
|
||||
waitingForResponse,
|
||||
};
|
||||
|
||||
provide(ChatSymbol, chatConfig);
|
||||
provide(ChatOptionsSymbol, chatOptions);
|
||||
|
||||
onMounted(() => {
|
||||
chatEventBus.emit('focusInput');
|
||||
chatEventBus.on('close', onBeforeClose);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
chatEventBus.off('close', onBeforeClose);
|
||||
});
|
||||
|
||||
async function onBeforeClose() {
|
||||
const confirmModal = await confirm(locale.baseText('aiAssistantChat.closeChatConfirmation'), {
|
||||
confirmButtonText: locale.baseText('aiAssistantChat.closeChatConfirmation.confirm'),
|
||||
cancelButtonText: locale.baseText('aiAssistantChat.closeChatConfirmation.cancel'),
|
||||
});
|
||||
|
||||
if (confirmModal === MODAL_CONFIRM) {
|
||||
aiStore.assistantChatOpen = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[$style.container, 'ignore-key-press']">
|
||||
<ChatComponent />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.container {
|
||||
height: 100%;
|
||||
background-color: var(--color-background-light);
|
||||
filter: drop-shadow(0px 8px 24px #41424412);
|
||||
border-left: 1px solid var(--color-foreground-dark);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: var(--font-size-l);
|
||||
background-color: #fff;
|
||||
padding: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--spacing-xs);
|
||||
}
|
||||
</style>
|
||||
@@ -1,148 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useAIStore } from '@/stores/ai.store';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { computed } from 'vue';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
|
||||
const aiStore = useAIStore();
|
||||
const locale = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const emit = defineEmits<{ optionSelected: [option: string] }>();
|
||||
|
||||
const aiAssistantChatOpen = computed(() => aiStore.assistantChatOpen);
|
||||
|
||||
const title = computed(() => {
|
||||
return aiStore.nextStepPopupConfig.title;
|
||||
});
|
||||
|
||||
const options = computed(() => [
|
||||
{
|
||||
label: locale.baseText('nextStepPopup.option.choose'),
|
||||
icon: '➕',
|
||||
key: 'choose',
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: locale.baseText('nextStepPopup.option.generate'),
|
||||
icon: '✨',
|
||||
key: 'generate',
|
||||
disabled: aiAssistantChatOpen.value,
|
||||
},
|
||||
]);
|
||||
|
||||
const position = computed(() => {
|
||||
return [aiStore.nextStepPopupConfig.position[0], aiStore.nextStepPopupConfig.position[1]];
|
||||
});
|
||||
|
||||
const style = computed(() => {
|
||||
return {
|
||||
left: `${position.value[0]}px`,
|
||||
top: `${position.value[1]}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
aiStore.closeNextStepPopup();
|
||||
};
|
||||
|
||||
const onOptionSelected = (option: string) => {
|
||||
if (option === 'choose') {
|
||||
emit('optionSelected', option);
|
||||
} else if (option === 'generate') {
|
||||
telemetry.track('User clicked generate AI button', {}, { withPostHog: true });
|
||||
aiStore.assistantChatOpen = true;
|
||||
}
|
||||
close();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-on-click-outside="close" :class="$style.container" :style="style">
|
||||
<div :class="$style.title">{{ title }}</div>
|
||||
<ul :class="$style.options">
|
||||
<li
|
||||
v-for="option in options"
|
||||
:key="option.key"
|
||||
:class="{ [$style.option]: true, [$style.disabled]: option.disabled }"
|
||||
@click="onOptionSelected(option.key)"
|
||||
>
|
||||
<div :class="$style.icon">
|
||||
{{ option.icon }}
|
||||
</div>
|
||||
<div :class="$style.label">
|
||||
{{ option.label }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.container {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 190px;
|
||||
font-size: var(--font-size-2xs);
|
||||
background: var(--color-background-xlight);
|
||||
filter: drop-shadow(0px 6px 16px #441c170f);
|
||||
border: var(--border-width-base) var(--border-style-base) var(--color-foreground-light);
|
||||
border-radius: var(--border-radius-base);
|
||||
|
||||
// Arrow border is created as the outer triange
|
||||
&:before {
|
||||
content: '';
|
||||
position: relative;
|
||||
left: -11px;
|
||||
top: calc(50% - 8px);
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
border-right: 10px solid var(--color-foreground-light);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// Arrow background is created as the inner triangle
|
||||
&:after {
|
||||
content: '';
|
||||
position: relative;
|
||||
left: -10px;
|
||||
top: calc(50% - 8px);
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
border-right: 10px solid var(--color-background-xlight);
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: var(--spacing-xs);
|
||||
color: var(--color-text-base);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.options {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: var(--spacing-2xs);
|
||||
}
|
||||
|
||||
.option {
|
||||
display: flex;
|
||||
padding: var(--spacing-3xs) var(--spacing-xs);
|
||||
gap: var(--spacing-xs);
|
||||
cursor: pointer;
|
||||
color: var(--color-text-dark);
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-base);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,66 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import Button from 'n8n-design-system/components/N8nButton/Button.vue';
|
||||
|
||||
type QuickReply = {
|
||||
label: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
const locale = useI18n();
|
||||
|
||||
const emit = defineEmits<{
|
||||
replySelected: [value: QuickReply];
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
suggestions: QuickReply[];
|
||||
}>();
|
||||
|
||||
function onButtonClick(action: QuickReply) {
|
||||
emit('replySelected', action);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<p :class="$style.hint">{{ locale.baseText('aiAssistantChat.quickReply.title') }}</p>
|
||||
<div :class="$style.suggestions">
|
||||
<Button
|
||||
v-for="action in suggestions"
|
||||
:key="action.key"
|
||||
:class="$style.replyButton"
|
||||
outline
|
||||
type="secondary"
|
||||
@click="onButtonClick(action)"
|
||||
>
|
||||
{{ action.label }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2xs);
|
||||
width: auto;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.suggestions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: fit-content;
|
||||
gap: var(--spacing-4xs);
|
||||
}
|
||||
.hint {
|
||||
color: var(--color-text-base);
|
||||
font-size: var(--font-size-2xs);
|
||||
}
|
||||
.replyButton {
|
||||
display: flex;
|
||||
background: var(--chat--color-white);
|
||||
}
|
||||
</style>
|
||||
@@ -1,216 +0,0 @@
|
||||
<template>
|
||||
<Modal
|
||||
width="700px"
|
||||
:title="i18n.baseText('generateCurlModal.title')"
|
||||
:event-bus="modalBus"
|
||||
:name="GENERATE_CURL_MODAL_KEY"
|
||||
:center="true"
|
||||
>
|
||||
<template #content>
|
||||
<div :class="$style.container">
|
||||
<N8nFormInputs
|
||||
:inputs="formInputs"
|
||||
:event-bus="formBus"
|
||||
column-view
|
||||
@update="onUpdate"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.modalFooter">
|
||||
<N8nNotice
|
||||
:class="$style.notice"
|
||||
:content="i18n.baseText('generateCurlModal.notice.content')"
|
||||
/>
|
||||
<div>
|
||||
<N8nButton
|
||||
float="right"
|
||||
:loading="loading"
|
||||
:label="i18n.baseText('generateCurlModal.button.label')"
|
||||
@click="onGenerate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Modal from '@/components/Modal.vue';
|
||||
import { GENERATE_CURL_MODAL_KEY } from '@/constants';
|
||||
import { ref } from 'vue';
|
||||
import { createEventBus } from 'n8n-design-system/utils';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useAIStore } from '@/stores/ai.store';
|
||||
import type { IFormInput } from 'n8n-design-system';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useImportCurlCommand } from '@/composables/useImportCurlCommand';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
|
||||
const telemetry = useTelemetry();
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
|
||||
const uiStore = useUIStore();
|
||||
const aiStore = useAIStore();
|
||||
const ndvStore = useNDVStore();
|
||||
|
||||
const modalBus = createEventBus();
|
||||
const formBus = createEventBus();
|
||||
|
||||
const initialServiceValue = uiStore.modalsById[GENERATE_CURL_MODAL_KEY].data?.service as string;
|
||||
const initialRequestValue = uiStore.modalsById[GENERATE_CURL_MODAL_KEY].data?.request as string;
|
||||
|
||||
const formInputs: IFormInput[] = [
|
||||
{
|
||||
name: 'service',
|
||||
initialValue: initialServiceValue,
|
||||
properties: {
|
||||
label: i18n.baseText('generateCurlModal.service.label'),
|
||||
placeholder: i18n.baseText('generateCurlModal.service.placeholder'),
|
||||
type: 'text',
|
||||
required: true,
|
||||
capitalize: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'request',
|
||||
initialValue: initialRequestValue,
|
||||
properties: {
|
||||
label: i18n.baseText('generateCurlModal.request.label'),
|
||||
placeholder: i18n.baseText('generateCurlModal.request.placeholder'),
|
||||
type: 'text',
|
||||
required: true,
|
||||
capitalize: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const formValues = ref<{ service: string; request: string }>({
|
||||
service: initialServiceValue ?? '',
|
||||
request: initialRequestValue ?? '',
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const { importCurlCommand } = useImportCurlCommand({
|
||||
onImportSuccess,
|
||||
onImportFailure,
|
||||
onAfterImport,
|
||||
i18n: {
|
||||
invalidCurCommand: {
|
||||
title: 'generateCurlModal.invalidCurlCommand.title',
|
||||
message: 'generateCurlModal.invalidCurlCommand.message',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function closeDialog(): void {
|
||||
modalBus.emit('close');
|
||||
}
|
||||
|
||||
function onImportSuccess() {
|
||||
sendImportCurlTelemetry();
|
||||
|
||||
toast.showMessage({
|
||||
title: i18n.baseText('generateCurlModal.success.title'),
|
||||
message: i18n.baseText('generateCurlModal.success.message'),
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
function onImportFailure(data: { invalidProtocol: boolean; protocol?: string }) {
|
||||
sendImportCurlTelemetry({ valid: false, ...data });
|
||||
}
|
||||
|
||||
function onAfterImport() {
|
||||
uiStore.setModalData({
|
||||
name: GENERATE_CURL_MODAL_KEY,
|
||||
data: {
|
||||
service: formValues.value.service,
|
||||
request: formValues.value.request,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function sendImportCurlTelemetry(
|
||||
data: { valid: boolean; invalidProtocol: boolean; protocol?: string } = {
|
||||
valid: true,
|
||||
invalidProtocol: false,
|
||||
protocol: '',
|
||||
},
|
||||
): void {
|
||||
const service = formValues.value.service;
|
||||
const request = formValues.value.request;
|
||||
|
||||
telemetry.track(
|
||||
'User generated curl command using AI',
|
||||
{
|
||||
request,
|
||||
request_service_name: service,
|
||||
valid_curl_response: data.valid,
|
||||
api_docs_returned: false,
|
||||
invalidProtocol: data.invalidProtocol,
|
||||
protocol: data.protocol,
|
||||
node_type: ndvStore.activeNode?.type,
|
||||
node_name: ndvStore.activeNode?.name,
|
||||
},
|
||||
{ withPostHog: true },
|
||||
);
|
||||
}
|
||||
|
||||
async function onUpdate(field: { name: string; value: string }) {
|
||||
formValues.value = {
|
||||
...formValues.value,
|
||||
[field.name]: field.value,
|
||||
};
|
||||
}
|
||||
|
||||
async function onGenerate() {
|
||||
formBus.emit('submit');
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
const service = formValues.value.service;
|
||||
const request = formValues.value.request;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
const data = await aiStore.generateCurl({
|
||||
service,
|
||||
request,
|
||||
});
|
||||
|
||||
await importCurlCommand(data.curl);
|
||||
} catch (error) {
|
||||
toast.showError(error, i18n.baseText('error'));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.modalFooter {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container > * {
|
||||
margin-bottom: var(--spacing-s);
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { GENERATE_CURL_MODAL_KEY, IMPORT_CURL_MODAL_KEY } from '@/constants';
|
||||
import { IMPORT_CURL_MODAL_KEY } from '@/constants';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useAIStore } from '@/stores/ai.store';
|
||||
|
||||
defineProps<{
|
||||
isReadOnly: boolean;
|
||||
}>();
|
||||
|
||||
const uiStore = useUIStore();
|
||||
const aiStore = useAIStore();
|
||||
|
||||
function onImportCurlClicked() {
|
||||
uiStore.openModal(IMPORT_CURL_MODAL_KEY);
|
||||
}
|
||||
|
||||
function onGenerateCurlClicked() {
|
||||
uiStore.openModal(GENERATE_CURL_MODAL_KEY);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -28,16 +22,6 @@ function onGenerateCurlClicked() {
|
||||
size="mini"
|
||||
@click="onImportCurlClicked"
|
||||
/>
|
||||
|
||||
<n8n-button
|
||||
v-if="aiStore.isGenerateCurlEnabled"
|
||||
class="mr-2xs"
|
||||
type="secondary"
|
||||
:label="$locale.baseText('generateCurlParameter.label')"
|
||||
:disabled="isReadOnly"
|
||||
size="mini"
|
||||
@click="onGenerateCurlClicked"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
MFA_SETUP_MODAL_KEY,
|
||||
WORKFLOW_HISTORY_VERSION_RESTORE,
|
||||
SETUP_CREDENTIALS_MODAL_KEY,
|
||||
GENERATE_CURL_MODAL_KEY,
|
||||
PROJECT_MOVE_RESOURCE_MODAL,
|
||||
PROJECT_MOVE_RESOURCE_CONFIRM_MODAL,
|
||||
} from '@/constants';
|
||||
@@ -55,7 +54,6 @@ import WorkflowSettings from '@/components/WorkflowSettings.vue';
|
||||
import DeleteUserModal from '@/components/DeleteUserModal.vue';
|
||||
import ActivationModal from '@/components/ActivationModal.vue';
|
||||
import ImportCurlModal from '@/components/ImportCurlModal.vue';
|
||||
import GenerateCurlModal from '@/components/GenerateCurlModal.vue';
|
||||
import MfaSetupModal from '@/components/MfaSetupModal.vue';
|
||||
import WorkflowShareModal from '@/components/WorkflowShareModal.ee.vue';
|
||||
import EventDestinationSettingsModal from '@/components/SettingsLogStreaming/EventDestinationSettingsModal.ee.vue';
|
||||
@@ -166,10 +164,6 @@ import ProjectMoveResourceConfirmModal from '@/components/Projects/ProjectMoveRe
|
||||
<ImportCurlModal />
|
||||
</ModalRoot>
|
||||
|
||||
<ModalRoot :name="GENERATE_CURL_MODAL_KEY">
|
||||
<GenerateCurlModal />
|
||||
</ModalRoot>
|
||||
|
||||
<ModalRoot :name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY">
|
||||
<template #default="{ modalName, activeId, mode }">
|
||||
<CommunityPackageManageConfirmModal
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div
|
||||
v-if="active"
|
||||
ref="nodeCreator"
|
||||
:class="{ [$style.nodeCreator]: true, [$style.chatOpened]: chatSidebarOpen }"
|
||||
:class="{ [$style.nodeCreator]: true }"
|
||||
:style="nodeCreatorInlineStyle"
|
||||
data-test-id="node-creator"
|
||||
@dragover="onDragOver"
|
||||
@@ -37,7 +37,6 @@ import { useActionsGenerator } from './composables/useActionsGeneration';
|
||||
import NodesListPanel from './Panel/NodesListPanel.vue';
|
||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useAIStore } from '@/stores/ai.store';
|
||||
import { DRAG_EVENT_DATA_KEY } from '@/constants';
|
||||
|
||||
export interface Props {
|
||||
@@ -53,7 +52,6 @@ const emit = defineEmits<{
|
||||
nodeTypeSelected: [value: string[]];
|
||||
}>();
|
||||
const uiStore = useUIStore();
|
||||
const aiStore = useAIStore();
|
||||
|
||||
const { setShowScrim, setActions, setMergeNodes } = useNodeCreatorStore();
|
||||
const { generateMergedNodesAndActions } = useActionsGenerator();
|
||||
@@ -67,8 +65,6 @@ const showScrim = computed(() => useNodeCreatorStore().showScrim);
|
||||
|
||||
const viewStacksLength = computed(() => useViewStacks().viewStacks.length);
|
||||
|
||||
const chatSidebarOpen = computed(() => aiStore.assistantChatOpen);
|
||||
|
||||
const nodeCreatorInlineStyle = computed(() => {
|
||||
return { top: `${uiStore.bannersHeight + uiStore.headerHeight}px` };
|
||||
});
|
||||
@@ -177,10 +173,6 @@ onBeforeUnmount(() => {
|
||||
z-index: 200;
|
||||
width: $node-creator-width;
|
||||
color: $node-creator-text-color;
|
||||
|
||||
&.chatOpened {
|
||||
right: $chat-width;
|
||||
}
|
||||
}
|
||||
|
||||
.nodeCreatorScrim {
|
||||
|
||||
@@ -58,7 +58,6 @@ export const ONBOARDING_CALL_SIGNUP_MODAL_KEY = 'onboardingCallSignup';
|
||||
export const COMMUNITY_PACKAGE_INSTALL_MODAL_KEY = 'communityPackageInstall';
|
||||
export const COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY = 'communityPackageManageConfirm';
|
||||
export const IMPORT_CURL_MODAL_KEY = 'importCurl';
|
||||
export const GENERATE_CURL_MODAL_KEY = 'generateCurl';
|
||||
export const LOG_STREAM_MODAL_KEY = 'settingsLogStream';
|
||||
export const SOURCE_CONTROL_PUSH_MODAL_KEY = 'sourceControlPush';
|
||||
export const SOURCE_CONTROL_PULL_MODAL_KEY = 'sourceControlPull';
|
||||
@@ -671,12 +670,6 @@ export const ASK_AI_EXPERIMENT = {
|
||||
|
||||
export const TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT = '017_template_credential_setup_v2';
|
||||
|
||||
export const AI_ASSISTANT_EXPERIMENT = {
|
||||
name: '19_ai_assistant_experiment',
|
||||
control: 'control',
|
||||
variant: 'variant',
|
||||
};
|
||||
|
||||
export const CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT = {
|
||||
name: '20_canvas_auto_add_manual_trigger',
|
||||
control: 'control',
|
||||
@@ -686,7 +679,6 @@ export const CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT = {
|
||||
export const EXPERIMENTS_TO_TRACK = [
|
||||
ASK_AI_EXPERIMENT.name,
|
||||
TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT,
|
||||
AI_ASSISTANT_EXPERIMENT.name,
|
||||
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name,
|
||||
];
|
||||
|
||||
@@ -848,13 +840,6 @@ export const INSECURE_CONNECTION_WARNING = `
|
||||
</div>
|
||||
</body>`;
|
||||
|
||||
export const AI_ASSISTANT_EXPERIMENT_URLS = {
|
||||
FEEDBACK_FORM: 'https://chat.arro.co/to4639rATEMV',
|
||||
SIGN_UP: 'https://adore.app.n8n.cloud/form/4704cce3-4cef-4dc8-b67f-8a510c5d561a',
|
||||
};
|
||||
|
||||
export const AI_ASSISTANT_LOCAL_STORAGE_KEY = 'N8N_AI_ASSISTANT_EXPERIMENT';
|
||||
|
||||
/**
|
||||
* Injection Keys
|
||||
*/
|
||||
|
||||
@@ -36,7 +36,6 @@ $warning-tooltip-color: var(--color-danger);
|
||||
|
||||
// sass variable is used for scss files
|
||||
$header-height: calc(var(--header-height) * 1px);
|
||||
$chat-width: calc(var(--chat-width) * 1px);
|
||||
|
||||
// sidebar
|
||||
$sidebar-width: 65px;
|
||||
|
||||
@@ -4,34 +4,6 @@
|
||||
:root {
|
||||
// Using native css variable enables us to use this value in JS
|
||||
--header-height: 65;
|
||||
--chat-width: 350;
|
||||
|
||||
// n8n-chat customizations
|
||||
--chat--spacing: var(--spacing-2xs);
|
||||
|
||||
--chat--header-height: calc(var(--header-height) * 1px);
|
||||
--chat--header--padding: 0 var(--spacing-xs);
|
||||
--chat--heading--font-size: var(--font-size-m);
|
||||
--chat--subtitle--font-size: var(--font-size-s);
|
||||
--chat--subtitle--line-height: var(--font-line-height-base);
|
||||
--chat--header--background: var(--color-background-xlight);
|
||||
--chat--header--color: var(--color-text-dark);
|
||||
--chat--header--border-bottom: var(--border-base);
|
||||
--chat--close--button--color-hover: var(--color-primary);
|
||||
|
||||
// Message styles
|
||||
--chat--message--padding: var(--spacing-3xs);
|
||||
--chat--message--font-size: 14px;
|
||||
--chat--message-line-height: 1.5;
|
||||
--chat--message--bot--border: 1px solid var(--color-foreground-light);
|
||||
--chat--message--bot--color: var(--color-text-dark);
|
||||
--chat--message--user--color: var(--color-text-dark);
|
||||
--chat--message--user--background: var(--color-success-tint-1);
|
||||
--chat--message--user--border: 1px solid var(--color-success-light-2);
|
||||
|
||||
// Chat input
|
||||
--chat--input--font-size: var(--font-size-s);
|
||||
--chat--input--send--button--color: var(--color-success);
|
||||
}
|
||||
|
||||
.clickable {
|
||||
|
||||
@@ -96,22 +96,6 @@
|
||||
"activationModal.yourTriggersWillNowFire": "Your triggers will now fire production executions automatically.",
|
||||
"activationModal.yourWorkflowWillNowListenForEvents": "Your workflow will now listen for events from {serviceName} and trigger executions.",
|
||||
"activationModal.yourWorkflowWillNowRegularlyCheck": "Your workflow will now regularly check {serviceName} for events and trigger executions for them.",
|
||||
"aiAssistantChat.title": "✨ Generate workflow step with AI",
|
||||
"aiAssistantChat.greeting": "Hi {username}!",
|
||||
"aiAssistantChat.chatPlaceholder": "Enter your response...",
|
||||
"aiAssistantChat.getStarted": "Get started",
|
||||
"aiAssistantChat.initialMessage.firstStep": "What should the first step in your workflow do?",
|
||||
"aiAssistantChat.initialMessage.nextStep": "Can you describe the next step after the __{currentAction}__ action in your workflow?",
|
||||
"aiAssistantChat.response.message1": "Thanks for trying our new __Generate Workflow Step__ feature. Currently, the feature is not ready yet. We're gathering real-world prompts like yours to ensure we're creating a high-quality, valuable feature.",
|
||||
"aiAssistantChat.response.message2": "We understand this may be disappointing, but we believe it's crucial to developing the best possible feature for you and others. We’d love to invite you to be one of the first users to get their hands on the real feature once it’s ready.",
|
||||
"aiAssistantChat.response.quickReply.close": "Close chat thread",
|
||||
"aiAssistantChat.response.quickReply.signUp": "Sign up for early access",
|
||||
"aiAssistantChat.response.quickReply.giveFeedback": "Give feedback to product team",
|
||||
"aiAssistantChat.closeButtonTooltip": "Close chat",
|
||||
"aiAssistantChat.closeChatConfirmation": "Are you sure you want to end this chat session?",
|
||||
"aiAssistantChat.closeChatConfirmation.confirm": "Yes, close it",
|
||||
"aiAssistantChat.closeChatConfirmation.cancel": "No, stay",
|
||||
"aiAssistantChat.quickReply.title": "Quick reply 👇",
|
||||
"auth.changePassword": "Change password",
|
||||
"auth.changePassword.currentPassword": "Current password",
|
||||
"auth.changePassword.error": "Problem changing the password",
|
||||
@@ -966,10 +950,6 @@
|
||||
"ndv.httpRequest.credentialOnly.docsNotice": "Use the <a target=\"_blank\" href=\"{docsUrl}\">{nodeName} docs</a> to construct your request. We'll take care of the authentication part if you add a {nodeName} credential below.",
|
||||
"noTagsView.readyToOrganizeYourWorkflows": "Ready to organize your workflows?",
|
||||
"noTagsView.withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows",
|
||||
"nextStepPopup.title.firstStep": "What triggers this workflow?",
|
||||
"nextStepPopup.title.nextStep": "What happens next?",
|
||||
"nextStepPopup.option.choose": "Choose from list...",
|
||||
"nextStepPopup.option.generate": "Generate step with AI...",
|
||||
"node.thisIsATriggerNode": "This is a Trigger node. <a target=\"_blank\" href=\"https://docs.n8n.io/workflows/components/nodes/\">Learn more</a>",
|
||||
"node.activateDeactivateNode": "Activate/Deactivate Node",
|
||||
"node.changeColor": "Change color",
|
||||
@@ -2265,18 +2245,6 @@
|
||||
"importCurlParameter.showError.invalidProtocol1.title": "Use the {node} node",
|
||||
"importCurlParameter.showError.invalidProtocol2.title": "Invalid Protocol",
|
||||
"importCurlParameter.showError.invalidProtocol.message": "The HTTP node doesn’t support {protocol} requests",
|
||||
"generateCurlParameter.label": "Ask AI ✨",
|
||||
"generateCurlModal.title": "Generate HTTP Request",
|
||||
"generateCurlModal.notice.content": "This will overwrite any changes you have already made to the current node",
|
||||
"generateCurlModal.button.label": "Generate",
|
||||
"generateCurlModal.service.label": "Service",
|
||||
"generateCurlModal.service.placeholder": "Enter the name of the service",
|
||||
"generateCurlModal.request.label": "Request",
|
||||
"generateCurlModal.request.placeholder": "Describe the request you want to make",
|
||||
"generateCurlModal.invalidCurlCommand.title": "Generation failed",
|
||||
"generateCurlModal.invalidCurlCommand.message": "The AI couldn't process your request",
|
||||
"generateCurlModal.success.title": "HTTP Request filled out",
|
||||
"generateCurlModal.success.message": "Please check carefully as AI content can be inaccurate",
|
||||
"variables.heading": "Variables",
|
||||
"variables.add": "Add variable",
|
||||
"variables.add.unavailable": "Upgrade plan to keep using variables",
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import * as aiApi from '@/api/ai';
|
||||
import type { GenerateCurlPayload } from '@/api/ai';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import type { AIAssistantConnectionInfo, XYPosition } from '@/Interface';
|
||||
import { usePostHog } from './posthog.store';
|
||||
import { AI_ASSISTANT_EXPERIMENT } from '@/constants';
|
||||
|
||||
const CURRENT_POPUP_HEIGHT = 94;
|
||||
|
||||
/**
|
||||
* Calculates the position for the next step popup based on the specified element
|
||||
* so they are aligned vertically.
|
||||
*/
|
||||
const getPopupCenterPosition = (relativeElement: HTMLElement) => {
|
||||
const bounds = relativeElement.getBoundingClientRect();
|
||||
const rectMiddle = bounds.top + bounds.height / 2;
|
||||
const x = bounds.left + bounds.width + 22;
|
||||
const y = rectMiddle - CURRENT_POPUP_HEIGHT / 2;
|
||||
return [x, y] as XYPosition;
|
||||
};
|
||||
|
||||
export const useAIStore = defineStore('ai', () => {
|
||||
const rootStore = useRootStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const posthogStore = usePostHog();
|
||||
|
||||
const assistantChatOpen = ref(false);
|
||||
const nextStepPopupConfig = reactive({
|
||||
open: false,
|
||||
title: '',
|
||||
position: [0, 0] as XYPosition,
|
||||
});
|
||||
const latestConnectionInfo: Ref<AIAssistantConnectionInfo | null> = ref(null);
|
||||
const isGenerateCurlEnabled = computed(() => settingsStore.settings.ai.features.generateCurl);
|
||||
const isAssistantExperimentEnabled = computed(
|
||||
() => posthogStore.getVariant(AI_ASSISTANT_EXPERIMENT.name) === AI_ASSISTANT_EXPERIMENT.variant,
|
||||
);
|
||||
|
||||
function openNextStepPopup(title: string, relativeElement: HTMLElement) {
|
||||
nextStepPopupConfig.open = true;
|
||||
nextStepPopupConfig.title = title;
|
||||
nextStepPopupConfig.position = getPopupCenterPosition(relativeElement);
|
||||
}
|
||||
|
||||
function closeNextStepPopup() {
|
||||
nextStepPopupConfig.open = false;
|
||||
}
|
||||
|
||||
async function generateCurl(payload: GenerateCurlPayload) {
|
||||
return await aiApi.generateCurl(rootStore.restApiContext, payload);
|
||||
}
|
||||
|
||||
return {
|
||||
assistantChatOpen,
|
||||
nextStepPopupConfig,
|
||||
openNextStepPopup,
|
||||
closeNextStepPopup,
|
||||
latestConnectionInfo,
|
||||
generateCurl,
|
||||
isGenerateCurlEnabled,
|
||||
isAssistantExperimentEnabled,
|
||||
};
|
||||
});
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
SimplifiedNodeType,
|
||||
ActionsRecord,
|
||||
ToggleNodeCreatorOptions,
|
||||
AIAssistantConnectionInfo,
|
||||
NewConnectionInfo,
|
||||
} from '@/Interface';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
@@ -172,7 +172,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||
});
|
||||
}
|
||||
|
||||
function openNodeCreatorForConnectingNode(info: AIAssistantConnectionInfo) {
|
||||
function openNodeCreatorForConnectingNode(info: NewConnectionInfo) {
|
||||
const type = info.outputType ?? NodeConnectionType.Main;
|
||||
// Get the node and set it as active that new nodes
|
||||
// which get created get automatically connected
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
N8N_PRICING_PAGE_URL,
|
||||
WORKFLOW_HISTORY_VERSION_RESTORE,
|
||||
SETUP_CREDENTIALS_MODAL_KEY,
|
||||
GENERATE_CURL_MODAL_KEY,
|
||||
PROJECT_MOVE_RESOURCE_MODAL,
|
||||
PROJECT_MOVE_RESOURCE_CONFIRM_MODAL,
|
||||
} from '@/constants';
|
||||
@@ -141,13 +140,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
||||
curlCommand: '',
|
||||
},
|
||||
},
|
||||
[GENERATE_CURL_MODAL_KEY]: {
|
||||
open: false,
|
||||
data: {
|
||||
service: '',
|
||||
request: '',
|
||||
},
|
||||
},
|
||||
[LOG_STREAM_MODAL_KEY]: {
|
||||
open: false,
|
||||
data: undefined,
|
||||
|
||||
@@ -120,9 +120,6 @@
|
||||
<Suspense>
|
||||
<ContextMenu @action="onContextMenuAction" />
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<NextStepPopup v-show="isNextStepPopupVisible" @option-selected="onNextStepSelected" />
|
||||
</Suspense>
|
||||
<div v-if="!isReadOnlyRoute && !readOnlyEnv" class="workflow-execute-wrapper">
|
||||
<span
|
||||
v-if="!isManualChatOnly"
|
||||
@@ -246,7 +243,6 @@ import {
|
||||
AI_NODE_CREATOR_VIEW,
|
||||
DRAG_EVENT_DATA_KEY,
|
||||
UPDATE_WEBHOOK_ID_NODE_TYPES,
|
||||
AI_ASSISTANT_LOCAL_STORAGE_KEY,
|
||||
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT,
|
||||
} from '@/constants';
|
||||
|
||||
@@ -268,7 +264,6 @@ import Node from '@/components/Node.vue';
|
||||
import Sticky from '@/components/Sticky.vue';
|
||||
import CanvasAddButton from './CanvasAddButton.vue';
|
||||
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
|
||||
import NextStepPopup from '@/components/AIAssistantChat/NextStepPopup.vue';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import type {
|
||||
IConnection,
|
||||
@@ -314,7 +309,6 @@ import type {
|
||||
AddedNodesAndConnections,
|
||||
ToggleNodeCreatorOptions,
|
||||
IPushDataExecutionFinished,
|
||||
AIAssistantConnectionInfo,
|
||||
NodeFilterType,
|
||||
} from '@/Interface';
|
||||
|
||||
@@ -389,8 +383,6 @@ import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
import { useRunWorkflow } from '@/composables/useRunWorkflow';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import type { ProjectSharingData } from '@/types/projects.types';
|
||||
import { useAIStore } from '@/stores/ai.store';
|
||||
import { useStorage } from '@/composables/useStorage';
|
||||
import { isJSPlumbEndpointElement, isJSPlumbConnection } from '@/utils/typeGuards';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
||||
@@ -424,7 +416,6 @@ export default defineComponent({
|
||||
CanvasControls,
|
||||
ContextMenu,
|
||||
SetupWorkflowCredentialsButton,
|
||||
NextStepPopup,
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (
|
||||
@@ -587,7 +578,6 @@ export default defineComponent({
|
||||
useSourceControlStore,
|
||||
useExecutionsStore,
|
||||
useProjectsStore,
|
||||
useAIStore,
|
||||
useNpsSurveyStore,
|
||||
),
|
||||
nativelyNumberSuffixedDefaults(): string[] {
|
||||
@@ -752,16 +742,6 @@ export default defineComponent({
|
||||
isReadOnlyRoute() {
|
||||
return this.$route?.meta?.readOnlyCanvas === true;
|
||||
},
|
||||
isNextStepPopupVisible(): boolean {
|
||||
return this.aiStore.nextStepPopupConfig.open;
|
||||
},
|
||||
shouldShowNextStepDialog(): boolean {
|
||||
const userHasSeenAIAssistantExperiment =
|
||||
useStorage(AI_ASSISTANT_LOCAL_STORAGE_KEY).value === 'true';
|
||||
const experimentEnabled = this.aiStore.isAssistantExperimentEnabled;
|
||||
const isCloudDeployment = this.settingsStore.isCloudDeployment;
|
||||
return isCloudDeployment && experimentEnabled && !userHasSeenAIAssistantExperiment;
|
||||
},
|
||||
isProductionExecutionPreview(): boolean {
|
||||
return this.nodeHelpers.isProductionExecutionPreview.value;
|
||||
},
|
||||
@@ -1247,32 +1227,8 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
},
|
||||
async onCanvasAddButtonCLick(event: PointerEvent) {
|
||||
if (event) {
|
||||
if (this.shouldShowNextStepDialog) {
|
||||
const newNodeButton = (event.target as HTMLElement).closest('button');
|
||||
if (newNodeButton) {
|
||||
this.aiStore.latestConnectionInfo = null;
|
||||
this.aiStore.openNextStepPopup(
|
||||
this.$locale.baseText('nextStepPopup.title.firstStep'),
|
||||
newNodeButton,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.TRIGGER_PLACEHOLDER_BUTTON);
|
||||
return;
|
||||
}
|
||||
},
|
||||
onNextStepSelected(action: string) {
|
||||
if (action === 'choose') {
|
||||
const lastConnectionInfo = this.aiStore.latestConnectionInfo as NewConnectionInfo;
|
||||
if (lastConnectionInfo === null) {
|
||||
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.TRIGGER_PLACEHOLDER_BUTTON);
|
||||
} else {
|
||||
this.insertNodeAfterSelected(lastConnectionInfo);
|
||||
}
|
||||
}
|
||||
async onCanvasAddButtonCLick() {
|
||||
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.TRIGGER_PLACEHOLDER_BUTTON);
|
||||
},
|
||||
showTriggerCreator(source: NodeCreatorOpenSource) {
|
||||
if (this.createNodeActive) return;
|
||||
@@ -1508,7 +1464,6 @@ export default defineComponent({
|
||||
// Save the location of the mouse click
|
||||
this.lastClickPosition = this.getMousePositionWithinNodeView(e);
|
||||
if (e instanceof MouseEvent && e.button === 1) {
|
||||
this.aiStore.closeNextStepPopup();
|
||||
this.moveCanvasKeyPressed = true;
|
||||
}
|
||||
|
||||
@@ -1535,7 +1490,6 @@ export default defineComponent({
|
||||
},
|
||||
async keyDown(e: KeyboardEvent) {
|
||||
this.contextMenu.close();
|
||||
this.aiStore.closeNextStepPopup();
|
||||
|
||||
const ctrlModifier = this.deviceSupport.isCtrlKeyPressed(e) && !e.shiftKey && !e.altKey;
|
||||
const shiftModifier = e.shiftKey && !e.altKey && !this.deviceSupport.isCtrlKeyPressed(e);
|
||||
@@ -2904,7 +2858,7 @@ export default defineComponent({
|
||||
|
||||
return filter;
|
||||
},
|
||||
insertNodeAfterSelected(info: AIAssistantConnectionInfo) {
|
||||
insertNodeAfterSelected(info: NewConnectionInfo) {
|
||||
const type = info.outputType ?? NodeConnectionType.Main;
|
||||
// Get the node and set it as active that new nodes
|
||||
// which get created get automatically connected
|
||||
@@ -2982,59 +2936,12 @@ export default defineComponent({
|
||||
}
|
||||
return;
|
||||
}
|
||||
// When connection is aborted, we want to show the 'Next step' popup
|
||||
const endpointId = `${connection.parameters.nodeId}-output${connection.parameters.index}`;
|
||||
const endpoint = connection.instance.getEndpoint(endpointId);
|
||||
// First, show node creator if endpoint is not a plus endpoint
|
||||
// or if the AI Assistant experiment doesn't need to be shown to user
|
||||
if (!endpoint?.endpoint?.canvas || !this.shouldShowNextStepDialog) {
|
||||
this.insertNodeAfterSelected({
|
||||
sourceId: connection.parameters.nodeId,
|
||||
index: connection.parameters.index,
|
||||
eventSource: NODE_CREATOR_OPEN_SOURCES.NODE_CONNECTION_DROP,
|
||||
connection,
|
||||
outputType: connection.parameters.type,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Else render the popup
|
||||
const endpointElement: HTMLElement = endpoint.endpoint.canvas;
|
||||
// Use observer to trigger the popup once the endpoint is rendered back again
|
||||
// after connection drag is aborted (so we can get it's position and dimensions)
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
// Find the mutation in which the current endpoint becomes visible again
|
||||
const endpointMutation = mutations.find((mutation) => {
|
||||
const target = mutation.target;
|
||||
|
||||
return (
|
||||
isJSPlumbEndpointElement(target) &&
|
||||
target.jtk?.endpoint?.uuid === endpoint.uuid &&
|
||||
target.style.display === 'block'
|
||||
);
|
||||
});
|
||||
if (endpointMutation) {
|
||||
// When found, display the popup
|
||||
const newConnectionInfo: AIAssistantConnectionInfo = {
|
||||
sourceId: connection.parameters.nodeId,
|
||||
index: connection.parameters.index,
|
||||
eventSource: NODE_CREATOR_OPEN_SOURCES.NODE_CONNECTION_DROP,
|
||||
outputType: connection.parameters.type,
|
||||
endpointUuid: endpoint.uuid,
|
||||
stepName: endpoint.__meta.nodeName,
|
||||
};
|
||||
this.aiStore.latestConnectionInfo = newConnectionInfo;
|
||||
this.aiStore.openNextStepPopup(
|
||||
this.$locale.baseText('nextStepPopup.title.nextStep'),
|
||||
endpointElement,
|
||||
);
|
||||
observer.disconnect();
|
||||
return;
|
||||
}
|
||||
});
|
||||
observer.observe(this.$refs.nodeViewRef as HTMLElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['style'],
|
||||
subtree: true,
|
||||
this.insertNodeAfterSelected({
|
||||
sourceId: connection.parameters.nodeId,
|
||||
index: connection.parameters.index,
|
||||
eventSource: NODE_CREATOR_OPEN_SOURCES.NODE_CONNECTION_DROP,
|
||||
connection,
|
||||
outputType: connection.parameters.type,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -3562,32 +3469,13 @@ export default defineComponent({
|
||||
.forEach((endpoint) => setTimeout(() => endpoint.instance.revalidate(endpoint.element), 0));
|
||||
},
|
||||
onPlusEndpointClick(endpoint: Endpoint) {
|
||||
if (this.shouldShowNextStepDialog) {
|
||||
if (endpoint?.__meta) {
|
||||
this.aiStore.latestConnectionInfo = {
|
||||
sourceId: endpoint.__meta.nodeId,
|
||||
index: endpoint.__meta.index,
|
||||
eventSource: NODE_CREATOR_OPEN_SOURCES.PLUS_ENDPOINT,
|
||||
outputType: getEndpointScope(endpoint.scope),
|
||||
endpointUuid: endpoint.uuid,
|
||||
stepName: endpoint.__meta.nodeName,
|
||||
};
|
||||
const endpointElement = endpoint.endpoint.canvas;
|
||||
this.aiStore.openNextStepPopup(
|
||||
this.$locale.baseText('nextStepPopup.title.nextStep'),
|
||||
endpointElement,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.insertNodeAfterSelected({
|
||||
sourceId: endpoint.__meta.nodeId,
|
||||
index: endpoint.__meta.index,
|
||||
eventSource: NODE_CREATOR_OPEN_SOURCES.PLUS_ENDPOINT,
|
||||
outputType: getEndpointScope(endpoint.scope),
|
||||
endpointUuid: endpoint.uuid,
|
||||
stepName: endpoint.__meta.nodeName,
|
||||
});
|
||||
}
|
||||
this.insertNodeAfterSelected({
|
||||
sourceId: endpoint.__meta.nodeId,
|
||||
index: endpoint.__meta.index,
|
||||
eventSource: NODE_CREATOR_OPEN_SOURCES.PLUS_ENDPOINT,
|
||||
outputType: getEndpointScope(endpoint.scope),
|
||||
endpointUuid: endpoint.uuid,
|
||||
});
|
||||
},
|
||||
onAddInputEndpointClick(endpoint: Endpoint) {
|
||||
if (endpoint?.__meta) {
|
||||
|
||||
Reference in New Issue
Block a user