feat(editor): Remove AI Error Debugging (#9337)

This commit is contained in:
Milorad FIlipović
2024-05-08 14:13:47 +02:00
committed by GitHub
parent f64a41d617
commit cda062bde6
14 changed files with 5 additions and 356 deletions

View File

@@ -143,7 +143,6 @@ export const defaultSettings: IN8nUISettings = {
ai: {
enabled: false,
provider: '',
errorDebugging: false,
},
workflowHistory: {
pruneTime: 0,

View File

@@ -2,14 +2,6 @@ import type { IRestApiContext, Schema } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IDataObject } from 'n8n-workflow';
export interface DebugErrorPayload {
error: Error;
}
export interface DebugErrorResponse {
message: string;
}
export interface GenerateCurlPayload {
service: string;
request: string;
@@ -47,18 +39,6 @@ export async function generateCodeForPrompt(
} as IDataObject);
}
export const debugError = async (
context: IRestApiContext,
payload: DebugErrorPayload,
): Promise<DebugErrorResponse> => {
return await makeRestApiRequest(
context,
'POST',
'/ai/debug-error',
payload as unknown as IDataObject,
);
};
export const generateCurl = async (
context: IRestApiContext,
payload: GenerateCurlPayload,

View File

@@ -1,9 +1,7 @@
<script lang="ts" setup>
import Feedback from '@/components/Feedback.vue';
import { useI18n } from '@/composables/useI18n';
import type { PropType } from 'vue';
import { computed, ref } from 'vue';
import { useTelemetry } from '@/composables/useTelemetry';
import { computed } from 'vue';
import { useClipboard } from '@/composables/useClipboard';
import { useToast } from '@/composables/useToast';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
@@ -19,9 +17,7 @@ import type {
NodeOperationError,
} from 'n8n-workflow';
import { sanitizeHtml } from '@/utils/htmlUtils';
import { useAIStore } from '@/stores/ai.store';
import { MAX_DISPLAY_DATA_SIZE } from '@/constants';
import VueMarkdown from 'vue-markdown-render';
import type { BaseTextKey } from '@/plugins/i18n';
const props = defineProps({
@@ -34,16 +30,10 @@ const props = defineProps({
const clipboard = useClipboard();
const toast = useToast();
const i18n = useI18n();
const telemetry = useTelemetry();
const nodeTypesStore = useNodeTypesStore();
const ndvStore = useNDVStore();
const rootStore = useRootStore();
const aiStore = useAIStore();
const isLoadingErrorDebugging = ref(false);
const errorDebuggingMessage = ref('');
const errorDebuggingFeedback = ref<'positive' | 'negative' | undefined>();
const displayCause = computed(() => {
return JSON.stringify(props.error.cause).length < MAX_DISPLAY_DATA_SIZE;
@@ -116,45 +106,6 @@ const prepareRawMessages = computed(() => {
return returnData;
});
async function onDebugError() {
try {
isLoadingErrorDebugging.value = true;
telemetry.track(
'User clicked AI error helper button',
{
node_type: props.error.node?.type,
error_title: props.error.message,
},
{ withPostHog: true },
);
const { message } = await aiStore.debugError({ error: props.error });
errorDebuggingMessage.value = message;
} catch (error) {
toast.showError(error, i18n.baseText('generic.error'));
} finally {
isLoadingErrorDebugging.value = false;
}
}
async function onDebugErrorRegenerate() {
errorDebuggingMessage.value = '';
errorDebuggingFeedback.value = undefined;
await onDebugError();
telemetry.track('User regenerated error debugging AI hint', {
node_type: props.error.node?.type,
error_title: props.error.message,
});
}
async function onErrorDebuggingFeedback(feedback: 'positive' | 'negative') {
telemetry.track('User responded error debugging AI hint', {
helpful: feedback === 'positive',
node_type: props.error.node?.type,
error_title: props.error.message,
});
}
function nodeVersionTag(nodeType: NodeError['node']): string {
if (!nodeType || ('hidden' in nodeType && nodeType.hidden)) {
return i18n.baseText('nodeSettings.deprecated');
@@ -429,26 +380,6 @@ function copySuccess() {
></div>
</div>
<N8nCard
v-if="isLoadingErrorDebugging || errorDebuggingMessage"
class="node-error-view__debugging mb-s"
>
<span v-if="isLoadingErrorDebugging">
<N8nSpinner class="mr-3xs" />
{{ i18n.baseText('nodeErrorView.debugError.loading') }}
</span>
<VueMarkdown v-else :source="errorDebuggingMessage" />
<div v-if="errorDebuggingMessage" class="node-error-view__feedback-toolbar">
<Feedback v-model="errorDebuggingFeedback" @update:model-value="onErrorDebuggingFeedback" />
<N8nTooltip :content="i18n.baseText('nodeErrorView.debugError.feedback.reload')">
<span class="node-error-view__feedback-button" @click="onDebugErrorRegenerate">
<FontAwesomeIcon icon="sync-alt" />
</span>
</N8nTooltip>
</div>
</N8nCard>
<div class="node-error-view__info">
<div class="node-error-view__info-header">
<p class="node-error-view__info-title">

View File

@@ -1066,9 +1066,6 @@
"nodeErrorView.description.pairedItemMultipleMatches": "An expression here won't work because it uses <code>.item</code> and n8n can't figure out the <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/”>matching item</a>. (There are multiple possible matches) <br/><br/>Try using <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code> or <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-code-node/”>reference a different node</a>.",
"nodeErrorView.description.pairedItemMultipleMatchesCodeNode": "The code here won't work because it uses <code>.item</code> and n8n can't figure out the <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/”>matching item</a>. (There are multiple possible matches) <br/><br/>Try using <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code> or <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-code-node/”>reference a different node</a>.",
"nodeErrorView.description.pairedItemPinned": "The <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/”>item-matching</a> data in that node may be stale. It is needed by an expression in this node that uses <code>.item</code>.",
"nodeErrorView.debugError.button": "Ask AI ✨",
"nodeErrorView.debugError.loading": "Asking AI.. ✨",
"nodeErrorView.debugError.feedback.reload": "Regenerate answer",
"nodeHelpers.credentialsUnset": "Credentials for '{credentialType}' are not set.",
"nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.",
"nodeSettings.alwaysOutputData.displayName": "Always Output Data",

View File

@@ -1,77 +0,0 @@
import { setActivePinia, createPinia } from 'pinia';
import { useAIStore } from '@/stores/ai.store';
import * as aiApi from '@/api/ai';
vi.mock('@/api/ai', () => ({
debugError: vi.fn(),
generateCurl: vi.fn(),
}));
vi.mock('@/stores/n8nRoot.store', () => ({
useRootStore: () => ({
getRestApiContext: {
/* Mocked context */
},
}),
}));
vi.mock('@/stores/settings.store', () => ({
useSettingsStore: () => ({
settings: {
ai: {
features: {
errorDebugging: false,
generateCurl: false,
},
},
},
}),
}));
describe('useAIStore', () => {
beforeEach(() => {
setActivePinia(createPinia());
});
describe('isErrorDebuggingEnabled', () => {
it('reflects error debugging setting from settingsStore', () => {
const aiStore = useAIStore();
expect(aiStore.isErrorDebuggingEnabled).toBe(false);
});
});
describe('debugError()', () => {
it('calls aiApi.debugError with correct parameters and returns expected result', async () => {
const mockResult = { message: 'This is an example' };
const aiStore = useAIStore();
const payload = {
error: new Error('Test error'),
};
vi.mocked(aiApi.debugError).mockResolvedValue(mockResult);
const result = await aiStore.debugError(payload);
expect(aiApi.debugError).toHaveBeenCalledWith({}, payload);
expect(result).toEqual(mockResult);
});
});
describe('debugError()', () => {
it('calls aiApi.debugError with correct parameters and returns expected result', async () => {
const mockResult = { curl: 'curl -X GET https://n8n.io', metadata: {} };
const aiStore = useAIStore();
const payload = {
service: 'OpenAI',
request: 'Create user message saying "Hello World"',
};
vi.mocked(aiApi.generateCurl).mockResolvedValue(mockResult);
const result = await aiStore.generateCurl(payload);
expect(aiApi.generateCurl).toHaveBeenCalledWith({}, payload);
expect(result).toEqual(mockResult);
});
});
});

View File

@@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import * as aiApi from '@/api/ai';
import type { DebugErrorPayload, GenerateCurlPayload } from '@/api/ai';
import type { GenerateCurlPayload } from '@/api/ai';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useSettingsStore } from '@/stores/settings.store';
import { computed, reactive, ref } from 'vue';
@@ -35,7 +35,6 @@ export const useAIStore = defineStore('ai', () => {
position: [0, 0] as XYPosition,
});
const latestConnectionInfo: Ref<AIAssistantConnectionInfo | null> = ref(null);
const isErrorDebuggingEnabled = computed(() => settingsStore.settings.ai.features.errorDebugging);
const isGenerateCurlEnabled = computed(() => settingsStore.settings.ai.features.generateCurl);
const isAssistantExperimentEnabled = computed(
() => posthogStore.getVariant(AI_ASSISTANT_EXPERIMENT.name) === AI_ASSISTANT_EXPERIMENT.variant,
@@ -51,17 +50,11 @@ export const useAIStore = defineStore('ai', () => {
nextStepPopupConfig.open = false;
}
async function debugError(payload: DebugErrorPayload) {
return await aiApi.debugError(rootStore.getRestApiContext, payload);
}
async function generateCurl(payload: GenerateCurlPayload) {
return await aiApi.generateCurl(rootStore.getRestApiContext, payload);
}
return {
isErrorDebuggingEnabled,
debugError,
assistantChatOpen,
nextStepPopupConfig,
openNextStepPopup,