diff --git a/apps/desktop/src/components/assistant-ui/thread/assistant-message.tsx b/apps/desktop/src/components/assistant-ui/thread/assistant-message.tsx index a8b9729e78..4b9c12c881 100644 --- a/apps/desktop/src/components/assistant-ui/thread/assistant-message.tsx +++ b/apps/desktop/src/components/assistant-ui/thread/assistant-message.tsx @@ -31,6 +31,7 @@ import { Codicon } from '@/components/ui/codicon' import { CopyButton } from '@/components/ui/copy-button' import { useI18n } from '@/i18n' import { type ErrorSurface, formatErrorDiagnostics } from '@/lib/error-surface' +import { openExternalLink } from '@/lib/external-link' import { triggerHaptic } from '@/lib/haptics' import { AudioLines, GitForkIcon, Loader2Icon, RefreshCwIcon, SmilePlusIcon, VolumeXIcon, XIcon } from '@/lib/icons' import { extractPreviewTargets } from '@/lib/preview-targets' @@ -39,13 +40,17 @@ import { useEnterAnimation } from '@/lib/use-enter-animation' import { cn } from '@/lib/utils' import { playSpeechText, stopVoicePlayback } from '@/lib/voice-playback' import { notifyError } from '@/store/notifications' -import { $currentModel } from '@/store/session' +import { $currentModel, $currentProvider } from '@/store/session' import { $voicePlayback } from '@/store/voice-playback' // Stable empty identity for the settled-parts selector — a fresh [] per render // would re-derive the changed-files card on every message re-render. const EMPTY_PARTS: readonly unknown[] = [] +// Nous Portal's help hub (docs, Discord, GitHub, support) — the error card's +// "Nous support" action for sessions running on Portal auth. +const NOUS_SUPPORT_URL = 'https://portal.nousresearch.com/help' + // PERF: hoisted to module scope so the element OBJECT is identical on every // render of every assistant message. React bails out of re-rendering a child // whose element identity is unchanged, so a status flip on the message root @@ -490,6 +495,13 @@ const ErrorRecoveryActions: FC = () => { // child mounts only when one is (see SwitchProviderAction). const inRouter = useInRouterContext() const model = useStore($currentModel) + const provider = useStore($currentProvider) + + // Nous Portal users get a direct line to Nous support (docs/Discord/GitHub + // hub behind their portal account). Provider === 'nous' means the session + // runs on Portal auth — the "logged in" signal that's available + // synchronously without an extra billing query per error card. + const showNousSupport = provider.trim().toLowerCase() === 'nous' // Retry = assistant-ui reload (same wiring as the footer's refresh action): // re-runs the failed turn's prompt in place. Suppressed when the classifier @@ -541,6 +553,15 @@ const ErrorRecoveryActions: FC = () => { )} {showSwitchProvider && inRouter && } + {showNousSupport && ( + + )} {window.hermesDesktop?.logsRoot && (