diff --git a/src/components/builder/AppBuilder.vue b/src/components/builder/AppBuilder.vue index 9a2c5ed340..fdc5266fcf 100644 --- a/src/components/builder/AppBuilder.vue +++ b/src/components/builder/AppBuilder.vue @@ -160,7 +160,7 @@ function handleClick(e: MouseEvent) { else appModeStore.selectedOutputs.splice(index, 1) return } - if (!isSelectInputsMode.value) return + if (!isSelectInputsMode.value || widget.options.canvasOnly) return const index = appModeStore.selectedInputs.findIndex( ([nodeId, widgetName]) => node.id == nodeId && widget.name === widgetName diff --git a/src/components/common/Dialogue.vue b/src/components/common/Dialogue.vue new file mode 100644 index 0000000000..63ea2b310d --- /dev/null +++ b/src/components/common/Dialogue.vue @@ -0,0 +1,51 @@ + + + + + + + + + + + {{ title }} + + + + + + + + + + + diff --git a/src/components/error/ErrorOverlay.vue b/src/components/error/ErrorOverlay.vue index aefda39435..be599a3995 100644 --- a/src/components/error/ErrorOverlay.vue +++ b/src/components/error/ErrorOverlay.vue @@ -50,7 +50,9 @@ {{ t('g.dismiss') }} - {{ t('errorOverlay.seeErrors') }} + {{ + appMode ? t('linearMode.error.goto') : t('errorOverlay.seeErrors') + }} @@ -69,6 +71,8 @@ import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useErrorGroups } from '@/components/rightSidePanel/errors/useErrorGroups' +defineProps<{ appMode?: boolean }>() + const { t } = useI18n() const executionErrorStore = useExecutionErrorStore() const rightSidePanelStore = useRightSidePanelStore() @@ -94,6 +98,7 @@ function dismiss() { } function seeErrors() { + canvasStore.linearMode = false if (canvasStore.canvas) { canvasStore.canvas.deselectAll() canvasStore.updateSelectedItems() diff --git a/src/components/rightSidePanel/errors/useErrorGroups.ts b/src/components/rightSidePanel/errors/useErrorGroups.ts index 2fdf2eab44..71781886fb 100644 --- a/src/components/rightSidePanel/errors/useErrorGroups.ts +++ b/src/components/rightSidePanel/errors/useErrorGroups.ts @@ -1,5 +1,5 @@ -import { computed, reactive, ref, watch } from 'vue' -import type { Ref } from 'vue' +import { computed, reactive, ref, toValue, watch } from 'vue' +import type { MaybeRefOrGetter } from 'vue' import Fuse from 'fuse.js' import type { IFuseOptions } from 'fuse.js' @@ -227,7 +227,7 @@ function searchErrorGroups(groups: ErrorGroup[], query: string) { } export function useErrorGroups( - searchQuery: Ref, + searchQuery: MaybeRefOrGetter, t: (key: string) => string ) { const executionErrorStore = useExecutionErrorStore() @@ -584,7 +584,7 @@ export function useErrorGroups( }) const filteredGroups = computed(() => { - const query = searchQuery.value.trim() + const query = toValue(searchQuery).trim() return searchErrorGroups(tabErrorGroups.value, query) }) diff --git a/src/components/ui/ZoomPane.vue b/src/components/ui/ZoomPane.vue index e79e64b724..f3cd775cf0 100644 --- a/src/components/ui/ZoomPane.vue +++ b/src/components/ui/ZoomPane.vue @@ -24,7 +24,7 @@ function handleWheel(e: WheelEvent) { let dragging = false function handleDown(e: PointerEvent) { - if (e.button !== 0) return + if (e.button !== 0 && e.button !== 1) return const zoomPaneEl = zoomPane.value if (!zoomPaneEl) return diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 582e3dffb3..8dd9548992 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -3180,6 +3180,7 @@ "cancelThisRun": "Cancel this run", "deleteAllAssets": "Delete all assets from this run", "hasCreditCost": "Requires additional credits", + "viewGraph": "View node graph", "welcome": { "title": "App Mode", "message": "A simplified view that hides the node graph so you can focus on creating.", @@ -3224,6 +3225,19 @@ "outputPlaceholder": "Output nodes will show up here", "outputRequiredPlaceholder": "At least one node is required" }, + "error": { + "header": "This app encountered an error", + "log": "Error Logs", + "mobileFixable": "Check {0} for errors", + "requiresGraph": "Something went wrong during generation. This could be due to invalid hidden inputs, missing resources, or workflow configuration issues.", + "promptVisitGraph": "View the node graph to see the full error.", + "getHelp": "For help, view our {0}, {1}, or {2} with the copied error.", + "goto": "Show errors in graph", + "github": "submit a GitHub issue", + "guide": "troubleshooting guide", + "support": "contact our support", + "promptShow": "Show error report" + }, "queue": { "clickToClear": "Click to clear queue", "clear": "Clear queue" diff --git a/src/renderer/extensions/linearMode/MobileDisplay.vue b/src/renderer/extensions/linearMode/MobileDisplay.vue index d529c416c0..87b6695229 100644 --- a/src/renderer/extensions/linearMode/MobileDisplay.vue +++ b/src/renderer/extensions/linearMode/MobileDisplay.vue @@ -15,7 +15,9 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import LinearControls from '@/renderer/extensions/linearMode/LinearControls.vue' import LinearPreview from '@/renderer/extensions/linearMode/LinearPreview.vue' +import MobileError from '@/renderer/extensions/linearMode/MobileError.vue' import { useColorPaletteService } from '@/services/colorPaletteService' +import { useExecutionErrorStore } from '@/stores/executionErrorStore' import { useQueueStore } from '@/stores/queueStore' import { useMenuItemStore } from '@/stores/menuItemStore' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' @@ -31,6 +33,7 @@ const canvasStore = useCanvasStore() const colorPaletteService = useColorPaletteService() const colorPaletteStore = useColorPaletteStore() const { isLoggedIn } = useCurrentUser() +const executionErrorStore = useExecutionErrorStore() const { t } = useI18n() const { commandIdToMenuItem } = useMenuItemStore() const queueStore = useQueueStore() @@ -40,7 +43,7 @@ const { toggle: toggleFullscreen } = useFullscreen(undefined, { autoExit: true }) -const activeIndex = ref(2) +const activeIndex = ref(1) const sliderPaneRef = useTemplateRef('sliderPaneRef') const sliderWidth = computed(() => sliderPaneRef.value?.offsetWidth) @@ -192,7 +195,11 @@ const menuEntries = computed(() => [ - + + (() => [ + +import { computed } from 'vue' +import { useI18n } from 'vue-i18n' + +import Dialogue from '@/components/common/Dialogue.vue' +import { useErrorGroups } from '@/components/rightSidePanel/errors/useErrorGroups' +import Button from '@/components/ui/button/Button.vue' +import { useAppMode } from '@/composables/useAppMode' +import { useCopyToClipboard } from '@/composables/useCopyToClipboard' +import { useExternalLink } from '@/composables/useExternalLink' +import { buildSupportUrl } from '@/platform/support/config' +import { useAppModeStore } from '@/stores/appModeStore' +import { useExecutionErrorStore } from '@/stores/executionErrorStore' + +defineEmits<{ navigateControls: [] }>() + +const { t } = useI18n() +const appModeStore = useAppModeStore() +const { setMode } = useAppMode() +const executionErrorStore = useExecutionErrorStore() +const { buildDocsUrl, staticUrls } = useExternalLink() +const { allErrorGroups } = useErrorGroups('', t) +const { copyToClipboard } = useCopyToClipboard() + +const guideUrl = buildDocsUrl('troubleshooting/overview', { + includeLocale: true +}) +const supportUrl = buildSupportUrl() + +const inputNodeIds = computed(() => { + const ids = new Set() + for (const [id] of appModeStore.selectedInputs) ids.add(String(id)) + return ids +}) + +const accessibleNodeErrors = computed(() => + Object.keys(executionErrorStore.lastNodeErrors ?? {}).filter((k) => + inputNodeIds.value.has(k) + ) +) +const accessibleErrors = computed(() => + accessibleNodeErrors.value.flatMap((k) => + executionErrorStore.lastNodeErrors![k].errors.flatMap((error) => { + const { extra_info } = error + if (!extra_info) return [] + + const selectedInput = appModeStore.selectedInputs.find( + ([id, name]) => id == k && extra_info.input_name === name + ) + if (!selectedInput) return [] + + return [`${selectedInput[1]}: ${error.message}`] + }) + ) +) +const allErrors = computed(() => + allErrorGroups.value.flatMap((group) => { + if (group.type !== 'execution') return [group.title] + + return group.cards.flatMap((c) => + c.errors.map((e) => + e.details + ? `${c.title} (${e.details}): ${e.message}` + : `${c.title}: ${e.message}` + ) + ) + }) +) + +function copy(obj: unknown) { + copyToClipboard(JSON.stringify(obj)) +} + + + + + {{ t('linearMode.error.header') }} + + + + {{ t('linearMode.mobileControls') }} + + + + + + + + + + + + + + + + {{ t('linearMode.error.promptShow') }} + + + + + + + + + + + {{ t('g.close') }} + + + {{ t('importFailed.copyError') }} + + + + + + + + + + + + + + + + {{ t('g.dismiss') }} + + + {{ t('linearMode.viewGraph') }} + + + {{ t('importFailed.copyError') }} + + + + + diff --git a/src/views/LinearView.vue b/src/views/LinearView.vue index 0ec133b991..95cde1fdc5 100644 --- a/src/views/LinearView.vue +++ b/src/views/LinearView.vue @@ -9,6 +9,7 @@ import { computed, useTemplateRef } from 'vue' import AppBuilder from '@/components/builder/AppBuilder.vue' import AppModeToolbar from '@/components/appMode/AppModeToolbar.vue' import ExtensionSlot from '@/components/common/ExtensionSlot.vue' +import ErrorOverlay from '@/components/error/ErrorOverlay.vue' import TopbarBadges from '@/components/topbar/TopbarBadges.vue' import TopbarSubscribeButton from '@/components/topbar/TopbarSubscribeButton.vue' import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue' @@ -156,6 +157,7 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef') +
+ + + + + +