feat(desktop): error card offers Nous support link on Portal-auth sessions

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.
This commit is contained in:
Teknium
2026-08-21 12:52:30 -07:00
parent 892790f980
commit e3d46bb5fb
8 changed files with 31 additions and 1 deletions
@@ -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 = () => {
</ActionBarPrimitive.Reload>
)}
{showSwitchProvider && inRouter && <SwitchProviderAction label={copy.errorSwitchProvider} />}
{showNousSupport && (
<button
className="aui-error-action"
onClick={() => openExternalLink(NOUS_SUPPORT_URL)}
type="button"
>
{copy.errorNousSupport}
</button>
)}
{window.hermesDesktop?.logsRoot && (
<button className="aui-error-action" onClick={() => void openLogs()} type="button">
{copy.errorOpenLogs}
+1
View File
@@ -2449,6 +2449,7 @@ export const ar = defineLocale({
errorSwitchProvider: 'تبديل المزوّد',
errorOpenLogs: 'فتح السجلات',
errorOpenLogsFailed: 'تعذّر فتح مجلد السجلات',
errorNousSupport: 'دعم Nous',
errorCopyDiagnostics: 'نسخ التشخيصات',
filesChanged: count => `${count} ملفات تم تغييرها`,
reviewChanges: 'مراجعة',
+1
View File
@@ -3094,6 +3094,7 @@ export const en: Translations = {
errorSwitchProvider: 'Switch provider',
errorOpenLogs: 'Open logs',
errorOpenLogsFailed: 'Could not open the logs folder',
errorNousSupport: 'Nous support',
errorCopyDiagnostics: 'Copy diagnostics',
filesChanged: count => (count === 1 ? '1 file changed' : `${count} files changed`),
reviewChanges: 'Review',
+1
View File
@@ -2746,6 +2746,7 @@ export const ja = defineLocale({
errorSwitchProvider: 'プロバイダーを切り替え',
errorOpenLogs: 'ログを開く',
errorOpenLogsFailed: 'ログフォルダを開けませんでした',
errorNousSupport: 'Nousサポート',
errorCopyDiagnostics: '診断情報をコピー',
filesChanged: count => `${count} 件のファイルを変更`,
reviewChanges: 'レビュー',
+1
View File
@@ -2665,6 +2665,7 @@ export interface Translations {
errorSwitchProvider: string
errorOpenLogs: string
errorOpenLogsFailed: string
errorNousSupport: string
errorCopyDiagnostics: string
filesChanged: (count: number) => string
reviewChanges: string
+1
View File
@@ -2656,6 +2656,7 @@ export const zhHant = defineLocale({
errorSwitchProvider: '切換服務商',
errorOpenLogs: '開啟日誌',
errorOpenLogsFailed: '無法開啟日誌資料夾',
errorNousSupport: 'Nous 支援',
errorCopyDiagnostics: '複製診斷資訊',
filesChanged: count => `${count} 個檔案已變更`,
reviewChanges: '檢視',
+1
View File
@@ -3258,6 +3258,7 @@ export const zh: Translations = {
errorSwitchProvider: '切换服务商',
errorOpenLogs: '打开日志',
errorOpenLogsFailed: '无法打开日志文件夹',
errorNousSupport: 'Nous 支持',
errorCopyDiagnostics: '复制诊断信息',
filesChanged: count => `${count} 个文件已更改`,
reviewChanges: '查看',
+3
View File
@@ -420,6 +420,9 @@ generic error toast. The card offers recovery actions matched to the failure:
deterministically reproduce the failure, e.g. a content-policy rejection).
- **Switch provider** — jumps to Settings → Models for provider, endpoint,
auth, and billing failures.
- **Nous support** — shown when the session runs on Nous Portal auth; opens
the [portal help hub](https://portal.nousresearch.com/help) (docs, Discord,
GitHub) in your browser.
- **Open logs** — opens `HERMES_HOME/logs` in your file manager.
- **Copy diagnostics** — copies a compact plain-text summary (layer, code,
provider/model, error message) you can paste into a bug report or Discord.