mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-19 12:24:46 +08:00
* fix: improve config loading resilience for non-localhost access (#510) - Add DEFAULT_CONFIG fallback to always return valid config even if fetch fails - Implement retry logic with exponential backoff (max 2 retries) to handle transient failures - Add 5-second fetch timeout to prevent hanging on unreachable backends - Improve error logging with clear messages about config fetch status - Always return DeerFlowConfig (never null) to prevent UI rendering issues - Add safety checks in input-box component to verify reasoning models before access - Improve type safety: verify array length before accessing array indices - Add comprehensive documentation in .env.example with examples for different deployment scenarios - Document NEXT_PUBLIC_API_URL variable behavior and fallback mechanism * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: add nullish coalescing to prevent TypeScript error in input-box - Add ?? operator to handle potential undefined value when accessing reasoning[0] - Fixes TS2322 error: Type 'string | undefined' is not assignable to type 'string | number | Date' --------- Co-authored-by: Willem Jiang <143703838+willem-bd@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -13,8 +13,31 @@
|
|||||||
# SERVERVAR="foo"
|
# SERVERVAR="foo"
|
||||||
# NEXT_PUBLIC_CLIENTVAR="bar"
|
# NEXT_PUBLIC_CLIENTVAR="bar"
|
||||||
|
|
||||||
|
# API Configuration
|
||||||
|
# IMPORTANT: Set this variable when accessing the application from a network IP address
|
||||||
|
# or when the backend server is hosted on a different machine.
|
||||||
|
#
|
||||||
|
# Default value (when not set): http://localhost:8000/api
|
||||||
|
# This default works only when accessing from localhost (http://localhost:3000)
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# - Local development (no change needed):
|
||||||
|
# NEXT_PUBLIC_API_URL=http://localhost:8000/api
|
||||||
|
#
|
||||||
|
# - Accessing from LAN IP (e.g., http://192.168.1.100:3000):
|
||||||
|
# NEXT_PUBLIC_API_URL=http://192.168.1.100:8000/api
|
||||||
|
#
|
||||||
|
# - Accessing from different machine on network (e.g., http://10.24.9.33:3000):
|
||||||
|
# NEXT_PUBLIC_API_URL=http://your-backend-server-ip:8000/api
|
||||||
|
#
|
||||||
|
# - Remote deployment (behind reverse proxy):
|
||||||
|
# NEXT_PUBLIC_API_URL=https://your-domain.com/api
|
||||||
|
#
|
||||||
|
# If not set, the frontend will attempt to use http://localhost:8000/api as fallback.
|
||||||
|
# If the backend is unreachable, the application will use default configuration with
|
||||||
|
# limited features (some LLM-based features may be disabled).
|
||||||
NEXT_PUBLIC_API_URL=http://localhost:8000/api
|
NEXT_PUBLIC_API_URL=http://localhost:8000/api
|
||||||
|
|
||||||
# Github
|
# Github OAuth Token (optional)
|
||||||
GITHUB_OAUTH_TOKEN=xxxx
|
GITHUB_OAUTH_TOKEN=xxxx
|
||||||
|
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export function InputBox({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center px-4 py-2">
|
<div className="flex items-center px-4 py-2">
|
||||||
<div className="flex grow gap-2">
|
<div className="flex grow gap-2">
|
||||||
{config?.models.reasoning?.[0] && (
|
{config?.models?.reasoning && config.models.reasoning.length > 0 && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className="max-w-60"
|
className="max-w-60"
|
||||||
title={
|
title={
|
||||||
@@ -226,7 +226,7 @@ export function InputBox({
|
|||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
{t("deepThinkingTooltip.description", {
|
{t("deepThinkingTooltip.description", {
|
||||||
model: config.models.reasoning?.[0] ?? "",
|
model: config.models.reasoning[0] ?? "",
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,29 +44,70 @@ export function useReplayMetadata() {
|
|||||||
return { title, isLoading, hasError: error };
|
return { title, isLoading, hasError: error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: DeerFlowConfig = {
|
||||||
|
rag: { provider: "" },
|
||||||
|
models: { basic: [], reasoning: [] },
|
||||||
|
};
|
||||||
|
|
||||||
export function useConfig(): {
|
export function useConfig(): {
|
||||||
config: DeerFlowConfig | null;
|
config: DeerFlowConfig;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
} {
|
} {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [config, setConfig] = useState<DeerFlowConfig | null>(null);
|
const [config, setConfig] = useState<DeerFlowConfig>(DEFAULT_CONFIG);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY) {
|
if (env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch(resolveServiceURL("./config"))
|
|
||||||
.then((res) => res.json())
|
const fetchConfigWithRetry = async () => {
|
||||||
.then((config) => {
|
const maxRetries = 2;
|
||||||
setConfig(config);
|
let lastError: Error | null = null;
|
||||||
setLoading(false);
|
|
||||||
})
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||||
.catch((err) => {
|
try {
|
||||||
console.error("Failed to fetch config", err);
|
const res = await fetch(resolveServiceURL("./config"), {
|
||||||
setConfig(null);
|
signal: AbortSignal.timeout(5000), // 5 second timeout
|
||||||
setLoading(false);
|
});
|
||||||
});
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const configData = await res.json();
|
||||||
|
setConfig(configData);
|
||||||
|
setLoading(false);
|
||||||
|
return; // Success, exit retry loop
|
||||||
|
} catch (err) {
|
||||||
|
lastError = err instanceof Error ? err : new Error(String(err));
|
||||||
|
|
||||||
|
// Log attempt details
|
||||||
|
if (attempt === 0) {
|
||||||
|
const apiUrl = resolveServiceURL("./config");
|
||||||
|
console.warn(
|
||||||
|
`[Config] Failed to fetch from ${apiUrl}: ${lastError.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait before retrying (exponential backoff: 100ms, 500ms)
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
const delay = Math.pow(2, attempt) * 100;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All retries failed, use default config
|
||||||
|
console.warn(
|
||||||
|
`[Config] Using default config after ${maxRetries + 1} attempts. Last error: ${lastError?.message ?? "Unknown"}`,
|
||||||
|
);
|
||||||
|
setConfig(DEFAULT_CONFIG);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
void fetchConfigWithRetry();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { config, loading };
|
return { config, loading };
|
||||||
|
|||||||
Reference in New Issue
Block a user