File size: 2,966 Bytes
94004a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// MIRROR OF backend/core/error_docs_map.py — keep in sync.
// The Python test_keys_match_python_map / test_keys_match_taxonomy guards
// the 4-class taxonomy on the backend; the `_KEYS` array below is the
// TS-side anchor (the keys-sync test imports it and asserts equality).
//
// This `BASE` constant is the SECOND hardcoded URL drift site: the
// canonical Python-side resolver is `backend/core/links.py`
// (`PROJECT_REPO_BLOB_MAIN`). The TS half runs in the browser and can't
// read `pyproject.toml`, so it gets a hand-maintained mirror. Centralising
// the URL on the TS side is a v0.4 concern — the milestone accepts the
// drift risk and relies on the keys-sync test + threat-model T-02-01
// to bound the blast radius.

import { openExternal } from '../api/external';

const BASE = 'https://github.com/debpalash/OmniVoice-Studio/blob/main';

export const ERROR_DOCS: Record<string, string> = {
  GATEKEEPER_QUARANTINE: `${BASE}/docs/install/macos.md#gatekeeper-quarantine`,
  APPIMAGE_WEBKIT_WHITESCREEN: `${BASE}/docs/install/linux.md#appimage-white-screen-on-fedora-44--ubuntu-2404`,
  PKG_RESOURCES_MISSING: `${BASE}/docs/install/troubleshooting.md#pkg_resources-missing`,
  HF_AUTH_FAILED: `${BASE}/docs/setup/huggingface-token.md`,
};

export const DEFAULT_DOCS = `${BASE}/docs/install/troubleshooting.md`;

// Locked taxonomy keys — Phase 5 bug reporter consumes this exact set.
// Adding a 5th class is a contract change; update the Python map at the
// same time (`backend/core/error_docs_map.py`).
export const ERROR_CLASS_KEYS = [
  'GATEKEEPER_QUARANTINE',
  'APPIMAGE_WEBKIT_WHITESCREEN',
  'PKG_RESOURCES_MISSING',
  'HF_AUTH_FAILED',
] as const;

export type ErrorClass = (typeof ERROR_CLASS_KEYS)[number];

/**
 * Heuristic error message → ErrorClass classifier. ErrorBoundary uses this
 * when the thrown Error doesn't carry an explicit `errorClass` property.
 */
export function classifyError(error: unknown): ErrorClass | null {
  const message =
    (error as { message?: string } | null | undefined)?.message ?? String(error ?? '');
  const lower = message.toLowerCase();
  if (/pkg_resources/.test(lower)) return 'PKG_RESOURCES_MISSING';
  if (/\b401\b/.test(lower) || /hfhub|hfhubhttp/.test(lower) || /unauthorized/.test(lower)) {
    return 'HF_AUTH_FAILED';
  }
  if (/webkit/.test(lower) || /white\s*screen/.test(lower)) {
    return 'APPIMAGE_WEBKIT_WHITESCREEN';
  }
  if (/quarantine/.test(lower) || /gatekeeper/.test(lower)) return 'GATEKEEPER_QUARANTINE';
  return null;
}

export function urlFor(errorClass: ErrorClass | null | undefined): string {
  if (!errorClass) return DEFAULT_DOCS;
  return ERROR_DOCS[errorClass] ?? DEFAULT_DOCS;
}

/** Open the docs URL for `errorClass` in the user's default browser. */
export async function openDocsFor(
  errorClass: ErrorClass | string | null | undefined,
): Promise<void> {
  const url = ERROR_DOCS[errorClass as string] ?? DEFAULT_DOCS;
  await openExternal(url);
}