fix: update backoff configuration and improve CLI detection messages

- Added installation guides for manual configuration in DroidToolCard.js and other tool cards to assist users in setting up the necessary CLI tools.
This commit is contained in:
decolua
2026-04-17 12:51:08 +07:00
parent 333af110ed
commit 6fb78b597d
7 changed files with 95 additions and 47 deletions

View File

@@ -31,7 +31,7 @@ export const DEFAULT_ERROR_MESSAGES = {
// Exponential backoff config for rate limits
export const BACKOFF_CONFIG = {
base: 1000,
max: 3 * 60 * 1000,
max: 4 * 60 * 1000,
maxLevel: 15
};

View File

@@ -2,12 +2,13 @@ import { ERROR_RULES, BACKOFF_CONFIG, TRANSIENT_COOLDOWN_MS } from "../config/er
/**
* Calculate exponential backoff cooldown for rate limits (429)
* Level 0: 1s, Level 1: 2s, Level 2: 4s... → max 2 min
* Level 1: 1s, Level 2: 2s, Level 3: 4s... → max 4 min
* @param {number} backoffLevel - Current backoff level
* @returns {number} Cooldown in milliseconds
*/
export function getQuotaCooldown(backoffLevel = 0) {
const cooldown = BACKOFF_CONFIG.base * Math.pow(2, backoffLevel);
const level = Math.max(0, backoffLevel - 1);
const cooldown = BACKOFF_CONFIG.base * Math.pow(2, level);
return Math.min(cooldown, BACKOFF_CONFIG.max);
}
@@ -29,7 +30,7 @@ export function checkFallbackError(status, errorText, backoffLevel = 0) {
if (rule.text && lowerError && lowerError.includes(rule.text)) {
if (rule.backoff) {
const newLevel = Math.min(backoffLevel + 1, BACKOFF_CONFIG.maxLevel);
return { shouldFallback: true, cooldownMs: getQuotaCooldown(backoffLevel), newBackoffLevel: newLevel };
return { shouldFallback: true, cooldownMs: getQuotaCooldown(newLevel), newBackoffLevel: newLevel };
}
return { shouldFallback: true, cooldownMs: rule.cooldownMs };
}
@@ -38,7 +39,7 @@ export function checkFallbackError(status, errorText, backoffLevel = 0) {
if (rule.status && rule.status === status) {
if (rule.backoff) {
const newLevel = Math.min(backoffLevel + 1, BACKOFF_CONFIG.maxLevel);
return { shouldFallback: true, cooldownMs: getQuotaCooldown(backoffLevel), newBackoffLevel: newLevel };
return { shouldFallback: true, cooldownMs: getQuotaCooldown(newLevel), newBackoffLevel: newLevel };
}
return { shouldFallback: true, cooldownMs: rule.cooldownMs };
}

View File

@@ -253,14 +253,16 @@ export default function ClaudeToolCard({
{!checkingClaude && claudeStatus && !claudeStatus.installed && (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">Claude CLI not detected locally</p>
<p className="text-sm text-text-muted">Manual configuration is still available if 9router is deployed on a remote server.</p>
<div className="flex flex-col gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<div className="flex items-start gap-3">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">Claude CLI not detected locally</p>
<p className="text-sm text-text-muted">Manual configuration is still available if 9router is deployed on a remote server.</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" onClick={() => setShowManualConfigModal(true)}>
<div className="flex items-center gap-2 pl-9">
<Button variant="secondary" size="sm" onClick={() => setShowManualConfigModal(true)} className="!bg-yellow-500/20 !border-yellow-500/40 !text-yellow-700 dark:!text-yellow-300 hover:!bg-yellow-500/30">
<span className="material-symbols-outlined text-[18px] mr-1">content_copy</span>
Manual Config
</Button>

View File

@@ -220,16 +220,24 @@ model = "${effectiveSubagentModel}"
{!checkingCodex && codexStatus && !codexStatus.installed && (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">Codex CLI not installed</p>
<p className="text-sm text-text-muted">Please install Codex CLI to use auto-apply feature.</p>
<div className="flex flex-col gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<div className="flex items-start gap-3">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">Codex CLI not detected locally</p>
<p className="text-sm text-text-muted">Manual configuration is still available if 9router is deployed on a remote server.</p>
</div>
</div>
<div className="flex items-center gap-2 pl-9">
<Button variant="secondary" size="sm" onClick={() => setShowManualConfigModal(true)} className="!bg-yellow-500/20 !border-yellow-500/40 !text-yellow-700 dark:!text-yellow-300 hover:!bg-yellow-500/30">
<span className="material-symbols-outlined text-[18px] mr-1">content_copy</span>
Manual Config
</Button>
<Button variant="outline" size="sm" onClick={() => setShowInstallGuide(!showInstallGuide)}>
<span className="material-symbols-outlined text-[18px] mr-1">{showInstallGuide ? "expand_less" : "help"}</span>
{showInstallGuide ? "Hide" : "How to Install"}
</Button>
</div>
<Button variant="outline" size="sm" onClick={() => setShowInstallGuide(!showInstallGuide)}>
<span className="material-symbols-outlined text-[18px] mr-1">{showInstallGuide ? "expand_less" : "help"}</span>
{showInstallGuide ? "Hide" : "How to Install"}
</Button>
</div>
{showInstallGuide && (
<div className="p-4 bg-surface border border-border rounded-lg">

View File

@@ -28,6 +28,7 @@ export default function DroidToolCard({
const [modalOpen, setModalOpen] = useState(false);
const [modelAliases, setModelAliases] = useState({});
const [showManualConfigModal, setShowManualConfigModal] = useState(false);
const [showInstallGuide, setShowInstallGuide] = useState(false);
const [customBaseUrl, setCustomBaseUrl] = useState("");
const hasInitializedModel = useRef(false);
@@ -246,12 +247,38 @@ export default function DroidToolCard({
)}
{!checkingDroid && droidStatus && !droidStatus.installed && (
<div className="flex items-center gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">Factory Droid CLI not installed</p>
<p className="text-sm text-text-muted">Please install Factory Droid CLI to use this feature.</p>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<div className="flex items-start gap-3">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">Factory Droid CLI not detected locally</p>
<p className="text-sm text-text-muted">Manual configuration is still available if 9router is deployed on a remote server.</p>
</div>
</div>
<div className="flex items-center gap-2 pl-9">
<Button variant="secondary" size="sm" onClick={() => setShowManualConfigModal(true)} className="!bg-yellow-500/20 !border-yellow-500/40 !text-yellow-700 dark:!text-yellow-300 hover:!bg-yellow-500/30">
<span className="material-symbols-outlined text-[18px] mr-1">content_copy</span>
Manual Config
</Button>
<Button variant="outline" size="sm" onClick={() => setShowInstallGuide(!showInstallGuide)}>
<span className="material-symbols-outlined text-[18px] mr-1">{showInstallGuide ? "expand_less" : "help"}</span>
{showInstallGuide ? "Hide" : "How to Install"}
</Button>
</div>
</div>
{showInstallGuide && (
<div className="p-4 bg-surface border border-border rounded-lg">
<h4 className="font-medium mb-3">Installation Guide</h4>
<div className="space-y-3 text-sm">
<div>
<p className="text-text-muted mb-1">macOS / Linux / Windows:</p>
<code className="block px-3 py-2 bg-black/5 dark:bg-white/5 rounded font-mono text-xs">curl -fsSL https://app.factory.ai/cli | sh</code>
</div>
<p className="text-text-muted">After installation, run <code className="px-1 bg-black/5 dark:bg-white/5 rounded">droid</code> to verify.</p>
</div>
</div>
)}
</div>
)}

View File

@@ -254,19 +254,21 @@ export default function OpenClawToolCard({
)}
{!checkingOpenclaw && openclawStatus && !openclawStatus.installed && (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">Open Claw CLI not installed</p>
<p className="text-sm text-text-muted">Please install Open Claw CLI to use this feature.</p>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<div className="flex items-start gap-3">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">Open Claw CLI not detected locally</p>
<p className="text-sm text-text-muted">Manual configuration is still available if 9router is deployed on a remote server.</p>
</div>
</div>
<div className="flex items-center gap-2 pl-9">
<Button variant="secondary" size="sm" onClick={() => setShowManualConfigModal(true)} className="!bg-yellow-500/20 !border-yellow-500/40 !text-yellow-700 dark:!text-yellow-300 hover:!bg-yellow-500/30">
<span className="material-symbols-outlined text-[18px] mr-1">content_copy</span>
Manual Config
</Button>
</div>
</div>
{/* Show manual config option even when detection fails */}
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" onClick={() => setShowManualConfigModal(true)}>
<span className="material-symbols-outlined text-[14px] mr-1">content_copy</span>Manual Config
</Button>
</div>
</div>
)}

View File

@@ -218,16 +218,24 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
{!checking && status && !status.installed && (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">OpenCode CLI not installed</p>
<p className="text-sm text-text-muted">Please install OpenCode CLI to use auto-apply feature.</p>
<div className="flex flex-col gap-3 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<div className="flex items-start gap-3">
<span className="material-symbols-outlined text-yellow-500">warning</span>
<div className="flex-1">
<p className="font-medium text-yellow-600 dark:text-yellow-400">OpenCode CLI not detected locally</p>
<p className="text-sm text-text-muted">Manual configuration is still available if 9router is deployed on a remote server.</p>
</div>
</div>
<div className="flex items-center gap-2 pl-9">
<Button variant="secondary" size="sm" onClick={() => setShowManualConfigModal(true)} className="!bg-yellow-500/20 !border-yellow-500/40 !text-yellow-700 dark:!text-yellow-300 hover:!bg-yellow-500/30">
<span className="material-symbols-outlined text-[18px] mr-1">content_copy</span>
Manual Config
</Button>
<Button variant="outline" size="sm" onClick={() => setShowInstallGuide(!showInstallGuide)}>
<span className="material-symbols-outlined text-[18px] mr-1">{showInstallGuide ? "expand_less" : "help"}</span>
{showInstallGuide ? "Hide" : "How to Install"}
</Button>
</div>
<Button variant="outline" size="sm" onClick={() => setShowInstallGuide(!showInstallGuide)}>
<span className="material-symbols-outlined text-[18px] mr-1">{showInstallGuide ? "expand_less" : "help"}</span>
{showInstallGuide ? "Hide" : "How to Install"}
</Button>
</div>
{showInstallGuide && (
<div className="p-4 bg-surface border border-border rounded-lg">