mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-27 09:46:25 +02:00
Merge remote-tracking branch 'upstream/dev' into fix/chat-ui
This commit is contained in:
commit
08e00d0991
40 changed files with 1274 additions and 865 deletions
|
|
@ -204,7 +204,9 @@ export const AssistantMessage: FC = () => {
|
|||
>
|
||||
<MessageSquare className={cn("size-4", hasComments && "fill-current")} />
|
||||
{hasComments ? (
|
||||
<span>{commentCount} {commentCount === 1 ? "comment" : "comments"}</span>
|
||||
<span>
|
||||
{commentCount} {commentCount === 1 ? "comment" : "comments"}
|
||||
</span>
|
||||
) : (
|
||||
<span>Add comment</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import { useIndexingConnectors } from "./connector-popup/hooks/use-indexing-conn
|
|||
import { ActiveConnectorsTab } from "./connector-popup/tabs/active-connectors-tab";
|
||||
import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab";
|
||||
import { ConnectorAccountsListView } from "./connector-popup/views/connector-accounts-list-view";
|
||||
import { MCPConnectorListView } from "./connector-popup/views/mcp-connector-list-view";
|
||||
import { YouTubeCrawlerView } from "./connector-popup/views/youtube-crawler-view";
|
||||
|
||||
export const ConnectorIndicator: FC = () => {
|
||||
|
|
@ -178,18 +177,16 @@ export const ConnectorIndicator: FC = () => {
|
|||
{isYouTubeView && searchSpaceId ? (
|
||||
<YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} />
|
||||
) : viewingMCPList ? (
|
||||
<div className="p-6 sm:p-12 h-full overflow-hidden">
|
||||
<MCPConnectorListView
|
||||
mcpConnectors={
|
||||
(allConnectors || []).filter(
|
||||
(c: SearchSourceConnector) => c.connector_type === "MCP_CONNECTOR"
|
||||
) as SearchSourceConnector[]
|
||||
}
|
||||
onAddNew={handleAddNewMCPFromList}
|
||||
onManageConnector={handleStartEdit}
|
||||
onBack={handleBackFromMCPList}
|
||||
/>
|
||||
</div>
|
||||
<ConnectorAccountsListView
|
||||
connectorType="MCP_CONNECTOR"
|
||||
connectorTitle="MCP Connectors"
|
||||
connectors={(allConnectors || []) as SearchSourceConnector[]}
|
||||
indexingConnectorIds={indexingConnectorIds}
|
||||
onBack={handleBackFromMCPList}
|
||||
onManage={handleStartEdit}
|
||||
onAddAccount={handleAddNewMCPFromList}
|
||||
addButtonText="Add New MCP Server"
|
||||
/>
|
||||
) : viewingAccountsType ? (
|
||||
<ConnectorAccountsListView
|
||||
connectorType={viewingAccountsType.connectorType}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { IconBrandYoutube } from "@tabler/icons-react";
|
|||
import { FileText, Loader2 } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useConnectorStatus } from "../hooks/use-connector-status";
|
||||
|
|
@ -18,6 +19,7 @@ interface ConnectorCardProps {
|
|||
isConnecting?: boolean;
|
||||
documentCount?: number;
|
||||
accountCount?: number;
|
||||
connectorCount?: number;
|
||||
isIndexing?: boolean;
|
||||
onConnect?: () => void;
|
||||
onManage?: () => void;
|
||||
|
|
@ -46,10 +48,12 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
|||
isConnecting = false,
|
||||
documentCount,
|
||||
accountCount,
|
||||
connectorCount,
|
||||
isIndexing = false,
|
||||
onConnect,
|
||||
onManage,
|
||||
}) => {
|
||||
const isMCP = connectorType === EnumConnectorName.MCP_CONNECTOR;
|
||||
// Get connector status
|
||||
const { getConnectorStatus, isConnectorEnabled, getConnectorStatusMessage, shouldShowWarnings } =
|
||||
useConnectorStatus();
|
||||
|
|
@ -112,13 +116,21 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
|||
</p>
|
||||
) : isConnected ? (
|
||||
<p className="text-[10px] text-muted-foreground mt-1 flex items-center gap-1.5">
|
||||
<span>{formatDocumentCount(documentCount)}</span>
|
||||
{accountCount !== undefined && accountCount > 0 && (
|
||||
{isMCP && connectorCount !== undefined ? (
|
||||
<span>
|
||||
{connectorCount} {connectorCount === 1 ? "server" : "servers"}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-muted-foreground/50">•</span>
|
||||
<span>
|
||||
{accountCount} {accountCount === 1 ? "Account" : "Accounts"}
|
||||
</span>
|
||||
<span>{formatDocumentCount(documentCount)}</span>
|
||||
{accountCount !== undefined && accountCount > 0 && (
|
||||
<>
|
||||
<span className="text-muted-foreground/50">•</span>
|
||||
<span>
|
||||
{accountCount} {accountCount === 1 ? "Account" : "Accounts"}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
|
|
|
|||
|
|
@ -4,18 +4,16 @@ import { CheckCircle2, ChevronDown, ChevronUp, Server, XCircle } from "lucide-re
|
|||
import { type FC, useRef, useState } from "react";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import type { MCPToolDefinition } from "@/contracts/types/mcp.types";
|
||||
import type { ConnectFormProps } from "..";
|
||||
import {
|
||||
extractServerName,
|
||||
type MCPConnectionTestResult,
|
||||
parseMCPConfig,
|
||||
testMCPConnection,
|
||||
type MCPConnectionTestResult,
|
||||
} from "../../utils/mcp-config-validator";
|
||||
import type { ConnectFormProps } from "..";
|
||||
|
||||
export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
|
||||
const isSubmittingRef = useRef(false);
|
||||
|
|
@ -46,7 +44,7 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
|||
name: "My Remote MCP Server",
|
||||
url: "https://your-mcp-server.com/mcp",
|
||||
headers: {
|
||||
"API_KEY": "your_api_key_here",
|
||||
API_KEY: "your_api_key_here",
|
||||
},
|
||||
transport: "streamable-http",
|
||||
},
|
||||
|
|
@ -178,29 +176,47 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
|||
id="config"
|
||||
value={configJson}
|
||||
onChange={(e) => handleConfigChange(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
const start = target.selectionStart;
|
||||
const end = target.selectionEnd;
|
||||
const indent = " "; // 2 spaces for JSON
|
||||
const newValue =
|
||||
configJson.substring(0, start) + indent + configJson.substring(end);
|
||||
handleConfigChange(newValue);
|
||||
// Set cursor position after the inserted tab
|
||||
requestAnimationFrame(() => {
|
||||
target.selectionStart = target.selectionEnd = start + indent.length;
|
||||
});
|
||||
}
|
||||
}}
|
||||
placeholder={DEFAULT_CONFIG}
|
||||
rows={16}
|
||||
className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`}
|
||||
/>
|
||||
{jsonError && <p className="text-xs text-red-500">JSON Error: {jsonError}</p>}
|
||||
<p className="text-[10px] sm:text-xs text-muted-foreground">
|
||||
<strong>Local (stdio):</strong> command, args, env, transport: "stdio"<br />
|
||||
<strong>Remote (HTTP):</strong> url, headers, transport: "streamable-http"
|
||||
Paste a single MCP server configuration. Must include: name, command, args (optional),
|
||||
env (optional), transport (optional).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Test Connection */}
|
||||
<div className="pt-4">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleTestConnection}
|
||||
disabled={isTesting}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
variant="secondary"
|
||||
className="w-full h-8 text-[13px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80"
|
||||
>
|
||||
{isTesting ? "Testing Connection..." : "Test Connection"}
|
||||
{isTesting ? "Testing Connection" : "Test Connection"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Test Result */}
|
||||
{testResult && (
|
||||
<Alert
|
||||
className={
|
||||
|
|
@ -226,7 +242,7 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2"
|
||||
className="h-6 px-2 self-start sm:self-auto text-xs"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -236,18 +252,20 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
|||
{showDetails ? (
|
||||
<>
|
||||
<ChevronUp className="h-3 w-3 mr-1" />
|
||||
Hide Details
|
||||
<span className="hidden sm:inline">Hide Details</span>
|
||||
<span className="sm:hidden">Hide</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="h-3 w-3 mr-1" />
|
||||
Show Details
|
||||
<span className="hidden sm:inline">Show Details</span>
|
||||
<span className="sm:hidden">Show</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<AlertDescription className="text-xs mt-1">
|
||||
<AlertDescription className="text-[10px] sm:text-xs mt-1">
|
||||
{testResult.message}
|
||||
{showDetails && testResult.tools.length > 0 && (
|
||||
<div className="mt-3 pt-3 border-t border-green-500/20">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { File, FileText, FileSpreadsheet, FolderClosed, Image, Presentation } from "lucide-react";
|
||||
import { File, FileSpreadsheet, FileText, FolderClosed, Image, Presentation } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { GoogleDriveFolderTree } from "@/components/connectors/google-drive-folder-tree";
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ import { Label } from "@/components/ui/label";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import type { MCPServerConfig } from "@/contracts/types/mcp.types";
|
||||
import type { ConnectorConfigProps } from "../index";
|
||||
import {
|
||||
type MCPConnectionTestResult,
|
||||
parseMCPConfig,
|
||||
testMCPConnection,
|
||||
type MCPConnectionTestResult,
|
||||
} from "../../utils/mcp-config-validator";
|
||||
import type { ConnectorConfigProps } from "../index";
|
||||
|
||||
interface MCPConfigProps extends ConnectorConfigProps {
|
||||
onNameChange?: (name: string) => void;
|
||||
|
|
@ -47,10 +47,10 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
const serverConfig = connector.config?.server_config as MCPServerConfig | undefined;
|
||||
if (serverConfig) {
|
||||
const transport = serverConfig.transport || "stdio";
|
||||
|
||||
|
||||
// Build config object based on transport type
|
||||
let configObj: Record<string, unknown>;
|
||||
|
||||
|
||||
if (transport === "streamable-http" || transport === "http" || transport === "sse") {
|
||||
// HTTP transport - use url and headers
|
||||
configObj = {
|
||||
|
|
@ -67,7 +67,7 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
transport: transport,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
setConfigJson(JSON.stringify(configObj, null, 2));
|
||||
}
|
||||
}, [isValidConnector, connector.name, connector.config?.server_config]);
|
||||
|
|
@ -148,15 +148,23 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Server Name */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Server Name *</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => handleNameChange(e.target.value)}
|
||||
placeholder="e.g., Filesystem Server"
|
||||
required
|
||||
/>
|
||||
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name" className="text-xs sm:text-sm">
|
||||
Server Name
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => handleNameChange(e.target.value)}
|
||||
placeholder="e.g., Filesystem Server"
|
||||
className="border-slate-400/20 focus-visible:border-slate-400/40"
|
||||
required
|
||||
/>
|
||||
<p className="text-[10px] sm:text-xs text-muted-foreground">
|
||||
A friendly name to identify this connector.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Server Configuration */}
|
||||
|
|
@ -173,12 +181,29 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
id="config"
|
||||
value={configJson}
|
||||
onChange={(e) => handleConfigChange(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
const start = target.selectionStart;
|
||||
const end = target.selectionEnd;
|
||||
const indent = " "; // 2 spaces for JSON
|
||||
const newValue =
|
||||
configJson.substring(0, start) + indent + configJson.substring(end);
|
||||
handleConfigChange(newValue);
|
||||
// Set cursor position after the inserted tab
|
||||
requestAnimationFrame(() => {
|
||||
target.selectionStart = target.selectionEnd = start + indent.length;
|
||||
});
|
||||
}
|
||||
}}
|
||||
rows={16}
|
||||
className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`}
|
||||
/>
|
||||
{jsonError && <p className="text-xs text-red-500">JSON Error: {jsonError}</p>}
|
||||
<p className="text-[10px] sm:text-xs text-muted-foreground">
|
||||
<strong>Local (stdio):</strong> command, args, env, transport: "stdio"<br />
|
||||
<strong>Local (stdio):</strong> command, args, env, transport: "stdio"
|
||||
<br />
|
||||
<strong>Remote (HTTP):</strong> url, headers, transport: "streamable-http"
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -189,10 +214,10 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
type="button"
|
||||
onClick={handleTestConnection}
|
||||
disabled={isTesting}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
variant="secondary"
|
||||
className="w-full h-8 text-[13px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80"
|
||||
>
|
||||
{isTesting ? "Testing Connection..." : "Test Connection"}
|
||||
{isTesting ? "Testing Connection" : "Test Connection"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
@ -211,7 +236,7 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
<XCircle className="h-4 w-4 text-red-600" />
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-0">
|
||||
<AlertTitle className="text-sm">
|
||||
{testResult.status === "success"
|
||||
? "Connection Successful"
|
||||
|
|
@ -222,7 +247,7 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2"
|
||||
className="h-6 px-2 self-start sm:self-auto text-xs"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -232,12 +257,14 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
{showDetails ? (
|
||||
<>
|
||||
<ChevronUp className="h-3 w-3 mr-1" />
|
||||
Hide Details
|
||||
<span className="hidden sm:inline">Hide Details</span>
|
||||
<span className="sm:hidden">Hide</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="h-3 w-3 mr-1" />
|
||||
Show Details
|
||||
<span className="hidden sm:inline">Show Details</span>
|
||||
<span className="sm:hidden">Show</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight text-wrap whitespace-normal wrap-break-word">
|
||||
{connector.connector_type === "MCP_CONNECTOR" ? "MCP Server" : connector.name}
|
||||
{connector.name}
|
||||
</h2>
|
||||
<p className="text-xs sm:text-base text-muted-foreground mt-1">
|
||||
Manage your connector settings and sync configuration
|
||||
|
|
|
|||
|
|
@ -646,7 +646,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
const successMessage =
|
||||
currentConnectorType === "MCP_CONNECTOR"
|
||||
? `${connector.name} MCP server added successfully`
|
||||
? `${connector.name} added successfully`
|
||||
: `${connectorTitle} connected and indexing started!`;
|
||||
toast.success(successMessage, {
|
||||
description: periodicEnabledForIndexing
|
||||
|
|
@ -711,7 +711,7 @@ export const useConnectorDialog = () => {
|
|||
// Other non-indexable connectors - just show success message and close
|
||||
const successMessage =
|
||||
currentConnectorType === "MCP_CONNECTOR"
|
||||
? `${connector.name} MCP server added successfully`
|
||||
? `${connector.name} added successfully`
|
||||
: `${connectorTitle} connected successfully!`;
|
||||
toast.success(successMessage);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { ConnectorCard } from "../components/connector-card";
|
||||
import { CRAWLERS, OAUTH_CONNECTORS, OTHER_CONNECTORS } from "../constants/connector-constants";
|
||||
|
|
@ -161,6 +162,16 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
|||
);
|
||||
const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id);
|
||||
|
||||
// For MCP connectors, count total MCP connectors instead of document count
|
||||
const isMCP = connector.connectorType === EnumConnectorName.MCP_CONNECTOR;
|
||||
const mcpConnectorCount =
|
||||
isMCP && allConnectors
|
||||
? allConnectors.filter(
|
||||
(c: SearchSourceConnector) =>
|
||||
c.connector_type === EnumConnectorName.MCP_CONNECTOR
|
||||
).length
|
||||
: undefined;
|
||||
|
||||
const handleConnect = onConnectNonOAuth
|
||||
? () => onConnectNonOAuth(connector.connectorType)
|
||||
: () => {}; // Fallback - connector popup should handle all connector types
|
||||
|
|
@ -175,6 +186,7 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
|||
isConnected={isConnected}
|
||||
isConnecting={isConnecting}
|
||||
documentCount={documentCount}
|
||||
connectorCount={mcpConnectorCount}
|
||||
isIndexing={isIndexing}
|
||||
onConnect={handleConnect}
|
||||
onManage={
|
||||
|
|
|
|||
|
|
@ -138,35 +138,37 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
|
|||
|
||||
// Replace technical error messages with user-friendly ones
|
||||
if (errorMsg.includes("expected string, received undefined")) {
|
||||
errorMsg = "This field is required";
|
||||
errorMsg = fieldPath ? `The '${fieldPath}' field is required` : "This field is required";
|
||||
} else if (errorMsg.includes("Invalid input")) {
|
||||
errorMsg = "Invalid value";
|
||||
errorMsg = fieldPath ? `The '${fieldPath}' field has an invalid value` : "Invalid value";
|
||||
} else if (fieldPath && !errorMsg.toLowerCase().includes(fieldPath.toLowerCase())) {
|
||||
// If error message doesn't mention the field name, prepend it
|
||||
errorMsg = `The '${fieldPath}' field: ${errorMsg}`;
|
||||
}
|
||||
|
||||
const formattedError = fieldPath ? `${fieldPath}: ${errorMsg}` : errorMsg;
|
||||
|
||||
console.error("[MCP Validator] ❌ Validation error:", formattedError);
|
||||
console.error("[MCP Validator] ❌ Validation error:", errorMsg);
|
||||
console.error("[MCP Validator] Full Zod errors:", result.error.issues);
|
||||
|
||||
return {
|
||||
config: null,
|
||||
error: formattedError,
|
||||
error: errorMsg,
|
||||
};
|
||||
}
|
||||
|
||||
// Build config based on transport type
|
||||
const config: MCPServerConfig = result.data.transport === "stdio" || !result.data.transport
|
||||
? {
|
||||
command: (result.data as z.infer<typeof StdioConfigSchema>).command,
|
||||
args: (result.data as z.infer<typeof StdioConfigSchema>).args,
|
||||
env: (result.data as z.infer<typeof StdioConfigSchema>).env,
|
||||
transport: "stdio" as const,
|
||||
}
|
||||
: {
|
||||
url: (result.data as z.infer<typeof HttpConfigSchema>).url,
|
||||
headers: (result.data as z.infer<typeof HttpConfigSchema>).headers,
|
||||
transport: result.data.transport as "streamable-http" | "http" | "sse",
|
||||
};
|
||||
const config: MCPServerConfig =
|
||||
result.data.transport === "stdio" || !result.data.transport
|
||||
? {
|
||||
command: (result.data as z.infer<typeof StdioConfigSchema>).command,
|
||||
args: (result.data as z.infer<typeof StdioConfigSchema>).args,
|
||||
env: (result.data as z.infer<typeof StdioConfigSchema>).env,
|
||||
transport: "stdio" as const,
|
||||
}
|
||||
: {
|
||||
url: (result.data as z.infer<typeof HttpConfigSchema>).url,
|
||||
headers: (result.data as z.infer<typeof HttpConfigSchema>).headers,
|
||||
transport: result.data.transport as "streamable-http" | "http" | "sse",
|
||||
};
|
||||
|
||||
// Cache the successfully parsed config
|
||||
configCache.set(configJson, {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns";
|
||||
import { ArrowLeft, Loader2, Plus } from "lucide-react";
|
||||
import { ArrowLeft, Loader2, Plus, Server } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
|
@ -19,6 +20,7 @@ interface ConnectorAccountsListViewProps {
|
|||
onManage: (connector: SearchSourceConnector) => void;
|
||||
onAddAccount: () => void;
|
||||
isConnecting?: boolean;
|
||||
addButtonText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -70,6 +72,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
onManage,
|
||||
onAddAccount,
|
||||
isConnecting = false,
|
||||
addButtonText,
|
||||
}) => {
|
||||
// Get connector status
|
||||
const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
|
||||
|
|
@ -80,6 +83,22 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
// Filter connectors to only show those of this type
|
||||
const typeConnectors = connectors.filter((c) => c.connector_type === connectorType);
|
||||
|
||||
// Determine button text - default to "Add Account" unless specified
|
||||
const buttonText =
|
||||
addButtonText ||
|
||||
(connectorType === EnumConnectorName.MCP_CONNECTOR ? "Add New MCP Server" : "Add Account");
|
||||
const isMCP = connectorType === EnumConnectorName.MCP_CONNECTOR;
|
||||
|
||||
// Helper to get display name for connector (handles MCP server name extraction)
|
||||
const getDisplayName = (connector: SearchSourceConnector): string => {
|
||||
if (isMCP) {
|
||||
// For MCP, extract server name from config if available
|
||||
const serverName = connector.config?.server_config?.name || connector.name;
|
||||
return serverName;
|
||||
}
|
||||
return getConnectorDisplayName(connector.name);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Header */}
|
||||
|
|
@ -115,22 +134,22 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
onClick={onAddAccount}
|
||||
disabled={isConnecting || !isEnabled}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg border-2 border-dashed text-left transition-all duration-200 shrink-0 self-center sm:self-auto sm:w-auto",
|
||||
"flex items-center justify-center gap-1.5 h-8 px-3 rounded-md border-2 border-dashed text-xs sm:text-sm transition-all duration-200 shrink-0 w-full sm:w-auto",
|
||||
!isEnabled
|
||||
? "border-border/30 opacity-50 cursor-not-allowed"
|
||||
: "border-primary/50 hover:bg-primary/5",
|
||||
: "border-slate-400/20 dark:border-white/20 hover:bg-primary/5",
|
||||
isConnecting && "opacity-50 cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
<div className="flex h-5 w-5 sm:h-6 sm:w-6 items-center justify-center rounded-md bg-primary/10 shrink-0">
|
||||
<div className="flex h-5 w-5 items-center justify-center rounded-md bg-primary/10 shrink-0">
|
||||
{isConnecting ? (
|
||||
<Loader2 className="size-3 sm:size-3.5 animate-spin text-primary" />
|
||||
<Loader2 className="size-3 animate-spin text-primary" />
|
||||
) : (
|
||||
<Plus className="size-3 sm:size-3.5 text-primary" />
|
||||
<Plus className="size-3 text-primary" />
|
||||
)}
|
||||
</div>
|
||||
<span className="text-[11px] sm:text-[12px] font-medium">
|
||||
{isConnecting ? "Connecting" : "Add Account"}
|
||||
<span className="text-xs sm:text-sm font-medium">
|
||||
{isConnecting ? "Connecting" : buttonText}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -139,61 +158,81 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto px-6 sm:px-12 pt-0 sm:pt-6 pb-6 sm:pb-8">
|
||||
{/* Connected Accounts Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{typeConnectors.map((connector) => {
|
||||
const isIndexing = indexingConnectorIds.has(connector.id);
|
||||
{typeConnectors.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div className="h-16 w-16 rounded-full bg-slate-400/5 dark:bg-white/5 flex items-center justify-center mb-4">
|
||||
{isMCP ? (
|
||||
<Server className="h-8 w-8 text-muted-foreground" />
|
||||
) : (
|
||||
getConnectorIcon(connectorType, "size-8")
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-sm font-medium mb-1">
|
||||
{isMCP ? "No MCP Servers" : `No ${connectorTitle} Accounts`}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground max-w-[280px]">
|
||||
{isMCP
|
||||
? "Get started by adding your first Model Context Protocol server"
|
||||
: `Get started by connecting your first ${connectorTitle} account`}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{typeConnectors.map((connector) => {
|
||||
const isIndexing = indexingConnectorIds.has(connector.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={connector.id}
|
||||
className={cn(
|
||||
"flex items-center gap-4 p-4 rounded-xl transition-all",
|
||||
isIndexing
|
||||
? "bg-primary/5 border-0"
|
||||
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10 border border-border"
|
||||
)}
|
||||
>
|
||||
return (
|
||||
<div
|
||||
key={connector.id}
|
||||
className={cn(
|
||||
"flex h-12 w-12 items-center justify-center rounded-lg border shrink-0",
|
||||
"flex items-center gap-4 p-4 rounded-xl transition-all",
|
||||
isIndexing
|
||||
? "bg-primary/10 border-primary/20"
|
||||
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
|
||||
? "bg-primary/5 border-0"
|
||||
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10 border border-border"
|
||||
)}
|
||||
>
|
||||
{getConnectorIcon(connector.connector_type, "size-6")}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[14px] font-semibold leading-tight truncate">
|
||||
{getConnectorDisplayName(connector.name)}
|
||||
</p>
|
||||
{isIndexing ? (
|
||||
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
Syncing
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-12 w-12 items-center justify-center rounded-lg border shrink-0",
|
||||
isIndexing
|
||||
? "bg-primary/10 border-primary/20"
|
||||
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
|
||||
)}
|
||||
>
|
||||
{getConnectorIcon(connector.connector_type, "size-6")}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[14px] font-semibold leading-tight truncate">
|
||||
{getDisplayName(connector)}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-[10px] text-muted-foreground mt-1 whitespace-nowrap truncate">
|
||||
{isIndexableConnector(connector.connector_type)
|
||||
? connector.last_indexed_at
|
||||
? `Last indexed: ${formatLastIndexedDate(connector.last_indexed_at)}`
|
||||
: "Never indexed"
|
||||
: "Active"}
|
||||
</p>
|
||||
)}
|
||||
{isIndexing ? (
|
||||
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
Syncing
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-[10px] text-muted-foreground mt-1 whitespace-nowrap truncate">
|
||||
{isIndexableConnector(connector.connector_type)
|
||||
? connector.last_indexed_at
|
||||
? `Last indexed: ${formatLastIndexedDate(connector.last_indexed_at)}`
|
||||
: "Never indexed"
|
||||
: "Active"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
|
||||
onClick={() => onManage(connector)}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
|
||||
onClick={() => onManage(connector)}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,134 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { Plus, Server, XCircle } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface MCPConnectorListViewProps {
|
||||
mcpConnectors: SearchSourceConnector[];
|
||||
onAddNew: () => void;
|
||||
onManageConnector: (connector: SearchSourceConnector) => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
export const MCPConnectorListView: FC<MCPConnectorListViewProps> = ({
|
||||
mcpConnectors,
|
||||
onAddNew,
|
||||
onManageConnector,
|
||||
onBack,
|
||||
}) => {
|
||||
// Validate that all connectors are MCP connectors
|
||||
const invalidConnectors = mcpConnectors.filter(
|
||||
(c) => c.connector_type !== EnumConnectorName.MCP_CONNECTOR
|
||||
);
|
||||
|
||||
if (invalidConnectors.length > 0) {
|
||||
console.error(
|
||||
"MCPConnectorListView received non-MCP connectors:",
|
||||
invalidConnectors.map((c) => c.connector_type)
|
||||
);
|
||||
return (
|
||||
<Alert className="border-red-500/50 bg-red-500/10">
|
||||
<XCircle className="h-4 w-4 text-red-600" />
|
||||
<AlertTitle>Invalid Connector Type</AlertTitle>
|
||||
<AlertDescription>
|
||||
This view can only display MCP connectors. Found {invalidConnectors.length} invalid
|
||||
connector(s).
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6 shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<Button variant="ghost" size="icon" onClick={onBack} className="h-8 w-8">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="m15 18-6-6 6-6" />
|
||||
</svg>
|
||||
</Button>
|
||||
<div>
|
||||
<h2 className="text-lg sm:text-xl font-semibold">MCP Connectors</h2>
|
||||
<p className="text-xs sm:text-sm text-muted-foreground">
|
||||
Manage your Model Context Protocol servers
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add New Button */}
|
||||
<div className="mb-4 shrink-0">
|
||||
<Button onClick={onAddNew} className="w-full" variant="outline">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add New MCP Server
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* MCP Connectors List */}
|
||||
<div className="space-y-3 flex-1 overflow-y-auto">
|
||||
{mcpConnectors.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div className="h-16 w-16 rounded-full bg-slate-400/5 dark:bg-white/5 flex items-center justify-center mb-4">
|
||||
<Server className="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 className="text-sm font-medium mb-1">No MCP Servers</h3>
|
||||
<p className="text-xs text-muted-foreground max-w-[280px]">
|
||||
Get started by adding your first Model Context Protocol server
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
mcpConnectors.map((connector) => {
|
||||
// Extract server name from config
|
||||
const serverName = connector.config?.server_config?.name || connector.name;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={connector.id}
|
||||
className={cn(
|
||||
"flex items-center gap-4 p-4 rounded-xl border border-border transition-all",
|
||||
"bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-12 w-12 items-center justify-center rounded-lg border shrink-0",
|
||||
"bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
|
||||
)}
|
||||
>
|
||||
{getConnectorIcon("MCP_CONNECTOR", "size-6")}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[14px] font-semibold leading-tight truncate">{serverName}</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
|
||||
onClick={() => onManageConnector(connector)}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -11,8 +11,8 @@ import {
|
|||
useState,
|
||||
} from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import type { Document } from "@/contracts/types/document.types";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import type { Document } from "@/contracts/types/document.types";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface MentionedDocument {
|
||||
|
|
|
|||
|
|
@ -36,10 +36,12 @@ export function CommentPanel({
|
|||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cn(
|
||||
"flex min-h-[120px] items-center justify-center p-4",
|
||||
!isMobile && "w-96 rounded-lg border bg-card"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex min-h-[120px] items-center justify-center p-4",
|
||||
!isMobile && "w-96 rounded-lg border bg-card"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<div className="size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
Loading comments...
|
||||
|
|
@ -57,10 +59,7 @@ export function CommentPanel({
|
|||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col",
|
||||
isMobile ? "w-full" : "w-85 rounded-lg border bg-card"
|
||||
)}
|
||||
className={cn("flex flex-col", isMobile ? "w-full" : "w-85 rounded-lg border bg-card")}
|
||||
style={!isMobile && effectiveMaxHeight ? { maxHeight: effectiveMaxHeight } : undefined}
|
||||
>
|
||||
{hasThreads && (
|
||||
|
|
@ -92,11 +91,7 @@ export function CommentPanel({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className={cn(
|
||||
"p-3",
|
||||
showEmptyState && !isMobile && "border-t",
|
||||
isMobile && "border-t"
|
||||
)}>
|
||||
<div className={cn("p-3", showEmptyState && !isMobile && "border-t", isMobile && "border-t")}>
|
||||
{isComposerOpen ? (
|
||||
<CommentComposer
|
||||
members={members}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { MessageSquare } from "lucide-react";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/components/ui/sheet";
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CommentPanelContainer } from "../comment-panel-container/comment-panel-container";
|
||||
import type { CommentSheetProps } from "./types";
|
||||
|
|
@ -26,9 +21,7 @@ export function CommentSheet({
|
|||
side={side}
|
||||
className={cn(
|
||||
"flex flex-col p-0",
|
||||
isBottomSheet
|
||||
? "h-[85vh] max-h-[85vh] rounded-t-xl"
|
||||
: "h-full w-full max-w-md"
|
||||
isBottomSheet ? "h-[85vh] max-h-[85vh] rounded-t-xl" : "h-full w-full max-w-md"
|
||||
)}
|
||||
>
|
||||
{/* Drag handle indicator - only for bottom sheet */}
|
||||
|
|
@ -37,10 +30,7 @@ export function CommentSheet({
|
|||
<div className="h-1 w-10 rounded-full bg-muted-foreground/30" />
|
||||
</div>
|
||||
)}
|
||||
<SheetHeader className={cn(
|
||||
"flex-shrink-0 border-b px-4",
|
||||
isBottomSheet ? "pb-3" : "py-4"
|
||||
)}>
|
||||
<SheetHeader className={cn("flex-shrink-0 border-b px-4", isBottomSheet ? "pb-3" : "py-4")}>
|
||||
<SheetTitle className="flex items-center gap-2 text-base font-semibold">
|
||||
<MessageSquare className="size-5" />
|
||||
Comments
|
||||
|
|
@ -52,11 +42,7 @@ export function CommentSheet({
|
|||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||
<CommentPanelContainer
|
||||
messageId={messageId}
|
||||
isOpen={true}
|
||||
variant="mobile"
|
||||
/>
|
||||
<CommentPanelContainer messageId={messageId} isOpen={true} variant="mobile" />
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
ChevronDown,
|
||||
ChevronRight,
|
||||
File,
|
||||
FileSpreadsheet,
|
||||
FileText,
|
||||
FolderClosed,
|
||||
FolderOpen,
|
||||
|
|
@ -11,7 +12,6 @@ import {
|
|||
Image,
|
||||
Loader2,
|
||||
Presentation,
|
||||
FileSpreadsheet,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
|
|
|||
|
|
@ -28,21 +28,22 @@ export function Header({
|
|||
const hasThread = isChatPage && currentThreadState.id !== null;
|
||||
|
||||
// Create minimal thread object for ChatShareButton (used for API calls)
|
||||
const threadForButton: ThreadRecord | null = hasThread
|
||||
? {
|
||||
id: currentThreadState.id!,
|
||||
visibility: currentThreadState.visibility ?? "PRIVATE",
|
||||
// These fields are not used by ChatShareButton for display, only for checks
|
||||
created_by_id: null,
|
||||
search_space_id: 0,
|
||||
title: "",
|
||||
archived: false,
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
}
|
||||
: null;
|
||||
const threadForButton: ThreadRecord | null =
|
||||
hasThread && currentThreadState.id !== null
|
||||
? {
|
||||
id: currentThreadState.id,
|
||||
visibility: currentThreadState.visibility ?? "PRIVATE",
|
||||
// These fields are not used by ChatShareButton for display, only for checks
|
||||
created_by_id: null,
|
||||
search_space_id: 0,
|
||||
title: "",
|
||||
archived: false,
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
}
|
||||
: null;
|
||||
|
||||
const handleVisibilityChange = (visibility: ChatVisibility) => {
|
||||
const handleVisibilityChange = (_visibility: ChatVisibility) => {
|
||||
// Visibility change is handled by ChatShareButton internally via Jotai
|
||||
// This callback can be used for additional side effects if needed
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { Bell } from "lucide-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useNotifications } from "@/hooks/use-notifications";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||
import { NotificationPopup } from "./NotificationPopup";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useParams } from "next/navigation";
|
||||
import { NotificationPopup } from "./NotificationPopup";
|
||||
|
||||
export function NotificationButton() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
"use client";
|
||||
|
||||
import { Bell, CheckCheck, Loader2, AlertCircle, CheckCircle2 } from "lucide-react";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { AlertCircle, Bell, CheckCheck, CheckCircle2, Loader2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import type { Notification } from "@/hooks/use-notifications";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item";
|
||||
|
||||
interface NotificationPopupProps {
|
||||
notifications: Notification[];
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||
import {
|
||||
initElectric,
|
||||
cleanupElectric,
|
||||
isElectricInitialized,
|
||||
type ElectricClient,
|
||||
initElectric,
|
||||
isElectricInitialized,
|
||||
} from "@/lib/electric/client";
|
||||
import { ElectricContext } from "@/lib/electric/context";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue