feat(editor): Remove AI Error Debugging (#9337)
This commit is contained in:
committed by
GitHub
parent
f64a41d617
commit
cda062bde6
@@ -143,7 +143,6 @@ export const defaultSettings: IN8nUISettings = {
|
||||
ai: {
|
||||
enabled: false,
|
||||
provider: '',
|
||||
errorDebugging: false,
|
||||
},
|
||||
workflowHistory: {
|
||||
pruneTime: 0,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user