Skip to content

Commit 4d68337

Browse files
克隆(宗可龙)claude
andcommitted
feat(local-chat): show friendly warning card when API key is missing
Replaces the raw OpenClaw error ("Agent failed before reply: No API key found for provider…") with a friendly yellow warning card that explains the issue in plain language and links directly to /workspace/settings?tab=providers for easy configuration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 76ca389 commit 4d68337

File tree

3 files changed

+113
-2
lines changed

3 files changed

+113
-2
lines changed

apps/web/src/i18n/locales/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,10 @@ const en = {
12981298
"localChat.creatingDefaultBot": "Creating your bot…",
12991299
"localChat.createDefaultBotError": "Failed to create bot",
13001300
"localChat.retryCreateBot": "Retry",
1301+
"localChat.missingApiKey.title": "No API Key configured",
1302+
"localChat.missingApiKey.description":
1303+
"Please configure your LLM API key to use the chat.",
1304+
"localChat.missingApiKey.goToSettings": "Go to Settings",
13011305
} as const;
13021306

13031307
export default en;

apps/web/src/i18n/locales/zh-CN.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,10 @@ const zhCN = {
12271227
"localChat.creatingDefaultBot": "正在创建机器人…",
12281228
"localChat.createDefaultBotError": "创建机器人失败",
12291229
"localChat.retryCreateBot": "重试",
1230+
"localChat.missingApiKey.title": "未配置 API Key",
1231+
"localChat.missingApiKey.description":
1232+
"请先配置 LLM API Key,才能使用聊天功能。",
1233+
"localChat.missingApiKey.goToSettings": "前往设置",
12301234
} as const;
12311235

12321236
export default zhCN;

apps/web/src/pages/local-chat.tsx

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ const SESSION_DISCOVERY_INTERVAL_MS = 500;
105105
/** Maximum number of session-discovery attempts after send */
106106
const SESSION_DISCOVERY_MAX_ATTEMPTS = 6;
107107

108+
/** Patterns that identify the "missing API key" error from OpenClaw */
109+
const MISSING_API_KEY_PATTERNS = [
110+
"⚠️ Agent failed before reply: No API key found for provider",
111+
"No API key found for provider",
112+
"missing_api_key",
113+
];
114+
115+
function isMissingApiKeyError(text: string): boolean {
116+
return MISSING_API_KEY_PATTERNS.some((p) =>
117+
text.toLowerCase().includes(p.toLowerCase()),
118+
);
119+
}
120+
108121
// ---------------------------------------------------------------------------
109122
// Helpers
110123
// ---------------------------------------------------------------------------
@@ -630,8 +643,90 @@ function TypingIndicator() {
630643
);
631644
}
632645

646+
/** Renders a friendly "No API Key" warning card instead of a raw error */
647+
function ApiKeyWarningCard({
648+
t,
649+
}: { t: ReturnType<typeof useTranslation>["t"] }) {
650+
return (
651+
<div className="flex items-start gap-3">
652+
<img
653+
src={BOT_AVATAR}
654+
alt=""
655+
className="h-9 w-9 shrink-0 object-contain -ml-1"
656+
/>
657+
<div className="flex max-w-[44rem] flex-col gap-2">
658+
<div
659+
style={{
660+
background: "#FFF7E6",
661+
border: "1px solid #FFD591",
662+
borderRadius: 12,
663+
padding: "14px 16px",
664+
}}
665+
>
666+
<div className="flex items-center gap-2 mb-2">
667+
<svg
668+
style={{ color: "#FA8C16", width: 18, height: 18, flexShrink: 0 }}
669+
viewBox="0 0 24 24"
670+
fill="none"
671+
stroke="currentColor"
672+
strokeWidth="2"
673+
strokeLinecap="round"
674+
strokeLinejoin="round"
675+
aria-hidden="true"
676+
>
677+
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
678+
<path d="M12 9v4" />
679+
<path d="M12 17h.01" />
680+
</svg>
681+
<span
682+
style={{
683+
fontWeight: 600,
684+
fontSize: 13,
685+
color: "#431907",
686+
}}
687+
>
688+
{t("localChat.missingApiKey.title")}
689+
</span>
690+
</div>
691+
<p
692+
style={{
693+
color: "#7A4A0E",
694+
fontSize: 13,
695+
margin: "0 0 10px 26px",
696+
lineHeight: 1.5,
697+
}}
698+
>
699+
{t("localChat.missingApiKey.description")}
700+
</p>
701+
<a
702+
href="/workspace/settings?tab=providers"
703+
style={{
704+
display: "inline-block",
705+
marginLeft: 26,
706+
background: "#FA8C16",
707+
border: "none",
708+
borderRadius: 6,
709+
color: "#fff",
710+
fontSize: 12,
711+
fontWeight: 500,
712+
padding: "4px 12px",
713+
textDecoration: "none",
714+
cursor: "pointer",
715+
}}
716+
>
717+
{t("localChat.missingApiKey.goToSettings")}
718+
</a>
719+
</div>
720+
</div>
721+
</div>
722+
);
723+
}
724+
633725
/** Renders a single chat message — supports text, images, and file cards */
634-
function ChatBubble({ msg }: { msg: ChatMsg }) {
726+
function ChatBubble({
727+
msg,
728+
t,
729+
}: { msg: ChatMsg; t: ReturnType<typeof useTranslation>["t"] }) {
635730
const isBot = msg.role === "assistant";
636731
const time = formatTs(msg.timestamp);
637732

@@ -645,6 +740,14 @@ function ChatBubble({ msg }: { msg: ChatMsg }) {
645740
);
646741
if (!hasContent) return null;
647742

743+
// Detect and render the missing-API-key error as a friendly warning card
744+
const firstTextBlock = blocks.find((b) => b.kind === "text");
745+
const rawText =
746+
typeof firstTextBlock?.text === "string" ? firstTextBlock.text : "";
747+
if (isMissingApiKeyError(rawText)) {
748+
return <ApiKeyWarningCard t={t} />;
749+
}
750+
648751
return (
649752
<div
650753
className={cn(
@@ -1471,7 +1574,7 @@ export function LocalChatPage() {
14711574
<div className="mx-auto flex w-full max-w-[920px] flex-col gap-5">
14721575
{/* biome-ignore lint/style/noNonNullAssertion: messages is guaranteed non-null in this branch */}
14731576
{messages!.map((msg) => (
1474-
<ChatBubble key={msg.id} msg={msg} />
1577+
<ChatBubble key={msg.id} msg={msg} t={t} />
14751578
))}
14761579
{waitingReply && <TypingIndicator />}
14771580
<div ref={endRef} />

0 commit comments

Comments
 (0)