refactor: update alert components across various files to use a consistent structure and styling

This commit is contained in:
Anish Sarkar 2026-05-18 23:46:16 +05:30
parent e0ecea61f8
commit b7a6e3af3d
34 changed files with 372 additions and 619 deletions

View file

@ -218,13 +218,13 @@ export function AgentPermissionsContent() {
if (isError) { if (isError) {
return ( return (
<div className="rounded-lg border border-dashed border-destructive/40 p-8 text-center"> <Alert variant="destructive">
<AlertTriangle className="mx-auto size-8 text-destructive/60" /> <AlertTriangle />
<p className="mt-2 text-sm text-destructive">Failed to load rules</p> <AlertTitle>Failed to load rules</AlertTitle>
<p className="text-xs text-muted-foreground"> <AlertDescription>
{error instanceof Error ? error.message : "Unknown error."} {error instanceof Error ? error.message : "Unknown error."}
</p> </AlertDescription>
</div> </Alert>
); );
} }

View file

@ -5,6 +5,7 @@ import { AlertTriangle, Copy, Globe, Sparkles } from "lucide-react";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { copyPromptMutationAtom } from "@/atoms/prompts/prompts-mutation.atoms"; import { copyPromptMutationAtom } from "@/atoms/prompts/prompts-mutation.atoms";
import { publicPromptsAtom } from "@/atoms/prompts/prompts-query.atoms"; import { publicPromptsAtom } from "@/atoms/prompts/prompts-query.atoms";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
@ -44,11 +45,11 @@ export function CommunityPromptsContent() {
if (isError) { if (isError) {
return ( return (
<div className="rounded-lg border border-dashed border-destructive/40 p-8 text-center"> <Alert variant="destructive">
<AlertTriangle className="mx-auto size-8 text-destructive/60" /> <AlertTriangle />
<p className="mt-2 text-sm text-destructive">Failed to load community prompts</p> <AlertTitle>Failed to load community prompts</AlertTitle>
<p className="text-xs text-muted-foreground">Please try refreshing the page.</p> <AlertDescription>Please try refreshing the page.</AlertDescription>
</div> </Alert>
); );
} }

View file

@ -177,9 +177,9 @@ export function MemoryContent() {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
<p> <p>
SurfSense uses this personal memory to personalize your responses across all SurfSense uses this personal memory to personalize your responses across all
conversations. conversations.

View file

@ -20,6 +20,7 @@ import {
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@ -133,11 +134,11 @@ export function PromptsContent() {
if (isError) { if (isError) {
return ( return (
<div className="rounded-lg border border-dashed border-destructive/40 p-8 text-center"> <Alert variant="destructive">
<AlertTriangle className="mx-auto size-8 text-destructive/60" /> <AlertTriangle />
<p className="mt-2 text-sm text-destructive">Failed to load prompts</p> <AlertTitle>Failed to load prompts</AlertTitle>
<p className="text-xs text-muted-foreground">Please try refreshing the page.</p> <AlertDescription>Please try refreshing the page.</AlertDescription>
</div> </Alert>
); );
} }

View file

@ -380,33 +380,32 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
<div className="px-4 sm:px-12 py-4 sm:py-8 pb-12 sm:pb-16"> <div className="px-4 sm:px-12 py-4 sm:py-8 pb-12 sm:pb-16">
{/* LLM Configuration Warning */} {/* LLM Configuration Warning */}
{!llmConfigLoading && !hasDocumentSummaryLLM && ( {!llmConfigLoading && !hasDocumentSummaryLLM && (
<Alert <div className="mb-6">
variant="destructive" <Alert variant="destructive">
className="mb-6 bg-muted/50 rounded-xl border-destructive/30" <AlertTriangle />
> <AlertTitle>LLM Configuration Required</AlertTitle>
<AlertTriangle className="h-4 w-4" /> <AlertDescription>
<AlertTitle>LLM Configuration Required</AlertTitle> <p>
<AlertDescription className="mt-2"> {isAutoMode && !hasGlobalConfigs
<p className="mb-3"> ? "Auto mode requires a global LLM configuration. Please add one in Settings"
{isAutoMode && !hasGlobalConfigs : "A Document Summary LLM is required to process uploads, configure one in Settings"}
? "Auto mode requires a global LLM configuration. Please add one in Settings" </p>
: "A Document Summary LLM is required to process uploads, configure one in Settings"} <Button
</p> size="sm"
<Button variant="outline"
size="sm" onClick={() => {
variant="outline" handleOpenChange(false);
onClick={() => { router.push(
handleOpenChange(false); `/dashboard/${searchSpaceId}/search-space-settings?tab=models`
router.push( );
`/dashboard/${searchSpaceId}/search-space-settings?tab=models` }}
); >
}} <Settings className="mr-2 h-4 w-4" />
> Go to Settings
<Settings className="mr-2 h-4 w-4" /> </Button>
Go to Settings </AlertDescription>
</Button> </Alert>
</AlertDescription> </div>
</Alert>
)} )}
<TabsContent value="all" className="m-0"> <TabsContent value="all" className="m-0">

View file

@ -70,20 +70,22 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSu
return ( return (
<div className="space-y-6 pb-6"> <div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle> <AlertTitle>API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
You'll need a Baidu AppBuilder API key to use this connector. You can get one by signing <p>
up at{" "} You'll need a Baidu AppBuilder API key to use this connector. You can get one by
<a signing up at{" "}
href="https://qianfan.cloud.baidu.com/" <a
target="_blank" href="https://qianfan.cloud.baidu.com/"
rel="noopener noreferrer" target="_blank"
className="font-medium underline underline-offset-4" rel="noopener noreferrer"
> className="font-medium underline underline-offset-4"
qianfan.cloud.baidu.com >
</a> qianfan.cloud.baidu.com
</a>
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>

View file

@ -96,10 +96,10 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitt
return ( return (
<div className="space-y-6 pb-6"> <div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertTitle className="text-xs sm:text-sm">API Token Required</AlertTitle> <AlertTitle>API Token Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
You'll need a BookStack API Token to use this connector. You can create one from your You'll need a BookStack API Token to use this connector. You can create one from your
BookStack instance settings. BookStack instance settings.
</AlertDescription> </AlertDescription>

View file

@ -172,10 +172,10 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSub
return ( return (
<div className="space-y-6 pb-6"> <div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle> <AlertTitle>API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
Enter your Elasticsearch cluster endpoint URL and authentication credentials to connect. Enter your Elasticsearch cluster endpoint URL and authentication credentials to connect.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
@ -428,10 +428,10 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSub
</div> </div>
)} )}
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20"> <Alert>
<Info className="h-3 w-3 sm:h-4 sm:w-4" /> <Info />
<AlertTitle className="text-[10px] sm:text-xs">Index Selection Tips</AlertTitle> <AlertTitle>Index Selection Tips</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px] mt-2"> <AlertDescription>
<ul className="list-disc pl-4 space-y-1"> <ul className="list-disc pl-4 space-y-1">
<li>Use wildcards like "logs-*" to match multiple indices</li> <li>Use wildcards like "logs-*" to match multiple indices</li>
<li>Separate multiple indices with commas</li> <li>Separate multiple indices with commas</li>
@ -643,231 +643,6 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSub
</ul> </ul>
</div> </div>
)} )}
{/* Documentation Section */}
<Accordion
type="single"
collapsible
className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5"
>
<AccordionItem value="documentation" className="border-0">
<AccordionTrigger className="text-sm sm:text-base font-medium px-3 sm:px-6 no-underline hover:no-underline">
Documentation
</AccordionTrigger>
<AccordionContent className="px-3 sm:px-6 pb-3 sm:pb-6 space-y-6">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3>
<p className="text-[10px] sm:text-xs text-muted-foreground">
The Elasticsearch connector allows you to search and retrieve documents from your
Elasticsearch cluster. Configure connection details, select specific indices, and
set search parameters to make your existing data searchable within SurfSense.
</p>
</div>
<div className="space-y-4">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">Connection Setup</h3>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Get your Elasticsearch endpoint
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
You'll need the endpoint URL for your Elasticsearch cluster. This typically
looks like:
</p>
<ul className="list-disc pl-5 space-y-1 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
Cloud:{" "}
<code className="bg-muted px-1 py-0.5 rounded">
https://your-cluster.es.region.aws.com:443
</code>
</li>
<li>
Self-hosted:{" "}
<code className="bg-muted px-1 py-0.5 rounded">
https://elasticsearch.example.com:9200
</code>
</li>
</ul>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Configure authentication
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
Elasticsearch requires authentication. You can use either:
</p>
<ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
<strong>API Key:</strong> A base64-encoded API key. You can create one in
Elasticsearch by running:
<pre className="bg-muted p-2 rounded mt-1 text-[9px] overflow-x-auto">
<code>POST /_security/api_key</code>
</pre>
</li>
<li>
<strong>Username & Password:</strong> Basic authentication using your
Elasticsearch username and password.
</li>
</ul>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 3: Select indices
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
Specify which indices to search. You can:
</p>
<ul className="list-disc pl-5 space-y-1 text-[10px] sm:text-xs text-muted-foreground">
<li>
Use wildcards: <code className="bg-muted px-1 py-0.5 rounded">logs-*</code>{" "}
to match multiple indices
</li>
<li>
List specific indices:{" "}
<code className="bg-muted px-1 py-0.5 rounded">
logs-2024, documents-2024
</code>
</li>
<li>
Leave empty to search all accessible indices (not recommended for
performance)
</li>
</ul>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">Advanced Configuration</h3>
<div className="space-y-4">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Search Query</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-2">
The default query used for searches. Use{" "}
<code className="bg-muted px-1 py-0.5 rounded">*</code> to match all
documents, or specify a more complex Elasticsearch query.
</p>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Search Fields</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-2">
Limit searches to specific fields for better performance. Common fields
include:
</p>
<ul className="list-disc pl-5 space-y-1 text-[10px] sm:text-xs text-muted-foreground">
<li>
<code className="bg-muted px-1 py-0.5 rounded">title</code> - Document
titles
</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">content</code> - Main content
</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">description</code> -
Descriptions
</li>
</ul>
<p className="text-[10px] sm:text-xs text-muted-foreground mt-2">
Leave empty to search all fields in your documents.
</p>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Maximum Documents</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground">
Set a limit on the number of documents retrieved per search (1-10,000). This
helps control response times and resource usage. Leave empty to use
Elasticsearch's default limit.
</p>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">Troubleshooting</h3>
<div className="space-y-4">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Connection Issues</h4>
<ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>
<strong>Invalid URL:</strong> Ensure your endpoint URL includes the protocol
(https://) and port number if required.
</li>
<li>
<strong>SSL/TLS Errors:</strong> Verify that your cluster uses HTTPS and the
certificate is valid. Self-signed certificates may require additional
configuration.
</li>
<li>
<strong>Connection Timeout:</strong> Check your network connectivity and
firewall settings. Ensure the Elasticsearch cluster is accessible from
SurfSense servers.
</li>
</ul>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Authentication Issues
</h4>
<ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>
<strong>Invalid Credentials:</strong> Double-check your username/password or
API key. API keys must be base64-encoded.
</li>
<li>
<strong>Permission Denied:</strong> Ensure your API key or user account has
read permissions for the indices you want to search.
</li>
<li>
<strong>API Key Format:</strong> Elasticsearch API keys are typically
base64-encoded strings. Make sure you're using the full key value.
</li>
</ul>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Search Issues</h4>
<ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>
<strong>No Results:</strong> Verify that your index selection matches
existing indices. Use wildcards carefully.
</li>
<li>
<strong>Slow Searches:</strong> Limit the number of indices or use specific
index names instead of wildcards. Reduce the maximum documents limit.
</li>
<li>
<strong>Field Not Found:</strong> Ensure the search fields you specify
actually exist in your Elasticsearch documents.
</li>
</ul>
</div>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 mt-4">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Need More Help?</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
If you continue to experience issues, check your Elasticsearch cluster logs
and ensure your cluster version is compatible. For Elasticsearch Cloud
deployments, verify your access policies and IP allowlists.
</AlertDescription>
</Alert>
</div>
</div>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div> </div>
); );
}; };

View file

@ -105,20 +105,23 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting
return ( return (
<div className="space-y-6 pb-6"> <div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertTitle className="text-xs sm:text-sm">Personal Access Token (Optional)</AlertTitle> <AlertTitle>Personal Access Token (Optional)</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
A GitHub PAT is only required for private repositories. Public repos work without a token.{" "} <p>
<a A GitHub PAT is only required for private repositories. Public repos work without a
href="https://github.com/settings/tokens/new?description=surfsense&scopes=repo" token.{" "}
target="_blank" <a
rel="noopener noreferrer" href="https://github.com/settings/tokens/new?description=surfsense&scopes=repo"
className="font-medium underline underline-offset-4 inline-flex items-center gap-1.5" target="_blank"
> rel="noopener noreferrer"
Get your token className="font-medium underline underline-offset-4 inline-flex items-center gap-1.5"
<ExternalLink className="h-3 w-3 sm:h-4 sm:w-4" /> >
</a>{" "} Get your token
<ExternalLink className="h-3 w-3 sm:h-4 sm:w-4" />
</a>
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>

View file

@ -70,19 +70,21 @@ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitt
return ( return (
<div className="space-y-6 pb-6"> <div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle> <AlertTitle>API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
You'll need a Linkup API key to use this connector. You can get one by signing up at{" "} <p>
<a You'll need a Linkup API key to use this connector. You can get one by signing up at{" "}
href="https://linkup.so" <a
target="_blank" href="https://linkup.so"
rel="noopener noreferrer" target="_blank"
className="font-medium underline underline-offset-4" rel="noopener noreferrer"
> className="font-medium underline underline-offset-4"
linkup.so >
</a> linkup.so
</a>
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>

View file

@ -88,19 +88,21 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }
return ( return (
<div className="space-y-6 pb-6"> <div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle> <AlertTitle>API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
You'll need a Luma API Key to use this connector. You can create one from{" "} <p>
<a You'll need a Luma API Key to use this connector. You can create one from{" "}
href="https://lu.ma/api" <a
target="_blank" href="https://lu.ma/api"
rel="noopener noreferrer" target="_blank"
className="font-medium underline underline-offset-4" rel="noopener noreferrer"
> className="font-medium underline underline-offset-4"
Luma API Settings >
</a> Luma API Settings
</a>
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>

View file

@ -1,20 +1,17 @@
"use client"; "use client";
import { Check, Copy, Info } from "lucide-react"; import { Check, Copy, Info } from "lucide-react";
import { type FC, useCallback, useRef, useState } from "react"; import type { FC } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { useApiKey } from "@/hooks/use-api-key"; import { useApiKey } from "@/hooks/use-api-key";
import { copyToClipboard as copyToClipboardUtil } from "@/lib/utils";
import { getConnectorBenefits } from "../connector-benefits"; import { getConnectorBenefits } from "../connector-benefits";
import type { ConnectFormProps } from "../index"; import type { ConnectFormProps } from "../index";
const PLUGIN_RELEASES_URL = const PLUGIN_RELEASES_URL =
"https://github.com/MODSetter/SurfSense/releases?q=obsidian&expanded=true"; "https://github.com/MODSetter/SurfSense/releases?q=obsidian&expanded=true";
const BACKEND_URL = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL ?? "https://surfsense.com";
/** /**
* Obsidian connect form for the plugin-only architecture. * Obsidian connect form for the plugin-only architecture.
* *
@ -30,16 +27,6 @@ const BACKEND_URL = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL ?? "https://surf
*/ */
export const ObsidianConnectForm: FC<ConnectFormProps> = ({ onBack }) => { export const ObsidianConnectForm: FC<ConnectFormProps> = ({ onBack }) => {
const { apiKey, isLoading, copied, copyToClipboard } = useApiKey(); const { apiKey, isLoading, copied, copyToClipboard } = useApiKey();
const [copiedUrl, setCopiedUrl] = useState(false);
const urlCopyTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const copyServerUrl = useCallback(async () => {
const ok = await copyToClipboardUtil(BACKEND_URL);
if (!ok) return;
setCopiedUrl(true);
if (urlCopyTimerRef.current) clearTimeout(urlCopyTimerRef.current);
urlCopyTimerRef.current = setTimeout(() => setCopiedUrl(false), 2000);
}, []);
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
@ -52,10 +39,10 @@ export const ObsidianConnectForm: FC<ConnectFormProps> = ({ onBack }) => {
that just closes the dialog (see component-level docstring). */} that just closes the dialog (see component-level docstring). */}
<form id="obsidian-connect-form" onSubmit={handleSubmit} /> <form id="obsidian-connect-form" onSubmit={handleSubmit} />
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0 text-purple-500" /> <Info />
<AlertTitle className="text-xs sm:text-sm">Plugin-based sync</AlertTitle> <AlertTitle>Plugin-based sync</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
SurfSense now syncs Obsidian via an official plugin that runs inside Obsidian itself. SurfSense now syncs Obsidian via an official plugin that runs inside Obsidian itself.
Works on desktop and mobile, in cloud and self-hosted deployments. Works on desktop and mobile, in cloud and self-hosted deployments.
</AlertDescription> </AlertDescription>

View file

@ -123,20 +123,22 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmittin
return ( return (
<div className="space-y-6 pb-6"> <div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertTitle className="text-xs sm:text-sm">SearxNG Instance Required</AlertTitle> <AlertTitle>SearxNG Instance Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
You need access to a running SearxNG instance. Refer to the{" "} <p>
<a You need access to a running SearxNG instance. Refer to the{" "}
href="https://docs.searxng.org/admin/installation-docker.html" <a
target="_blank" href="https://docs.searxng.org/admin/installation-docker.html"
rel="noopener noreferrer" target="_blank"
className="font-medium underline underline-offset-4" rel="noopener noreferrer"
> className="font-medium underline underline-offset-4"
SearxNG installation guide >
</a>{" "} SearxNG installation guide
for setup instructions. If your instance requires an API key, include it below. </a>{" "}
for setup instructions. If your instance requires an API key, include it below.
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>

View file

@ -70,19 +70,21 @@ export const TavilyApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitt
return ( return (
<div className="space-y-6 pb-6"> <div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle> <AlertTitle>API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
You'll need a Tavily API key to use this connector. You can get one by signing up at{" "} <p>
<a You'll need a Tavily API key to use this connector. You can get one by signing up at{" "}
href="https://tavily.com" <a
target="_blank" href="https://tavily.com"
rel="noopener noreferrer" target="_blank"
className="font-medium underline underline-offset-4" rel="noopener noreferrer"
> className="font-medium underline underline-offset-4"
tavily.com >
</a> tavily.com
</a>
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>

View file

@ -166,10 +166,10 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameC
)} )}
{webhookInfo && ( {webhookInfo && (
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20"> <Alert>
<Info className="h-3 w-3 sm:h-4 sm:w-4" /> <Info />
<AlertTitle className="text-xs sm:text-sm">Configuration Instructions</AlertTitle> <AlertTitle>Configuration Instructions</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs mt-1"> <AlertDescription>
Configure this URL in Circleback Settings Automations Create automation Send Configure this URL in Circleback Settings Automations Create automation Send
webhook request. The webhook will automatically send meeting notes, transcripts, and webhook request. The webhook will automatically send meeting notes, transcripts, and
action items to this search space. action items to this search space.

View file

@ -3,6 +3,7 @@
import { Info, KeyRound } from "lucide-react"; import { Info, KeyRound } from "lucide-react";
import type { FC } from "react"; import type { FC } from "react";
import { useState } from "react"; import { useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import type { ConnectorConfigProps } from "../index"; import type { ConnectorConfigProps } from "../index";
@ -47,21 +48,17 @@ export const ClickUpConfig: FC<ClickUpConfigProps> = ({
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* OAuth Info */} {/* OAuth Info */}
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3"> <Alert>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5"> <Info />
<Info className="size-4" /> <AlertTitle>Connected via OAuth</AlertTitle>
</div> <AlertDescription>
<div className="text-xs sm:text-sm"> <p>
<p className="font-medium text-xs sm:text-sm">Connected via OAuth</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
Workspace:{" "} Workspace:{" "}
<code className="bg-muted px-1 py-0.5 rounded text-inherit">{workspaceName}</code> <code className="bg-muted px-1 py-0.5 rounded text-inherit">{workspaceName}</code>
</p> </p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm"> <p>To update your connection, reconnect this connector.</p>
To update your connection, reconnect this connector. </AlertDescription>
</p> </Alert>
</div>
</div>
</div> </div>
); );
} }

View file

@ -3,6 +3,7 @@
import { Info, KeyRound } from "lucide-react"; import { Info, KeyRound } from "lucide-react";
import type { FC } from "react"; import type { FC } from "react";
import { useState } from "react"; import { useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import type { ConnectorConfigProps } from "../index"; import type { ConnectorConfigProps } from "../index";
@ -72,23 +73,17 @@ export const ConfluenceConfig: FC<ConfluenceConfigProps> = ({
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* OAuth Info */} {/* OAuth Info */}
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3"> <Alert>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5"> <Info />
<Info className="size-4" /> <AlertTitle>Connected via OAuth</AlertTitle>
</div> <AlertDescription>
<div className="text-xs sm:text-sm"> <p>This connector is authenticated using OAuth 2.0. Your Confluence instance is:</p>
<p className="font-medium text-xs sm:text-sm">Connected via OAuth</p> <p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
This connector is authenticated using OAuth 2.0. Your Confluence instance is:
</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
<code className="bg-muted px-1 py-0.5 rounded text-inherit">{siteUrl}</code> <code className="bg-muted px-1 py-0.5 rounded text-inherit">{siteUrl}</code>
</p> </p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm"> <p>To update your connection, reconnect this connector.</p>
To update your connection, reconnect this connector. </AlertDescription>
</p> </Alert>
</div>
</div>
</div> </div>
); );
} }

View file

@ -2,6 +2,7 @@
import { AlertCircle, CheckCircle2, Hash, Info, Megaphone, RefreshCw } from "lucide-react"; import { AlertCircle, CheckCircle2, Hash, Info, Megaphone, RefreshCw } from "lucide-react";
import { type FC, useCallback, useEffect, useState } from "react"; import { type FC, useCallback, useEffect, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { connectorsApiService, type DiscordChannel } from "@/lib/apis/connectors-api.service"; import { connectorsApiService, type DiscordChannel } from "@/lib/apis/connectors-api.service";
@ -73,17 +74,14 @@ export const DiscordConfig: FC<DiscordConfigProps> = ({ connector }) => {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Info box */} {/* Info box */}
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3"> <Alert>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5"> <Info />
<Info className="size-4" /> <AlertTitle>Grant Channel Permissions</AlertTitle>
</div> <AlertDescription>
<div className="text-xs sm:text-sm"> The bot needs &quot;Read Message History&quot; permission to access channels. Ask a server
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm"> admin to grant this permission for channels shown below.
The bot needs &quot;Read Message History&quot; permission to access channels. Ask a </AlertDescription>
server admin to grant this permission for channels shown below. </Alert>
</p>
</div>
</div>
{/* Channels Section */} {/* Channels Section */}
<div className="space-y-3"> <div className="space-y-3">

View file

@ -3,6 +3,7 @@
import { Info, KeyRound } from "lucide-react"; import { Info, KeyRound } from "lucide-react";
import type { FC } from "react"; import type { FC } from "react";
import { useState } from "react"; import { useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import type { ConnectorConfigProps } from "../index"; import type { ConnectorConfigProps } from "../index";
@ -65,23 +66,17 @@ export const JiraConfig: FC<JiraConfigProps> = ({ connector, onConfigChange, onN
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* OAuth Info */} {/* OAuth Info */}
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3"> <Alert>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5"> <Info />
<Info className="size-4" /> <AlertTitle>Connected via OAuth</AlertTitle>
</div> <AlertDescription>
<div className="text-xs sm:text-sm"> <p>This connector is authenticated using OAuth 2.0. Your Jira instance is:</p>
<p className="font-medium text-xs sm:text-sm">Connected via OAuth</p> <p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
This connector is authenticated using OAuth 2.0. Your Jira instance is:
</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
<code className="bg-muted px-1 py-0.5 rounded text-inherit">{baseUrl}</code> <code className="bg-muted px-1 py-0.5 rounded text-inherit">{baseUrl}</code>
</p> </p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm"> <p>To update your connection, reconnect this connector.</p>
To update your connection, reconnect this connector. </AlertDescription>
</p> </Alert>
</div>
</div>
</div> </div>
); );
} }

View file

@ -47,12 +47,10 @@ export const ObsidianConfig: FC<ConnectorConfigProps> = ({ connector }) => {
const LegacyBanner: FC = () => { const LegacyBanner: FC = () => {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<Alert className="border-amber-500/40 bg-amber-500/10"> <Alert>
<AlertTriangle className="size-4 shrink-0 text-amber-500" /> <AlertTriangle />
<AlertTitle className="text-xs sm:text-sm"> <AlertTitle>Sync stopped, install the plugin to migrate</AlertTitle>
Sync stopped, install the plugin to migrate <AlertDescription>
</AlertTitle>
<AlertDescription className="text-[11px] sm:text-xs leading-relaxed">
This Obsidian connector used the legacy server-path scanner, which has been removed. The This Obsidian connector used the legacy server-path scanner, which has been removed. The
notes already indexed remain searchable, but they no longer reflect changes made in your notes already indexed remain searchable, but they no longer reflect changes made in your
vault. vault.
@ -124,10 +122,10 @@ const PluginStats: FC<{ config: Record<string, unknown> }> = ({ config }) => {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Alert className="border-emerald-500/30 bg-emerald-500/10"> <Alert>
<Info className="size-4 shrink-0 text-emerald-500" /> <Info />
<AlertTitle className="text-xs sm:text-sm">Plugin connected</AlertTitle> <AlertTitle>Plugin connected</AlertTitle>
<AlertDescription className="text-[11px] sm:text-xs"> <AlertDescription>
Your notes stay synced automatically. To stop syncing, disable or uninstall the plugin in Your notes stay synced automatically. To stop syncing, disable or uninstall the plugin in
Obsidian, or delete this connector. Obsidian, or delete this connector.
</AlertDescription> </AlertDescription>
@ -152,11 +150,11 @@ const PluginStats: FC<{ config: Record<string, unknown> }> = ({ config }) => {
const UnknownConnectorState: FC = () => ( const UnknownConnectorState: FC = () => (
<Alert> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertTitle className="text-xs sm:text-sm">Unrecognized config</AlertTitle> <AlertTitle>Unrecognized config</AlertTitle>
<AlertDescription className="text-[11px] sm:text-xs"> <AlertDescription>
This connector has neither plugin metadata nor a legacy marker. It may predate migration you This connector is missing plugin metadata and may predate the Obsidian plugin migration. You
can safely delete it and re-install the SurfSense Obsidian plugin to resume syncing. can safely delete it and reinstall the SurfSense Obsidian plugin to resume syncing.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
); );

View file

@ -2,6 +2,7 @@
import { AlertCircle, CheckCircle2, Hash, Info, Lock, RefreshCw } from "lucide-react"; import { AlertCircle, CheckCircle2, Hash, Info, Lock, RefreshCw } from "lucide-react";
import { type FC, useCallback, useEffect, useState } from "react"; import { type FC, useCallback, useEffect, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { connectorsApiService, type SlackChannel } from "@/lib/apis/connectors-api.service"; import { connectorsApiService, type SlackChannel } from "@/lib/apis/connectors-api.service";
@ -74,20 +75,18 @@ export const SlackConfig: FC<SlackConfigProps> = ({ connector }) => {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Info box */} {/* Info box */}
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3"> <Alert>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5"> <Info />
<Info className="size-4" /> <AlertTitle>Add Bot to Channels</AlertTitle>
</div> <AlertDescription>
<div className="text-xs sm:text-sm"> <p>
<p className="font-medium text-xs sm:text-sm">Add Bot to Channels</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
Before indexing, add the SurfSense bot to each channel you want to index. The bot can Before indexing, add the SurfSense bot to each channel you want to index. The bot can
only access messages from channels it's been added to. Type{" "} only access messages from channels it's been added to. Type{" "}
<code className="bg-muted px-1 py-0.5 rounded text-[9px]">/invite @SurfSense</code> in <code className="bg-muted px-1 py-0.5 rounded text-[9px]">/invite @SurfSense</code>{" "}
any channel to add it. in any channel to add it.
</p> </p>
</div> </AlertDescription>
</div> </Alert>
{/* Channels Section */} {/* Channels Section */}
<div className="space-y-3"> <div className="space-y-3">

View file

@ -2,6 +2,7 @@
import { Info } from "lucide-react"; import { Info } from "lucide-react";
import type { FC } from "react"; import type { FC } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import type { ConnectorConfigProps } from "../index"; import type { ConnectorConfigProps } from "../index";
export interface TeamsConfigProps extends ConnectorConfigProps { export interface TeamsConfigProps extends ConnectorConfigProps {
@ -11,19 +12,17 @@ export interface TeamsConfigProps extends ConnectorConfigProps {
export const TeamsConfig: FC<TeamsConfigProps> = () => { export const TeamsConfig: FC<TeamsConfigProps> = () => {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3"> <Alert>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5"> <Info />
<Info className="size-4" /> <AlertTitle>Microsoft Teams Access</AlertTitle>
</div> <AlertDescription>
<div className="text-xs sm:text-sm"> <p>
<p className="font-medium text-xs sm:text-sm">Microsoft Teams Access</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
Your agent can search and read messages from Teams channels you have access to, and send Your agent can search and read messages from Teams channels you have access to, and send
messages on your behalf. Make sure you&#39;re a member of the teams you want to interact messages on your behalf. Make sure you&#39;re a member of the teams you want to
with. interact with.
</p> </p>
</div> </AlertDescription>
</div> </Alert>
</div> </div>
); );
}; };

View file

@ -52,13 +52,13 @@ export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({ connector, onConfig
</div> </div>
{/* Chat tip */} {/* Chat tip */}
<div className="flex items-start gap-3 rounded-lg border border-blue-200/50 bg-blue-50/50 dark:border-blue-500/20 dark:bg-blue-950/20 p-3 text-xs sm:text-sm"> <Alert>
<Info className="size-4 mt-0.5 shrink-0 text-blue-600 dark:text-blue-400" /> <Info />
<p className="text-muted-foreground"> <AlertDescription>
Want a quick answer from a webpage without indexing it? Just paste the URL directly into Want a quick answer from a webpage without indexing it? Just paste the URL directly into
the chat instead. the chat instead.
</p> </AlertDescription>
</div> </Alert>
{/* API Key Field */} {/* API Key Field */}
<div className="space-y-2"> <div className="space-y-2">
@ -116,9 +116,9 @@ export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({ connector, onConfig
</div> </div>
{/* Info Alert */} {/* Info Alert */}
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3"> <Alert>
<Info className="size-4 shrink-0" /> <Info />
<AlertDescription className="text-[10px] sm:text-xs"> <AlertDescription>
Configuration is saved when you start indexing. You can update these settings anytime from Configuration is saved when you start indexing. You can update these settings anytime from
the connector management page. the connector management page.
</AlertDescription> </AlertDescription>

View file

@ -5,6 +5,7 @@ import { ArrowLeft, Info, RefreshCw } from "lucide-react";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
@ -350,20 +351,12 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
{/* Info box - hidden for live connectors */} {/* Info box - hidden for live connectors */}
{connector.is_indexable && !isLive && ( {connector.is_indexable && !isLive && (
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3"> <Alert>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5"> <Info />
<Info className="size-4" /> <AlertDescription>
</div> You can continue using SurfSense while we sync your data. Check inbox for updates.
<div className="text-xs sm:text-sm"> </AlertDescription>
<p className="font-medium text-xs sm:text-sm"> </Alert>
Re-indexing runs in the background
</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
You can continue using SurfSense while we sync your data. Check inbox for
updates.
</p>
</div>
</div>
)} )}
</div> </div>
</div> </div>

View file

@ -2,6 +2,7 @@
import { ArrowLeft, Check, Info } from "lucide-react"; import { ArrowLeft, Check, Info } from "lucide-react";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
@ -230,18 +231,12 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
{/* Info box - hidden for live connectors */} {/* Info box - hidden for live connectors */}
{connector?.is_indexable && !isLive && ( {connector?.is_indexable && !isLive && (
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3"> <Alert>
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5"> <Info />
<Info className="size-4" /> <AlertDescription>
</div> You can continue using SurfSense while we sync your data. Check inbox for updates.
<div className="text-xs sm:text-sm"> </AlertDescription>
<p className="font-medium text-xs sm:text-sm">Indexing runs in the background</p> </Alert>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
You can continue using SurfSense while we sync your data. Check inbox for
updates.
</p>
</div>
</div>
)} )}
</div> </div>
</div> </div>

View file

@ -7,6 +7,7 @@ import { useTranslations } from "next-intl";
import { type FC, useCallback, useState } from "react"; import { type FC, useCallback, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { createDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms"; import { createDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
@ -279,10 +280,10 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId,
{error && <div className="text-sm text-red-500 mt-2">{error}</div>} {error && <div className="text-sm text-red-500 mt-2">{error}</div>}
<div className="flex items-start gap-3 rounded-lg border border-blue-200/50 bg-blue-50/50 dark:border-blue-500/20 dark:bg-blue-950/20 p-4 text-sm"> <Alert>
<Info className="size-4 mt-0.5 shrink-0 text-blue-600 dark:text-blue-400" /> <Info />
<p className="text-muted-foreground">{t("chat_tip")}</p> <AlertDescription>{t("chat_tip")}</AlertDescription>
</div> </Alert>
<div className="bg-muted/50 rounded-lg p-4 text-sm"> <div className="bg-muted/50 rounded-lg p-4 text-sm">
<h4 className="font-medium mb-2">{t("tips_title")}</h4> <h4 className="font-medium mb-2">{t("tips_title")}</h4>

View file

@ -147,31 +147,30 @@ const DocumentUploadPopupContent: FC<{
<div className="px-4 sm:px-6 pb-4 sm:pb-6"> <div className="px-4 sm:px-6 pb-4 sm:pb-6">
{!isLoading && !hasDocumentSummaryLLM ? ( {!isLoading && !hasDocumentSummaryLLM ? (
<Alert <div className="mb-4">
variant="destructive" <Alert variant="destructive">
className="mb-4 bg-muted/50 rounded-xl border-destructive/30" <AlertTriangle />
> <AlertTitle>LLM Configuration Required</AlertTitle>
<AlertTriangle className="h-4 w-4" /> <AlertDescription>
<AlertTitle>LLM Configuration Required</AlertTitle> <p>
<AlertDescription className="mt-2"> {isAutoMode && !hasGlobalConfigs
<p className="mb-3"> ? "Auto mode requires a global LLM configuration. Please add one in Settings"
{isAutoMode && !hasGlobalConfigs : "A Document Summary LLM is required to process uploads, configure one in Settings"}
? "Auto mode requires a global LLM configuration. Please add one in Settings" </p>
: "A Document Summary LLM is required to process uploads, configure one in Settings"} <Button
</p> size="sm"
<Button variant="outline"
size="sm" onClick={() => {
variant="outline" onOpenChange(false);
onClick={() => { router.push(`/dashboard/${searchSpaceId}/search-space-settings?tab=models`);
onOpenChange(false); }}
router.push(`/dashboard/${searchSpaceId}/search-space-settings?tab=models`); >
}} <Settings className="mr-2 h-4 w-4" />
> Go to Settings
<Settings className="mr-2 h-4 w-4" /> </Button>
Go to Settings </AlertDescription>
</Button> </Alert>
</AlertDescription> </div>
</Alert>
) : ( ) : (
<DocumentUploadTab searchSpaceId={searchSpaceId} onSuccess={handleSuccess} /> <DocumentUploadTab searchSpaceId={searchSpaceId} onSuccess={handleSuccess} />
)} )}

View file

@ -128,8 +128,8 @@ export function PublicChatSnapshotsManager({
// Permission denied // Permission denied
if (!canView) { if (!canView) {
return ( return (
<Alert variant="destructive"> <Alert>
<Info className="h-4 w-4" /> <Info />
<AlertDescription> <AlertDescription>
You don't have permission to view public chat links in this search space. You don't have permission to view public chat links in this search space.
</AlertDescription> </AlertDescription>
@ -141,9 +141,9 @@ export function PublicChatSnapshotsManager({
return ( return (
<div className="space-y-4 md:space-y-5"> <div className="space-y-4 md:space-y-5">
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
Public chat links allow anyone with the URL to view a snapshot of a chat. These links do Public chat links allow anyone with the URL to view a snapshot of a chat. These links do
not update when the original chat changes. not update when the original chat changes.
</AlertDescription> </AlertDescription>

View file

@ -152,26 +152,30 @@ export function AgentModelManager({ searchSpaceId }: AgentModelManagerProps) {
{/* Read-only / Limited permissions notice */} {/* Read-only / Limited permissions notice */}
{access && !isLoading && isReadOnly && ( {access && !isLoading && isReadOnly && (
<div> <div>
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
You have <span className="font-medium">read-only</span> access to LLM configurations. <p>
Contact a space owner to request additional permissions. You have <span className="font-medium">read-only</span> access to LLM
configurations. Contact a space owner to request additional permissions.
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
</div> </div>
)} )}
{access && !isLoading && !isReadOnly && (!canCreate || !canUpdate || !canDelete) && ( {access && !isLoading && !isReadOnly && (!canCreate || !canUpdate || !canDelete) && (
<div> <div>
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
You can{" "} <p>
{[canCreate && "create", canUpdate && "edit", canDelete && "delete"] You can{" "}
.filter(Boolean) {[canCreate && "create", canUpdate && "edit", canDelete && "delete"]
.join(" and ")}{" "} .filter(Boolean)
configurations .join(" and ")}{" "}
{!canDelete && ", but cannot delete them"}. configurations
{!canDelete && ", but cannot delete them"}.
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
</div> </div>
@ -179,9 +183,9 @@ export function AgentModelManager({ searchSpaceId }: AgentModelManagerProps) {
{/* Global Configs Info */} {/* Global Configs Info */}
{globalConfigs.length > 0 && ( {globalConfigs.length > 0 && (
<Alert className="bg-muted/50 py-3"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
<p> <p>
<span className="font-medium"> <span className="font-medium">
{globalConfigs.length} global {globalConfigs.length === 1 ? "model" : "models"} {globalConfigs.length} global {globalConfigs.length === 1 ? "model" : "models"}

View file

@ -135,11 +135,9 @@ export function GeneralSettingsManager({ searchSpaceId }: GeneralSettingsManager
return ( return (
<div className="space-y-4 md:space-y-6"> <div className="space-y-4 md:space-y-6">
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>Update your search space name and description.</AlertDescription>
Update your search space name and description.
</AlertDescription>
</Alert> </Alert>
<form onSubmit={onSubmit} className="space-y-6"> <form onSubmit={onSubmit} className="space-y-6">

View file

@ -153,26 +153,30 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) {
{/* Read-only / Limited permissions notice */} {/* Read-only / Limited permissions notice */}
{access && !isLoading && isReadOnly && ( {access && !isLoading && isReadOnly && (
<div> <div>
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
You have <span className="font-medium">read-only</span> access to image generation <p>
configurations. Contact a space owner to request additional permissions. You have <span className="font-medium">read-only</span> access to image generation
configurations. Contact a space owner to request additional permissions.
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
</div> </div>
)} )}
{access && !isLoading && !isReadOnly && (!canCreate || !canDelete) && ( {access && !isLoading && !isReadOnly && (!canCreate || !canDelete) && (
<div> <div>
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
You can{" "} <p>
{[canCreate && "create and edit", canDelete && "delete"] You can{" "}
.filter(Boolean) {[canCreate && "create and edit", canDelete && "delete"]
.join(" and ")}{" "} .filter(Boolean)
image model configurations .join(" and ")}{" "}
{!canDelete && ", but cannot delete them"}. image model configurations
{!canDelete && ", but cannot delete them"}.
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
</div> </div>
@ -180,9 +184,9 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) {
{/* Global info */} {/* Global info */}
{globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length > 0 && ( {globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length > 0 && (
<Alert className="bg-muted/50 py-3"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
<p> <p>
<span className="font-medium"> <span className="font-medium">
{globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length}{" "} {globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length}{" "}

View file

@ -4,7 +4,7 @@ import { useQuery } from "@tanstack/react-query";
import { AlertTriangle, Info } from "lucide-react"; import { AlertTriangle, Info } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@ -102,21 +102,19 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
return ( return (
<div className="space-y-4 md:space-y-6"> <div className="space-y-4 md:space-y-6">
{/* Work in Progress Notice */} {/* Work in Progress Notice */}
<Alert <Alert>
variant="default" <AlertTriangle />
className="bg-amber-50 dark:bg-amber-950/30 border-amber-300 dark:border-amber-700 py-3 md:py-4" <AlertTitle>Work in Progress</AlertTitle>
> <AlertDescription>
<AlertTriangle className="h-3 w-3 md:h-4 md:w-4 text-amber-600 dark:text-amber-500 shrink-0" /> This functionality is currently under development and not yet connected to the backend.
<AlertDescription className="text-amber-800 dark:text-amber-300 text-xs md:text-sm"> Your instructions will be saved but won't affect AI behavior until the feature is fully
<span className="font-semibold">Work in Progress:</span> This functionality is currently implemented.
under development and not yet connected to the backend. Your instructions will be saved
but won't affect AI behavior until the feature is fully implemented.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
System instructions apply to all AI interactions in this search space. They guide how the System instructions apply to all AI interactions in this search space. They guide how the
AI responds, its tone, focus areas, and behavior patterns. AI responds, its tone, focus areas, and behavior patterns.
</AlertDescription> </AlertDescription>
@ -167,9 +165,9 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
</div> </div>
{customInstructions.trim().length === 0 && ( {customInstructions.trim().length === 0 && (
<Alert className="py-2 md:py-3"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
No system instructions are currently set. The AI will use default behavior. No system instructions are currently set. The AI will use default behavior.
</AlertDescription> </AlertDescription>
</Alert> </Alert>

View file

@ -183,9 +183,9 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
<p> <p>
SurfSense uses this shared memory to provide team-wide context across all conversations SurfSense uses this shared memory to provide team-wide context across all conversations
in this search space. in this search space.

View file

@ -155,35 +155,39 @@ export function VisionModelManager({ searchSpaceId }: VisionModelManagerProps) {
{access && !isLoading && isReadOnly && ( {access && !isLoading && isReadOnly && (
<div> <div>
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
You have <span className="font-medium">read-only</span> access to vision model <p>
configurations. Contact a space owner to request additional permissions. You have <span className="font-medium">read-only</span> access to vision model
configurations. Contact a space owner to request additional permissions.
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
</div> </div>
)} )}
{access && !isLoading && !isReadOnly && (!canCreate || !canDelete) && ( {access && !isLoading && !isReadOnly && (!canCreate || !canDelete) && (
<div> <div>
<Alert className="bg-muted/50 py-3 md:py-4"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
You can{" "} <p>
{[canCreate && "create and edit", canDelete && "delete"] You can{" "}
.filter(Boolean) {[canCreate && "create and edit", canDelete && "delete"]
.join(" and ")}{" "} .filter(Boolean)
vision model configurations .join(" and ")}{" "}
{!canDelete && ", but cannot delete them"}. vision model configurations
{!canDelete && ", but cannot delete them"}.
</p>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
</div> </div>
)} )}
{globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length > 0 && ( {globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length > 0 && (
<Alert className="bg-muted/50 py-3"> <Alert>
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" /> <Info />
<AlertDescription className="text-xs md:text-sm"> <AlertDescription>
<p> <p>
<span className="font-medium"> <span className="font-medium">
{globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length}{" "} {globalConfigs.filter((g) => !("is_auto_mode" in g && g.is_auto_mode)).length}{" "}