Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Settings } from './pages/Settings';
import { Setup } from './pages/Setup';
import { Startup } from './pages/Startup';
import { GatewaySessions } from './pages/GatewaySessions';
import { GatewayRecoveryOverlay } from '@/components/gateway/GatewayRecoveryOverlay';
import { useSettingsStore } from './stores/settings';
import { useUpdateStore } from './stores/update';
import { useBootstrapStore } from './stores/bootstrap';
Expand Down Expand Up @@ -245,6 +246,7 @@ function App() {
</Routes>

{showSettingsOverlay && <Settings />}
<GatewayRecoveryOverlay />

{/* Global toast notifications */}
<Toaster
Expand Down
100 changes: 100 additions & 0 deletions src/components/gateway/GatewayRecoveryOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useEffect, useState } from 'react';
import { AlertCircle } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import geeclawIcon from '@/assets/logo.svg';
import { Button } from '@/components/ui/button';
import { useGatewayStore } from '@/stores/gateway';
import {
DEFAULT_GATEWAY_RECOVERY_UI_STATE,
getNextGatewayRecoveryUiState,
} from './recovery-state';

export function GatewayRecoveryOverlay() {
const { t } = useTranslation('setup');
const restartGateway = useGatewayStore((state) => state.restart);
const [uiState, setUiState] = useState(() => (
getNextGatewayRecoveryUiState(
DEFAULT_GATEWAY_RECOVERY_UI_STATE,
useGatewayStore.getState().status,
)
));

useEffect(() => {
return useGatewayStore.subscribe((state) => {
setUiState((current) => getNextGatewayRecoveryUiState(current, state.status));
});
}, []);

if (uiState.phase === 'idle') {
return null;
}

const handleRetry = async () => {
await restartGateway();
};

if (uiState.phase === 'recovering') {
return (
<div className="pointer-events-auto fixed inset-0 z-[80] flex items-center justify-center bg-background/92 px-6 backdrop-blur-sm">
<div
role="dialog"
aria-modal="true"
aria-live="polite"
className="flex w-full max-w-2xl flex-col items-center justify-center text-center"
>
<img src={geeclawIcon} alt="GeeClaw" className="h-20 w-20" />
<h2 className="mt-6 text-balance text-[clamp(2rem,4.8vw,3.6rem)] font-semibold tracking-[-0.05em] text-foreground dark:text-white/95">
{t('startup.gatewayRecovery.recovering.title')}
</h2>
<p className="mt-4 text-base leading-7 text-muted-foreground dark:text-white/72">
{t('startup.gatewayRecovery.recovering.caption')}
</p>
</div>
</div>
);
}

return (
<div className="startup-shell fixed inset-0 z-[85] overflow-hidden bg-background text-foreground">
<div className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="absolute left-[-12%] top-[-16%] h-[28rem] w-[28rem] rounded-full bg-[radial-gradient(circle,rgba(203,232,255,0.9)_0%,rgba(203,232,255,0.22)_42%,rgba(203,232,255,0)_72%)] blur-2xl" />
<div className="absolute right-[-10%] top-[8%] h-[24rem] w-[24rem] rounded-full bg-[radial-gradient(circle,rgba(193,222,255,0.78)_0%,rgba(193,222,255,0.18)_46%,rgba(193,222,255,0)_72%)] blur-2xl" />
<div className="absolute bottom-[-14%] left-1/2 h-[22rem] w-[34rem] -translate-x-1/2 rounded-full bg-[radial-gradient(circle,rgba(255,229,210,0.28)_0%,rgba(255,229,210,0.1)_42%,rgba(255,229,210,0)_76%)] blur-3xl" />
</div>

<div className="relative flex h-full items-center justify-center px-6 py-10 md:px-10">
<div
role="alertdialog"
aria-modal="true"
className="modal-card-surface mx-auto w-full max-w-[40rem] rounded-[2rem] border p-8 backdrop-blur-xl"
>
<div className="flex items-start gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center">
<AlertCircle className="h-8 w-8 text-red-500" />
</div>
<div className="min-w-0 flex-1">
<h2 className="text-3xl font-semibold tracking-[-0.04em] text-foreground dark:text-white/95">
{t('startup.gatewayRecovery.error.title')}
</h2>
<p className="mt-3 text-base leading-7 text-muted-foreground dark:text-white/78">
{t('startup.gatewayRecovery.error.body')}
</p>
{uiState.error ? (
<pre className="mt-5 whitespace-pre-wrap break-words rounded-lg bg-slate-950/92 p-4 text-xs leading-6 text-slate-100 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]">
{uiState.error}
</pre>
) : null}
<Button
type="button"
className="modal-primary-button mt-6 h-12 px-6 text-sm"
onClick={() => void handleRetry()}
>
{t('startup.gatewayRecovery.error.retry')}
</Button>
</div>
</div>
</div>
</div>
</div>
);
}
54 changes: 54 additions & 0 deletions src/components/gateway/recovery-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { GatewayStatus } from '@/types/gateway';

export type GatewayRecoveryPhase = 'idle' | 'recovering' | 'failed';

export interface GatewayRecoveryUiState {
phase: GatewayRecoveryPhase;
sessionActive: boolean;
error: string | null;
}

export const DEFAULT_GATEWAY_RECOVERY_UI_STATE: GatewayRecoveryUiState = {
phase: 'idle',
sessionActive: false,
error: null,
};

export function getNextGatewayRecoveryUiState(
current: GatewayRecoveryUiState,
status: GatewayStatus,
): GatewayRecoveryUiState {
switch (status.state) {
case 'running':
return DEFAULT_GATEWAY_RECOVERY_UI_STATE;
case 'starting':
case 'reconnecting':
return {
phase: 'recovering',
sessionActive: true,
error: null,
};
case 'error':
if (!current.sessionActive) {
return current;
}
return {
phase: 'failed',
sessionActive: true,
error: status.error ?? current.error ?? null,
};
case 'stopped':
default:
if (!current.sessionActive) {
return DEFAULT_GATEWAY_RECOVERY_UI_STATE;
}
if (current.phase === 'failed') {
return current;
}
return {
phase: 'recovering',
sessionActive: true,
error: current.error,
};
}
}
4 changes: 2 additions & 2 deletions src/components/update/UpdateAnnouncementDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ export function UpdateAnnouncementDialog() {
)}

{status === 'downloading' && progress && (
<div className="mt-4 rounded-2xl border border-black/6 bg-background/70 px-4 py-3 text-sm text-muted-foreground dark:border-white/10">
<div className="mt-4 rounded-2xl border border-black/6 bg-background/70 px-4 py-3 mr-5 text-sm text-muted-foreground dark:border-white/10">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The mr-5 class was added here, but it seems unrelated to the main goals of this pull request, which are focused on the gateway recovery overlay and i18n copy. This might be an unintentional change that could affect the layout. If this margin is not required, it would be best to remove it to avoid unintended side effects.

Suggested change
<div className="mt-4 rounded-2xl border border-black/6 bg-background/70 px-4 py-3 mr-5 text-sm text-muted-foreground dark:border-white/10">
<div className="mt-4 rounded-2xl border border-black/6 bg-background/70 px-4 py-3 text-sm text-muted-foreground dark:border-white/10">

{t('updates.dialog.progress', {
percent: Math.round(progress.percent),
})}
</div>
)}

{status === 'downloaded' && autoInstallCountdown != null && autoInstallCountdown >= 0 && (
<div className="mt-4 rounded-2xl border border-black/6 bg-background/70 px-4 py-3 text-sm text-muted-foreground dark:border-white/10">
<div className="mt-4 rounded-2xl border border-black/6 bg-background/70 px-4 py-3 mr-5 text-sm text-muted-foreground dark:border-white/10">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the change above, the mr-5 class was added here. This seems unrelated to the main goals of this pull request. If this margin is not required, it would be best to remove it to avoid unintended layout side effects.

Suggested change
<div className="mt-4 rounded-2xl border border-black/6 bg-background/70 px-4 py-3 mr-5 text-sm text-muted-foreground dark:border-white/10">
<div className="mt-4 rounded-2xl border border-black/6 bg-background/70 px-4 py-3 text-sm text-muted-foreground dark:border-white/10">

{t('updates.status.autoInstalling', { seconds: autoInstallCountdown })}
</div>
)}
Expand Down
13 changes: 13 additions & 0 deletions src/i18n/locales/en/setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,19 @@
"title": "GeeClaw needs attention",
"body": "We could not finish preparing the app. Try again, or check the details below.",
"retry": "Try again"
},
"gatewayRecovery": {
"recovering": {
"eyebrow": "Gateway recovery",
"title": "Restarting OpenClaw",
"body": "Some features are temporarily unavailable We will resume automatically once the gateway is back",
"caption": "This usually takes around ten seconds Please keep this window open"
},
"error": {
"title": "The OpenClaw Gateway could not recover",
"body": "We tried to reconnect the OpenClaw Gateway, but this attempt did not succeed. You can retry now or inspect the details below.",
"retry": "Retry"
}
}
}
}
4 changes: 2 additions & 2 deletions src/i18n/locales/zh/chat.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"gatewayNotRunning": "网关未运行",
"gatewayRequired": "OpenClaw 网关需要运行才能使用聊天。它将自动启动,或者您可以从设置中启动。",
"gatewayNotRunning": "OpenClaw未运行",
"gatewayRequired": "OpenClaw 需要运行才能使用聊天,您可以从设置中启动",
"welcome": {
"title": "GeeClaw",
"subtitle": "您的 AI 龙虾助手已就绪,在下方开始对话。",
Expand Down
10 changes: 5 additions & 5 deletions src/i18n/locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"skills": "技能",
"channels": "聊天频道",
"onlineCount": "{{count}} 在线",
"offlineCount": "· {{count}} 在线",
"offlineCount": "{{count}} 在线",
"dashboard": "仪表盘",
"defaultAgent": "默认",
"agentMainSessionHint": "点击进入会话",
Expand Down Expand Up @@ -60,9 +60,9 @@
"saving": "保存中..."
},
"gateway": {
"notRunning": "网关未运行",
"notRunningDesc": "OpenClaw 网关需要运行才能使用此功能。它将自动启动,或者您可以从设置中启动。",
"warning": "网关未运行。"
"notRunning": "OpenClaw 服务未运行",
"notRunningDesc": "OpenClaw 服务需要运行才能使用此功能,您可以从设置中启动",
"warning": "OpenClaw 服务未运行"
},
"tray": {
"tooltipRunning": "GeeClaw - 网关运行中",
Expand All @@ -77,4 +77,4 @@
"checkUpdates": "检查更新...",
"quit": "退出 GeeClaw"
}
}
}
16 changes: 8 additions & 8 deletions src/i18n/locales/zh/cron.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"subtitle": "通过设置定时任务实现工作自动化处理",
"newTask": "新建任务",
"refresh": "刷新",
"gatewayWarning": "网关未运行。没有活跃的网关,无法管理定时任务",
"gatewayWarning": "OpenClaw 服务未运行,无法管理定时任务",
"stats": {
"total": "任务总数",
"active": "已启用",
Expand All @@ -12,7 +12,7 @@
},
"empty": {
"title": "暂无定时任务",
"description": "创建定时任务以自动化 AI 工作流。任务可以在指定时间发送消息、运行查询或执行操作",
"description": "创建定时任务以自动化 AI 工作流。任务可以在指定时间发送消息、运行查询或执行操作",
"create": "创建第一个任务"
},
"card": {
Expand All @@ -29,16 +29,16 @@
"back": "返回",
"taskFallback": "定时任务",
"pageTitle": "运行记录",
"subtitle": "查看该定时任务的执行历史与只读消息记录",
"subtitle": "查看该定时任务的执行历史与只读消息记录",
"runListTitle": "运行历史",
"runListDescription": "按最近执行时间倒序展示。",
"messagesTitle": "消息记录",
"messagesDescription": "从左侧选择一次运行查看详细消息",
"messagesDescription": "从左侧选择一次运行查看详细消息",
"emptyTitle": "暂无运行记录",
"emptyDescription": "这个任务还没有产生任何可查看的执行记录",
"emptyDescription": "这个任务还没有产生任何可查看的执行记录",
"selectRun": "请先从左侧选择一次运行。",
"messagesEmpty": "这次运行暂时没有可显示的消息记录",
"noSummary": "这次运行没有记录摘要",
"messagesEmpty": "这次运行暂时没有可显示的消息记录",
"noSummary": "这次运行没有记录摘要",
"durationUnknown": "无耗时信息",
"status": {
"ok": "成功",
Expand Down Expand Up @@ -116,4 +116,4 @@
"dailyAt": "每天 {{time}}",
"unknown": "未知"
}
}
}
6 changes: 3 additions & 3 deletions src/i18n/locales/zh/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"pageSubtitle": "查看网关状态、频道连接与常用快捷操作",
"gateway": "网关",
"pageSubtitle": "查看 OpenClaw 状态、频道连接与常用快捷操作",
"gateway": "OpenClaw 服务",
"channels": "频道",
"skills": "技能",
"uptime": "运行时间",
Expand All @@ -9,7 +9,7 @@
"connectedOf": "已连接",
"enabledOf": "已启用",
"sinceRestart": "自上次重启",
"gatewayNotRunning": "网关未运行",
"gatewayNotRunning": "OpenClaw 服务未运行",
"quickActions": {
"title": "快捷操作",
"description": "常用任务和快捷方式",
Expand Down
15 changes: 14 additions & 1 deletion src/i18n/locales/zh/setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,19 @@
"title": "GeeClaw 需要处理一下",
"body": "我们暂时没能完成应用准备。您可以重试,或查看下面的详情。",
"retry": "重试"
},
"gatewayRecovery": {
"recovering": {
"eyebrow": "网关恢复中",
"title": "正在重启 OpenClaw",
"body": "部分功能暂时不可用 我们会在网关恢复后自动继续",
"caption": "这通常只需要十几秒,请保持窗口打开"
},
"error": {
"title": "OpenClaw 服务未能恢复",
"body": "我们尝试重新连接 OpenClaw 网关,但这次没有成功。您可以直接重试,或查看下面的详情。",
"retry": "重试"
}
}
}
}
}
20 changes: 10 additions & 10 deletions src/i18n/locales/zh/skills.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"title": "技能",
"subtitle": "安装专业技能,解锁 AI 超能力,同时启用过多技能(超过20个),会造成 AI 能力指数级下降,请按需启用",
"subtitle": "安装专业技能,解锁 AI 超能力,同时启用过多技能(超过20个),会造成 AI 能力指数级下降,请按需启用",
"refresh": "刷新",
"openFolder": "打开技能文件夹",
"gatewayWarning": "网关未运行。没有活跃的网关,无法加载技能",
"gatewayWarning": "OpenClaw 服务未运行,无法加载技能",
"tabs": {
"installed": "已安装",
"marketplace": "市场"
Expand Down Expand Up @@ -51,13 +51,13 @@
"unsupportedOs": "当前系统不受支持",
"apiKey": "API 密钥",
"apiKeyPlaceholder": "输入 API 密钥(可选)",
"apiKeyDesc": "此技能的主要 API 密钥。如果不需要或在别处配置,请留空",
"apiKeyDesc": "此技能的主要 API 密钥。如果不需要或在别处配置,请留空",
"envVars": "环境变量",
"addVariable": "添加变量",
"noEnvVars": "未配置环境变量。",
"keyPlaceholder": "键名 (例如 BASE_URL)",
"valuePlaceholder": "值",
"envNote": "注意:键名为空的行将在保存时自动移除",
"envNote": "注意:键名为空的行将在保存时自动移除",
"saving": "保存中...",
"saveConfig": "保存配置",
"configSaved": "配置已保存",
Expand Down Expand Up @@ -94,7 +94,7 @@
"failedUninstall": "卸载失败",
"installedUnavailable": "技能已安装,但仍缺少运行所需依赖",
"enableRequirementsMissing": "技能所需依赖尚未满足",
"failedFolderNotFound": "技能文件夹尚不存在,请先安装一个技能",
"failedFolderNotFound": "技能文件夹尚不存在,请先安装一个技能",
"copiedPath": "路径已复制",
"failedCopyPath": "复制路径失败",
"failedOpenActualFolder": "打开技能实际目录失败",
Expand All @@ -112,16 +112,16 @@
"featured": "精选",
"install": "安装",
"securityNote": "安装前请点击技能卡片,在 ClawHub 上查看其文档和安全信息。",
"manualInstallHint": "遇到网络问题?您可以随时从 ClawHub.ai 下载技能压缩包,并将其解压至 \"{{path}}\" 目录来完成手动安装",
"manualInstallHint": "遇到网络问题?您可以随时从 ClawHub.ai 下载技能压缩包,并将其解压至 \"{{path}}\" 目录来完成手动安装",
"searching": "正在搜索 ClawHub...",
"loading": "正在加载市场...",
"noResults": "未找到匹配的技能",
"emptyPrompt": "搜索新技能以扩展您的能力",
"searchError": "ClawHub 搜索失败。请检查您的连接或安装",
"noResults": "未找到匹配的技能",
"emptyPrompt": "搜索新技能以扩展您的能力",
"searchError": "ClawHub 搜索失败。请检查您的连接或安装",
"loadError": "加载市场目录失败。",
"skillHub": {
"title": "推荐安装 SkillHub",
"description": "SkillHub 会优先使用国内镜像源,通常能显著降低技能安装失败和请求频繁的问题。点击后 GeeClaw 会自动准备 Python 并安装 SkillHub CLI",
"description": "SkillHub 会优先使用国内镜像源,通常能显著降低技能安装失败和请求频繁的问题。点击后 GeeClaw 会自动准备 Python 并安装 SkillHub CLI",
"install": "安装 SkillHub",
"enabled": "SkillHub 加速已启用(v{{version}})"
},
Expand Down
Loading
Loading