fix(ai): 网络失败时显示友好提示,不再直接展示 Failed to fetch
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useDevice } from "@/contexts/DeviceContext";
|
import { useDevice } from "@/contexts/DeviceContext";
|
||||||
|
import { isLikelyNetworkFetchError } from "@/lib/networkError";
|
||||||
import { Loader2, Wifi } from "lucide-react";
|
import { Loader2, Wifi } from "lucide-react";
|
||||||
import BottomNav from "@/components/BottomNav";
|
import BottomNav from "@/components/BottomNav";
|
||||||
import localeZh from "@/locales/data-zh.json";
|
import localeZh from "@/locales/data-zh.json";
|
||||||
@@ -50,7 +51,7 @@ export default function ConnectionPlaceholder() {
|
|||||||
<>
|
<>
|
||||||
<p className="text-[15px] text-white/80 mb-2">{text.failedTitle}</p>
|
<p className="text-[15px] text-white/80 mb-2">{text.failedTitle}</p>
|
||||||
<p className="text-[13px] text-white/45 text-center max-w-xs">
|
<p className="text-[13px] text-white/45 text-center max-w-xs">
|
||||||
{error ?? text.failedDesc}
|
{error && !isLikelyNetworkFetchError(error) ? error : text.failedDesc}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[12px] text-white/30 mt-3 text-center max-w-xs">{text.failedHint}</p>
|
<p className="text-[12px] text-white/30 mt-3 text-center max-w-xs">{text.failedHint}</p>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
PeqState,
|
PeqState,
|
||||||
} from "@/lib/luxsinApi";
|
} from "@/lib/luxsinApi";
|
||||||
import { getDeviceIpFromUrl, resolveInitialDeviceIp } from "@/lib/deviceIp";
|
import { getDeviceIpFromUrl, resolveInitialDeviceIp } from "@/lib/deviceIp";
|
||||||
|
import { isLikelyNetworkFetchError } from "@/lib/networkError";
|
||||||
|
|
||||||
interface DeviceContextType {
|
interface DeviceContextType {
|
||||||
ip: string;
|
ip: string;
|
||||||
@@ -115,7 +116,11 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
|
|||||||
setApi(newApi);
|
setApi(newApi);
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(`Connection failed: ${e instanceof Error ? e.message : String(e)}`);
|
setError(
|
||||||
|
isLikelyNetworkFetchError(e)
|
||||||
|
? null
|
||||||
|
: `Connection failed: ${e instanceof Error ? e.message : String(e)}`,
|
||||||
|
);
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
} finally {
|
} finally {
|
||||||
setIsConnecting(false);
|
setIsConnecting(false);
|
||||||
@@ -151,7 +156,11 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
|
|||||||
await fetchState(apiInstance);
|
await fetchState(apiInstance);
|
||||||
await fetchPeqState(apiInstance).catch(() => undefined);
|
await fetchPeqState(apiInstance).catch(() => undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(`Refresh failed: ${e instanceof Error ? e.message : String(e)}`);
|
setError(
|
||||||
|
isLikelyNetworkFetchError(e)
|
||||||
|
? null
|
||||||
|
: `Refresh failed: ${e instanceof Error ? e.message : String(e)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [api, isConnected, ip, isDemoMode, fetchState, fetchPeqState]);
|
}, [api, isConnected, ip, isDemoMode, fetchState, fetchPeqState]);
|
||||||
|
|
||||||
|
|||||||
+15
-2
@@ -11,6 +11,7 @@ import {
|
|||||||
dbToVolume,
|
dbToVolume,
|
||||||
volumeToDb,
|
volumeToDb,
|
||||||
} from "./luxsinApi";
|
} from "./luxsinApi";
|
||||||
|
import { AI_NETWORK_ERROR_CODE, isLikelyNetworkFetchError } from "./networkError";
|
||||||
|
|
||||||
// Allow override via Vite env / localStorage; default to same origin in prod.
|
// Allow override via Vite env / localStorage; default to same origin in prod.
|
||||||
export function getAIBaseUrl(): string {
|
export function getAIBaseUrl(): string {
|
||||||
@@ -120,7 +121,13 @@ export interface QuestionPayload {
|
|||||||
// REST helpers
|
// REST helpers
|
||||||
// ============================================================
|
// ============================================================
|
||||||
async function jsonFetch<T>(url: string, init?: RequestInit): Promise<T> {
|
async function jsonFetch<T>(url: string, init?: RequestInit): Promise<T> {
|
||||||
const res = await fetch(url, init);
|
let res: Response;
|
||||||
|
try {
|
||||||
|
res = await fetch(url, init);
|
||||||
|
} catch (error) {
|
||||||
|
if (isLikelyNetworkFetchError(error)) throw new Error(AI_NETWORK_ERROR_CODE);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
@@ -293,7 +300,7 @@ export function streamQuestion(payload: QuestionPayload, cb: StreamCallbacks): A
|
|||||||
cb.onDone?.();
|
cb.onDone?.();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name === "AbortError") return;
|
if (e?.name === "AbortError") return;
|
||||||
cb.onError?.(e?.message ?? String(e));
|
cb.onError?.(isLikelyNetworkFetchError(e) ? AI_NETWORK_ERROR_CODE : (e?.message ?? String(e)));
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -472,6 +479,12 @@ export async function executeFrontendTool(
|
|||||||
return { ok: false, message: `Unsupported tool: ${block.name}` };
|
return { ok: false, message: `Unsupported tool: ${block.name}` };
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
if (isLikelyNetworkFetchError(e)) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
message: "Device is not reachable. Ask the user to reconnect the device on the same network, then retry.",
|
||||||
|
};
|
||||||
|
}
|
||||||
return { ok: false, message: e?.message ?? String(e) };
|
return { ok: false, message: e?.message ?? String(e) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/** Browser / WebView fetch failures are usually TypeError with an opaque message. */
|
||||||
|
|
||||||
|
export const AI_NETWORK_ERROR_CODE = "AI_UNREACHABLE";
|
||||||
|
|
||||||
|
const NETWORK_FETCH_HINTS = [
|
||||||
|
"failed to fetch",
|
||||||
|
"networkerror",
|
||||||
|
"network request failed",
|
||||||
|
"load failed",
|
||||||
|
"internet connection appears to be offline",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function isLikelyNetworkFetchError(error: unknown): boolean {
|
||||||
|
const name = error instanceof Error ? error.name : "";
|
||||||
|
const msg = (error instanceof Error ? error.message : String(error ?? "")).toLowerCase();
|
||||||
|
if (msg === AI_NETWORK_ERROR_CODE.toLowerCase()) return true;
|
||||||
|
if (NETWORK_FETCH_HINTS.some((hint) => msg.includes(hint))) return true;
|
||||||
|
// Some WebViews surface a bare TypeError when fetch cannot complete.
|
||||||
|
return name === "TypeError" && !msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatNetworkAwareMessage(
|
||||||
|
template: string,
|
||||||
|
error: unknown,
|
||||||
|
networkCopy: string,
|
||||||
|
): string {
|
||||||
|
const raw = error instanceof Error ? error.message : String(error ?? "");
|
||||||
|
const message = isLikelyNetworkFetchError(error) || isLikelyNetworkFetchError(raw) ? networkCopy : raw;
|
||||||
|
if (template.includes("{message}")) {
|
||||||
|
return template.split("{message}").join(String(message));
|
||||||
|
}
|
||||||
|
return isLikelyNetworkFetchError(error) || isLikelyNetworkFetchError(raw) ? networkCopy : template;
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"ipMissingDesc": "Bitte fügen Sie die Geräte-IP in der Adressleiste hinzu, bevor Sie diese Seite öffnen, zum Beispiel:",
|
"ipMissingDesc": "Bitte fügen Sie die Geräte-IP in der Adressleiste hinzu, bevor Sie diese Seite öffnen, zum Beispiel:",
|
||||||
"connecting": "Verbindung zu {ip}…",
|
"connecting": "Verbindung zu {ip}…",
|
||||||
"failedTitle": "Verbindung fehlgeschlagen",
|
"failedTitle": "Verbindung fehlgeschlagen",
|
||||||
"failedDesc": "Überprüfen Sie die Geräte-IP und Ihre Netzwerkverbindung.",
|
"failedDesc": "Das Gerät ist nicht erreichbar. Stellen Sie sicher, dass diese Seite und das Gerät im selben Netzwerk sind und die IP stimmt.",
|
||||||
"failedHint": "Geben Sie das Gerät in der URL an, z.B. ?ip=10.0.0.119"
|
"failedHint": "Geben Sie das Gerät in der URL an, z.B. ?ip=10.0.0.119"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
@@ -616,6 +616,8 @@
|
|||||||
"thinking": "Denke…",
|
"thinking": "Denke…",
|
||||||
"stopped": "(Gestoppt)",
|
"stopped": "(Gestoppt)",
|
||||||
"requestFailed": "Anfrage fehlgeschlagen. Bitte versuchen Sie es später erneut.",
|
"requestFailed": "Anfrage fehlgeschlagen. Bitte versuchen Sie es später erneut.",
|
||||||
|
"deviceUnreachable": "Das Gerät ist nicht erreichbar. Prüfen Sie, ob diese Seite und das Gerät im selben Netzwerk sind, und versuchen Sie es erneut.",
|
||||||
|
"aiUnreachable": "Der Luxsin-KI-Dienst ist nicht erreichbar. Prüfen Sie Ihre Internetverbindung und versuchen Sie es erneut.",
|
||||||
"ariaSend": "Senden",
|
"ariaSend": "Senden",
|
||||||
"ariaStop": "Stopp",
|
"ariaStop": "Stopp",
|
||||||
"ariaNewChat": "Neuer Chat",
|
"ariaNewChat": "Neuer Chat",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"ipMissingDesc": "Add the device IP in the address bar before opening this page, for example:",
|
"ipMissingDesc": "Add the device IP in the address bar before opening this page, for example:",
|
||||||
"connecting": "Connecting to {ip}…",
|
"connecting": "Connecting to {ip}…",
|
||||||
"failedTitle": "Connection failed",
|
"failedTitle": "Connection failed",
|
||||||
"failedDesc": "Check the device IP and your network connection.",
|
"failedDesc": "Can't reach the device. Make sure this page and the device are on the same network, and that the IP is correct.",
|
||||||
"failedHint": "Specify the device in the URL, e.g. ?ip=10.0.0.119"
|
"failedHint": "Specify the device in the URL, e.g. ?ip=10.0.0.119"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
@@ -616,6 +616,8 @@
|
|||||||
"thinking": "Thinking…",
|
"thinking": "Thinking…",
|
||||||
"stopped": "(Stopped)",
|
"stopped": "(Stopped)",
|
||||||
"requestFailed": "Request failed. Please try again later.",
|
"requestFailed": "Request failed. Please try again later.",
|
||||||
|
"deviceUnreachable": "Can't reach the device. Check that this page and the device are on the same network, then try again.",
|
||||||
|
"aiUnreachable": "Can't reach the Luxsin AI service. Check your internet connection and try again.",
|
||||||
"ariaSend": "Send",
|
"ariaSend": "Send",
|
||||||
"ariaStop": "Stop",
|
"ariaStop": "Stop",
|
||||||
"ariaNewChat": "New chat",
|
"ariaNewChat": "New chat",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"ipMissingDesc": "Agregue la IP del dispositivo en la barra de direcciones antes de abrir esta página, por ejemplo:",
|
"ipMissingDesc": "Agregue la IP del dispositivo en la barra de direcciones antes de abrir esta página, por ejemplo:",
|
||||||
"connecting": "Conectando a {ip}…",
|
"connecting": "Conectando a {ip}…",
|
||||||
"failedTitle": "Error de conexión",
|
"failedTitle": "Error de conexión",
|
||||||
"failedDesc": "Verifique la IP del dispositivo y su conexión de red.",
|
"failedDesc": "No se puede alcanzar el dispositivo. Compruebe que esta página y el dispositivo estén en la misma red y que la IP sea correcta.",
|
||||||
"failedHint": "Especifique el dispositivo en la URL, ej. ?ip=10.0.0.119"
|
"failedHint": "Especifique el dispositivo en la URL, ej. ?ip=10.0.0.119"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
@@ -616,6 +616,8 @@
|
|||||||
"thinking": "Pensando…",
|
"thinking": "Pensando…",
|
||||||
"stopped": "(Detenido)",
|
"stopped": "(Detenido)",
|
||||||
"requestFailed": "Error en la solicitud. Inténtelo de nuevo más tarde.",
|
"requestFailed": "Error en la solicitud. Inténtelo de nuevo más tarde.",
|
||||||
|
"deviceUnreachable": "No se puede alcanzar el dispositivo. Compruebe que esta página y el dispositivo estén en la misma red e inténtelo de nuevo.",
|
||||||
|
"aiUnreachable": "No se puede alcanzar el servicio de Luxsin AI. Compruebe su conexión a Internet e inténtelo de nuevo.",
|
||||||
"ariaSend": "Enviar",
|
"ariaSend": "Enviar",
|
||||||
"ariaStop": "Detener",
|
"ariaStop": "Detener",
|
||||||
"ariaNewChat": "Nuevo chat",
|
"ariaNewChat": "Nuevo chat",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"ipMissingDesc": "Veuillez ajouter l'IP de l'appareil dans la barre d'adresse avant d'ouvrir cette page, par exemple :",
|
"ipMissingDesc": "Veuillez ajouter l'IP de l'appareil dans la barre d'adresse avant d'ouvrir cette page, par exemple :",
|
||||||
"connecting": "Connexion à {ip}…",
|
"connecting": "Connexion à {ip}…",
|
||||||
"failedTitle": "Échec de la connexion",
|
"failedTitle": "Échec de la connexion",
|
||||||
"failedDesc": "Vérifiez l'IP de l'appareil et votre connexion réseau.",
|
"failedDesc": "Impossible d'atteindre l'appareil. Vérifiez que cette page et l'appareil sont sur le même réseau, et que l'IP est correcte.",
|
||||||
"failedHint": "Spécifiez l'appareil dans l'URL, par ex. ?ip=10.0.0.119"
|
"failedHint": "Spécifiez l'appareil dans l'URL, par ex. ?ip=10.0.0.119"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
@@ -616,6 +616,8 @@
|
|||||||
"thinking": "Réflexion…",
|
"thinking": "Réflexion…",
|
||||||
"stopped": "(Arrêté)",
|
"stopped": "(Arrêté)",
|
||||||
"requestFailed": "Échec de la requête. Veuillez réessayer plus tard.",
|
"requestFailed": "Échec de la requête. Veuillez réessayer plus tard.",
|
||||||
|
"deviceUnreachable": "Impossible d'atteindre l'appareil. Vérifiez que cette page et l'appareil sont sur le même réseau, puis réessayez.",
|
||||||
|
"aiUnreachable": "Impossible d'atteindre le service Luxsin AI. Vérifiez votre connexion Internet, puis réessayez.",
|
||||||
"ariaSend": "Envoyer",
|
"ariaSend": "Envoyer",
|
||||||
"ariaStop": "Arrêter",
|
"ariaStop": "Arrêter",
|
||||||
"ariaNewChat": "Nouveau chat",
|
"ariaNewChat": "Nouveau chat",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"ipMissingDesc": "請在網址列加入裝置 IP 參數後再開啟本頁,例如:",
|
"ipMissingDesc": "請在網址列加入裝置 IP 參數後再開啟本頁,例如:",
|
||||||
"connecting": "正在連線 {ip}…",
|
"connecting": "正在連線 {ip}…",
|
||||||
"failedTitle": "連線失敗",
|
"failedTitle": "連線失敗",
|
||||||
"failedDesc": "請檢查裝置 IP 與網路連線。",
|
"failedDesc": "無法連接裝置。請確認本頁與裝置在同一網絡,並檢查裝置 IP 是否正確。",
|
||||||
"failedHint": "透過網址列指定裝置,例如 ?ip=10.0.0.119"
|
"failedHint": "透過網址列指定裝置,例如 ?ip=10.0.0.119"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
@@ -610,6 +610,8 @@
|
|||||||
"thinking": "思考中…",
|
"thinking": "思考中…",
|
||||||
"stopped": "(已停止)",
|
"stopped": "(已停止)",
|
||||||
"requestFailed": "請求失敗,請稍後重試",
|
"requestFailed": "請求失敗,請稍後重試",
|
||||||
|
"deviceUnreachable": "無法連接裝置。請確認本頁與裝置在同一網絡後再試。",
|
||||||
|
"aiUnreachable": "無法連接 Luxsin AI 服務。請檢查網絡後再試。",
|
||||||
"ariaSend": "發送",
|
"ariaSend": "發送",
|
||||||
"ariaStop": "停止",
|
"ariaStop": "停止",
|
||||||
"ariaNewChat": "新增會話",
|
"ariaNewChat": "新增會話",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"ipMissingDesc": "请在地址栏添加设备 IP 参数后再打开本页,例如:",
|
"ipMissingDesc": "请在地址栏添加设备 IP 参数后再打开本页,例如:",
|
||||||
"connecting": "正在连接 {ip}…",
|
"connecting": "正在连接 {ip}…",
|
||||||
"failedTitle": "连接失败",
|
"failedTitle": "连接失败",
|
||||||
"failedDesc": "请检查设备 IP 与网络连接。",
|
"failedDesc": "无法连接设备。请确认本页与设备在同一网络,并检查设备 IP 是否正确。",
|
||||||
"failedHint": "通过地址栏指定设备,例如 ?ip=10.0.0.119"
|
"failedHint": "通过地址栏指定设备,例如 ?ip=10.0.0.119"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
@@ -611,6 +611,8 @@
|
|||||||
"thinking": "思考中…",
|
"thinking": "思考中…",
|
||||||
"stopped": "(已停止)",
|
"stopped": "(已停止)",
|
||||||
"requestFailed": "请求失败,请稍后重试",
|
"requestFailed": "请求失败,请稍后重试",
|
||||||
|
"deviceUnreachable": "无法连接设备。请确认本页与设备在同一网络后再试。",
|
||||||
|
"aiUnreachable": "无法连接 Luxsin AI 服务。请检查网络后再试。",
|
||||||
"ariaSend": "发送",
|
"ariaSend": "发送",
|
||||||
"ariaStop": "停止",
|
"ariaStop": "停止",
|
||||||
"ariaNewChat": "新建会话",
|
"ariaNewChat": "新建会话",
|
||||||
|
|||||||
+13
-10
@@ -39,6 +39,7 @@ import {
|
|||||||
streamQuestion,
|
streamQuestion,
|
||||||
updateMessageApplied,
|
updateMessageApplied,
|
||||||
} from "@/lib/aiApi";
|
} from "@/lib/aiApi";
|
||||||
|
import { formatNetworkAwareMessage, isLikelyNetworkFetchError } from "@/lib/networkError";
|
||||||
|
|
||||||
import { buildPeqSvgCurveData, getFilterType, PeqBandForResponse } from "@/lib/peqAudio";
|
import { buildPeqSvgCurveData, getFilterType, PeqBandForResponse } from "@/lib/peqAudio";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -305,7 +306,7 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
|||||||
const list = await listChats(mac);
|
const list = await listChats(mac);
|
||||||
setChats(list);
|
setChats(list);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.error(formatText(aiText.loadHistoryFailed, { message: e?.message ?? e }));
|
toast.error(formatNetworkAwareMessage(aiText.loadHistoryFailed, e, aiText.aiUnreachable));
|
||||||
}
|
}
|
||||||
}, [aiText, mac]);
|
}, [aiText, mac]);
|
||||||
|
|
||||||
@@ -357,7 +358,7 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
|||||||
setChatId(id);
|
setChatId(id);
|
||||||
if (opts?.closePanel !== false) setHistoryOpen(false);
|
if (opts?.closePanel !== false) setHistoryOpen(false);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.error(formatText(aiText.loadChatFailed, { message: e?.message ?? e }));
|
toast.error(formatNetworkAwareMessage(aiText.loadChatFailed, e, aiText.aiUnreachable));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[aiText, mapRowsToUI, settleRiskyConfirm],
|
[aiText, mapRowsToUI, settleRiskyConfirm],
|
||||||
@@ -429,7 +430,7 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
|||||||
newChat();
|
newChat();
|
||||||
toast.success(aiText.cleared);
|
toast.success(aiText.cleared);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.error(formatText(aiText.clearFailed, { message: e?.message ?? e }));
|
toast.error(formatNetworkAwareMessage(aiText.clearFailed, e, aiText.aiUnreachable));
|
||||||
}
|
}
|
||||||
}, [aiText, chatId, newChat]);
|
}, [aiText, chatId, newChat]);
|
||||||
|
|
||||||
@@ -445,7 +446,7 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
|||||||
toast.success(aiText.history.deleted);
|
toast.success(aiText.history.deleted);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.error(
|
toast.error(
|
||||||
formatText(aiText.history.deleteFailed, { message: e?.message ?? e }),
|
formatNetworkAwareMessage(aiText.history.deleteFailed, e, aiText.aiUnreachable),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [aiText, chatId]);
|
}, [aiText, chatId]);
|
||||||
@@ -514,14 +515,15 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
|||||||
msgCount: deviceState.msgCount ?? 0,
|
msgCount: deviceState.msgCount ?? 0,
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
const text = isLikelyNetworkFetchError(e) ? aiText.deviceUnreachable : aiText.requestFailed;
|
||||||
updateAssistant((m) => ({
|
updateAssistant((m) => ({
|
||||||
...m,
|
...m,
|
||||||
pending: false,
|
pending: false,
|
||||||
error: true,
|
error: true,
|
||||||
text: aiText.requestFailed,
|
text,
|
||||||
}));
|
}));
|
||||||
setSending(false);
|
setSending(false);
|
||||||
toast.error(formatText(aiText.requestFailed, { message: e?.message ?? e }));
|
toast.error(text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,15 +562,16 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
|||||||
try {
|
try {
|
||||||
await sendToolResult(block.id, result);
|
await sendToolResult(block.id, result);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.error(formatText(aiText.toolResultFailed, { message: e?.message ?? e }));
|
toast.error(formatNetworkAwareMessage(aiText.toolResultFailed, e, aiText.aiUnreachable));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (msg) => {
|
onError: (msg) => {
|
||||||
|
const text = isLikelyNetworkFetchError(msg) ? aiText.aiUnreachable : (msg || aiText.requestFailed);
|
||||||
updateAssistant((m) => ({
|
updateAssistant((m) => ({
|
||||||
...m,
|
...m,
|
||||||
pending: false,
|
pending: false,
|
||||||
error: true,
|
error: true,
|
||||||
text: m.text || msg || aiText.requestFailed,
|
text: m.text || text,
|
||||||
}));
|
}));
|
||||||
setSending(false);
|
setSending(false);
|
||||||
},
|
},
|
||||||
@@ -779,7 +782,7 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
|||||||
nextApplied ? aiText.optimize.applySuccess : aiText.optimize.rollbackSuccess,
|
nextApplied ? aiText.optimize.applySuccess : aiText.optimize.rollbackSuccess,
|
||||||
);
|
);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.error(formatText(aiText.optimize.applyFailed, { message: e?.message ?? e }));
|
toast.error(formatNetworkAwareMessage(aiText.optimize.applyFailed, e, aiText.deviceUnreachable));
|
||||||
} finally {
|
} finally {
|
||||||
setApplyingId(null);
|
setApplyingId(null);
|
||||||
}
|
}
|
||||||
@@ -812,7 +815,7 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
|||||||
await updateSetting(bypassOn ? { peqEnable: 1, dsp_enable: 1 } : { peqEnable: 1 });
|
await updateSetting(bypassOn ? { peqEnable: 1, dsp_enable: 1 } : { peqEnable: 1 });
|
||||||
await executeApplyToggle(msg);
|
await executeApplyToggle(msg);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.error(formatText(aiText.optimize.applyFailed, { message: e?.message ?? e }));
|
toast.error(formatNetworkAwareMessage(aiText.optimize.applyFailed, e, aiText.deviceUnreachable));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[aiText, deviceState?.dsp_enable, executeApplyToggle, updateSetting],
|
[aiText, deviceState?.dsp_enable, executeApplyToggle, updateSetting],
|
||||||
|
|||||||
Reference in New Issue
Block a user