diff --git a/web/.env.example b/web/.env.example
index 8641b06..e712a26 100644
--- a/web/.env.example
+++ b/web/.env.example
@@ -13,8 +13,31 @@
# SERVERVAR="foo"
# 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
-# Github
+# Github OAuth Token (optional)
GITHUB_OAUTH_TOKEN=xxxx
diff --git a/web/src/app/chat/components/input-box.tsx b/web/src/app/chat/components/input-box.tsx
index ae18ec4..0d6025a 100644
--- a/web/src/app/chat/components/input-box.tsx
+++ b/web/src/app/chat/components/input-box.tsx
@@ -214,7 +214,7 @@ export function InputBox({
- {config?.models.reasoning?.[0] && (
+ {config?.models?.reasoning && config.models.reasoning.length > 0 && (
{t("deepThinkingTooltip.description", {
- model: config.models.reasoning?.[0] ?? "",
+ model: config.models.reasoning[0] ?? "",
})}
diff --git a/web/src/core/api/hooks.ts b/web/src/core/api/hooks.ts
index 133cc62..37ae323 100644
--- a/web/src/core/api/hooks.ts
+++ b/web/src/core/api/hooks.ts
@@ -44,29 +44,70 @@ export function useReplayMetadata() {
return { title, isLoading, hasError: error };
}
+const DEFAULT_CONFIG: DeerFlowConfig = {
+ rag: { provider: "" },
+ models: { basic: [], reasoning: [] },
+};
+
export function useConfig(): {
- config: DeerFlowConfig | null;
+ config: DeerFlowConfig;
loading: boolean;
} {
const [loading, setLoading] = useState(true);
- const [config, setConfig] = useState
(null);
+ const [config, setConfig] = useState(DEFAULT_CONFIG);
useEffect(() => {
if (env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY) {
setLoading(false);
return;
}
- fetch(resolveServiceURL("./config"))
- .then((res) => res.json())
- .then((config) => {
- setConfig(config);
- setLoading(false);
- })
- .catch((err) => {
- console.error("Failed to fetch config", err);
- setConfig(null);
- setLoading(false);
- });
+
+ const fetchConfigWithRetry = async () => {
+ const maxRetries = 2;
+ let lastError: Error | null = null;
+
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
+ try {
+ const res = await fetch(resolveServiceURL("./config"), {
+ signal: AbortSignal.timeout(5000), // 5 second timeout
+ });
+
+ 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 };