From e3d46bb5fb48c14d205f80a71daaff199005e802 Mon Sep 17 00:00:00 2001
From: Teknium <127238744+teknium1@users.noreply.github.com>
Date: Fri, 21 Aug 2026 12:52:30 -0700
Subject: [PATCH] feat(desktop): error card offers Nous support link on
Portal-auth sessions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Sessions running on provider 'nous' get a 'Nous support' action on the
failed-turn card, opening the portal help hub
(https://portal.nousresearch.com/help — docs, Discord, GitHub) in the
external browser. All five locales + docs updated.
---
.../assistant-ui/thread/assistant-message.tsx | 23 ++++++++++++++++++-
apps/desktop/src/i18n/ar.ts | 1 +
apps/desktop/src/i18n/en.ts | 1 +
apps/desktop/src/i18n/ja.ts | 1 +
apps/desktop/src/i18n/types.ts | 1 +
apps/desktop/src/i18n/zh-hant.ts | 1 +
apps/desktop/src/i18n/zh.ts | 1 +
website/docs/user-guide/desktop.md | 3 +++
8 files changed, 31 insertions(+), 1 deletion(-)
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 && (