From 6237cc29d034fb585c4fd484dd276355b2c9b3e9 Mon Sep 17 00:00:00 2001 From: Khalid Abdi Date: Sat, 27 Jun 2026 21:33:43 +0300 Subject: [PATCH] frontend: one-time AI setup notice above the chat input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show a single, dismissible heads-up on a fresh chat when no AI provider (an API key or a local Ollama endpoint) is configured, linking to Settings → AI. It only renders on the empty state, so it clears itself once the first message is sent. Co-Authored-By: Claude Opus 4.8 --- frontend/components/chat/ai-setup-notice.tsx | 71 ++++++++++++++++++++ frontend/components/chat/chat-panel.tsx | 4 ++ 2 files changed, 75 insertions(+) create mode 100644 frontend/components/chat/ai-setup-notice.tsx diff --git a/frontend/components/chat/ai-setup-notice.tsx b/frontend/components/chat/ai-setup-notice.tsx new file mode 100644 index 0000000..6856339 --- /dev/null +++ b/frontend/components/chat/ai-setup-notice.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { Sparkles, X } from "lucide-react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { Button } from "@/components/ui/button"; +import { getAiConfig } from "@/lib/ai-settings"; + +// A single, dismissible heads-up shown above the chat input on a fresh chat when +// no AI provider is configured yet (no API key, and no local Ollama). It only +// renders on the empty state, so it naturally disappears once a message is sent. +export function AiSetupNotice() { + const { t } = useTranslation(); + const [needsSetup, setNeedsSetup] = useState(false); + const [dismissed, setDismissed] = useState(false); + + useEffect(() => { + let active = true; + getAiConfig() + .then((cfg) => { + if (!active) return; + // Configured = an API key for any provider, or a local Ollama endpoint. + const hasApiKey = Object.values(cfg.apiKeySet).some(Boolean); + const hasLocal = + cfg.mode === "local" && cfg.ollamaBaseUrl.trim().length > 0; + setNeedsSetup(!(hasApiKey || hasLocal)); + }) + .catch(() => { + // If we can't read the config, don't nag — the chat still works. + if (active) setNeedsSetup(false); + }); + return () => { + active = false; + }; + }, []); + + if (!needsSetup || dismissed) return null; + + return ( +
+ +
+

+ {t("chat.setupNotice.title")} +

+

{t("chat.setupNotice.body")}

+ +
+ +
+ ); +} diff --git a/frontend/components/chat/chat-panel.tsx b/frontend/components/chat/chat-panel.tsx index 8e222be..9c24702 100644 --- a/frontend/components/chat/chat-panel.tsx +++ b/frontend/components/chat/chat-panel.tsx @@ -54,6 +54,7 @@ import { ToolOutput, } from "@/components/ai-elements/tool"; import { ActionPreviewCard } from "@/components/chat/action-preview-card"; +import { AiSetupNotice } from "@/components/chat/ai-setup-notice"; import { AnalyticsCard } from "@/components/chat/analytics-card"; import { BatchActionPreviewCard } from "@/components/chat/batch-action-preview-card"; import { ChatHistoryPanel } from "@/components/chat/chat-history-panel"; @@ -722,6 +723,9 @@ export function ChatPanel() {
{errorAlert} {veilGate} + {/* One-time setup heads-up — only on the empty state, so it clears + itself once the first message is sent. */} + {promptInput} {suggestions.map((s) => (