mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-04 05:12:38 +02:00
Merge remote-tracking branch 'upstream/dev' into fix/ui-mention-documents
This commit is contained in:
commit
e61b410805
81 changed files with 2117 additions and 2336 deletions
|
|
@ -123,9 +123,9 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
handleSkipIndexing,
|
||||
handleStartEdit,
|
||||
handleSaveConnector,
|
||||
handleDisconnectConnector,
|
||||
handleDisconnectFromList,
|
||||
handleBackFromEdit,
|
||||
handleDisconnectConnector,
|
||||
handleDisconnectFromList,
|
||||
handleBackFromEdit,
|
||||
handleBackFromConnect,
|
||||
handleBackFromYouTube,
|
||||
handleViewAccountsList,
|
||||
|
|
@ -226,27 +226,31 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
{isYouTubeView && searchSpaceId ? (
|
||||
<YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} />
|
||||
) : viewingMCPList ? (
|
||||
<ConnectorAccountsListView
|
||||
connectorType="MCP_CONNECTOR"
|
||||
connectorTitle="MCP Connectors"
|
||||
connectors={(allConnectors || []) as SearchSourceConnector[]}
|
||||
indexingConnectorIds={indexingConnectorIds}
|
||||
onBack={handleBackFromMCPList}
|
||||
onManage={handleStartEdit}
|
||||
onDisconnect={(connector) => handleDisconnectFromList(connector, () => refreshConnectors())}
|
||||
onAddAccount={handleAddNewMCPFromList}
|
||||
addButtonText="Add New MCP Server"
|
||||
/>
|
||||
<ConnectorAccountsListView
|
||||
connectorType="MCP_CONNECTOR"
|
||||
connectorTitle="MCP Connectors"
|
||||
connectors={(allConnectors || []) as SearchSourceConnector[]}
|
||||
indexingConnectorIds={indexingConnectorIds}
|
||||
onBack={handleBackFromMCPList}
|
||||
onManage={handleStartEdit}
|
||||
onDisconnect={(connector) =>
|
||||
handleDisconnectFromList(connector, () => refreshConnectors())
|
||||
}
|
||||
onAddAccount={handleAddNewMCPFromList}
|
||||
addButtonText="Add New MCP Server"
|
||||
/>
|
||||
) : viewingAccountsType ? (
|
||||
<ConnectorAccountsListView
|
||||
connectorType={viewingAccountsType.connectorType}
|
||||
connectorTitle={viewingAccountsType.connectorTitle}
|
||||
connectors={(connectors || []) as SearchSourceConnector[]}
|
||||
indexingConnectorIds={indexingConnectorIds}
|
||||
onBack={handleBackFromAccountsList}
|
||||
onManage={handleStartEdit}
|
||||
onDisconnect={(connector) => handleDisconnectFromList(connector, () => refreshConnectors())}
|
||||
onAddAccount={() => {
|
||||
<ConnectorAccountsListView
|
||||
connectorType={viewingAccountsType.connectorType}
|
||||
connectorTitle={viewingAccountsType.connectorTitle}
|
||||
connectors={(connectors || []) as SearchSourceConnector[]}
|
||||
indexingConnectorIds={indexingConnectorIds}
|
||||
onBack={handleBackFromAccountsList}
|
||||
onManage={handleStartEdit}
|
||||
onDisconnect={(connector) =>
|
||||
handleDisconnectFromList(connector, () => refreshConnectors())
|
||||
}
|
||||
onAddAccount={() => {
|
||||
// Check both OAUTH_CONNECTORS and COMPOSIO_CONNECTORS
|
||||
const oauthConnector =
|
||||
OAUTH_CONNECTORS.find(
|
||||
|
|
|
|||
|
|
@ -213,13 +213,13 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
|
|||
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 ? (
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Testing Connection...
|
||||
</>
|
||||
) : (
|
||||
"Test Connection"
|
||||
)}
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Testing Connection...
|
||||
</>
|
||||
) : (
|
||||
"Test Connection"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -218,13 +218,13 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
|
|||
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 ? (
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Testing Connection...
|
||||
</>
|
||||
) : (
|
||||
"Test Connection"
|
||||
)}
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
Testing Connection...
|
||||
</>
|
||||
) : (
|
||||
"Test Connection"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ export const TeamsConfig: FC<TeamsConfigProps> = () => {
|
|||
<div className="text-xs sm:text-sm">
|
||||
<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 messages on your behalf. Make sure you're a member of the teams
|
||||
you want to interact with.
|
||||
Your agent can search and read messages from Teams channels you have access to, and send
|
||||
messages on your behalf. Make sure you're a member of the teams you want to interact
|
||||
with.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { DateRangeSelector } from "../../components/date-range-selector";
|
|||
import { PeriodicSyncConfig } from "../../components/periodic-sync-config";
|
||||
import { SummaryConfig } from "../../components/summary-config";
|
||||
import { VisionLLMConfig } from "../../components/vision-llm-config";
|
||||
import { LIVE_CONNECTOR_TYPES, getReauthEndpoint } from "../../constants/connector-constants";
|
||||
import { getReauthEndpoint, LIVE_CONNECTOR_TYPES } from "../../constants/connector-constants";
|
||||
import { getConnectorDisplayName } from "../../tabs/all-connectors-tab";
|
||||
import { MCPServiceConfig } from "../components/mcp-service-config";
|
||||
import { getConnectorConfigComponent } from "../index";
|
||||
|
|
@ -380,8 +380,8 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
|
||||
{/* Fixed Footer - Action buttons */}
|
||||
<div className="flex-shrink-0 flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3 sm:gap-0 px-6 sm:px-12 py-6 sm:py-6 bg-muted border-t border-border">
|
||||
{showDisconnectConfirm ? (
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 flex-1 sm:flex-initial">
|
||||
{showDisconnectConfirm ? (
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 flex-1 sm:flex-initial">
|
||||
<span className="text-xs sm:text-sm text-muted-foreground sm:whitespace-nowrap">
|
||||
{isLive
|
||||
? "Your agent will lose access to this service."
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ import { DateRangeSelector } from "../../components/date-range-selector";
|
|||
import { PeriodicSyncConfig } from "../../components/periodic-sync-config";
|
||||
import { SummaryConfig } from "../../components/summary-config";
|
||||
import { VisionLLMConfig } from "../../components/vision-llm-config";
|
||||
import { LIVE_CONNECTOR_TYPES, type IndexingConfigState } from "../../constants/connector-constants";
|
||||
import {
|
||||
type IndexingConfigState,
|
||||
LIVE_CONNECTOR_TYPES,
|
||||
} from "../../constants/connector-constants";
|
||||
import { getConnectorDisplayName } from "../../tabs/all-connectors-tab";
|
||||
import { getConnectorConfigComponent } from "../index";
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
|||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { getDocumentTypeLabel } from "@/lib/documents/document-type-labels";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { COMPOSIO_CONNECTORS, LIVE_CONNECTOR_TYPES, OAUTH_CONNECTORS } from "../constants/connector-constants";
|
||||
import {
|
||||
COMPOSIO_CONNECTORS,
|
||||
LIVE_CONNECTOR_TYPES,
|
||||
OAUTH_CONNECTORS,
|
||||
} from "../constants/connector-constants";
|
||||
import { getDocumentCountForConnector } from "../utils/connector-document-mapping";
|
||||
import { getConnectorDisplayName } from "./all-connectors-tab";
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
|||
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||
import { formatRelativeDate } from "@/lib/format-date";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { LIVE_CONNECTOR_TYPES, getReauthEndpoint } from "../constants/connector-constants";
|
||||
import { getReauthEndpoint, LIVE_CONNECTOR_TYPES } from "../constants/connector-constants";
|
||||
import { useConnectorStatus } from "../hooks/use-connector-status";
|
||||
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
|
||||
|
||||
|
|
@ -182,11 +182,14 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{typeConnectors.map((connector) => {
|
||||
const isIndexing = indexingConnectorIds.has(connector.id);
|
||||
const connectorReauthEndpoint = getReauthEndpoint(connector);
|
||||
const isAuthExpired = !!connectorReauthEndpoint && connector.config?.auth_expired === true;
|
||||
const isLive = LIVE_CONNECTOR_TYPES.has(connector.connector_type) || Boolean(connector.config?.server_config);
|
||||
{typeConnectors.map((connector) => {
|
||||
const isIndexing = indexingConnectorIds.has(connector.id);
|
||||
const connectorReauthEndpoint = getReauthEndpoint(connector);
|
||||
const isAuthExpired =
|
||||
!!connectorReauthEndpoint && connector.config?.auth_expired === true;
|
||||
const isLive =
|
||||
LIVE_CONNECTOR_TYPES.has(connector.connector_type) ||
|
||||
Boolean(connector.config?.server_config);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -225,73 +228,73 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{isAuthExpired ? (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-amber-600 hover:bg-amber-700 text-white border-0 shadow-xs shrink-0"
|
||||
onClick={() => handleReauth(connector)}
|
||||
disabled={reauthingId === connector.id}
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn("size-3.5", reauthingId === connector.id && "animate-spin")}
|
||||
/>
|
||||
Re-authenticate
|
||||
</Button>
|
||||
) : isLive && onDisconnect ? (
|
||||
confirmDisconnectId === connector.id ? (
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
{isAuthExpired ? (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-amber-600 hover:bg-amber-700 text-white border-0 shadow-xs shrink-0"
|
||||
onClick={() => handleReauth(connector)}
|
||||
disabled={reauthingId === connector.id}
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn("size-3.5", reauthingId === connector.id && "animate-spin")}
|
||||
/>
|
||||
Re-authenticate
|
||||
</Button>
|
||||
) : isLive && onDisconnect ? (
|
||||
confirmDisconnectId === connector.id ? (
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium shadow-xs"
|
||||
onClick={async () => {
|
||||
setDisconnectingId(connector.id);
|
||||
setConfirmDisconnectId(null);
|
||||
try {
|
||||
await onDisconnect(connector);
|
||||
} finally {
|
||||
setDisconnectingId(null);
|
||||
}
|
||||
}}
|
||||
disabled={disconnectingId === connector.id}
|
||||
>
|
||||
{disconnectingId === connector.id ? (
|
||||
<RefreshCw className="size-3.5 animate-spin" />
|
||||
) : (
|
||||
"Confirm"
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-2 rounded-lg"
|
||||
onClick={() => setConfirmDisconnectId(null)}
|
||||
disabled={disconnectingId === connector.id}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
variant="destructive"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium shadow-xs"
|
||||
onClick={async () => {
|
||||
setDisconnectingId(connector.id);
|
||||
setConfirmDisconnectId(null);
|
||||
try {
|
||||
await onDisconnect(connector);
|
||||
} finally {
|
||||
setDisconnectingId(null);
|
||||
}
|
||||
}}
|
||||
disabled={disconnectingId === connector.id}
|
||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-red-50 hover:text-red-700 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-red-950 dark:hover:text-red-400 shrink-0"
|
||||
onClick={() => setConfirmDisconnectId(connector.id)}
|
||||
>
|
||||
{disconnectingId === connector.id ? (
|
||||
<RefreshCw className="size-3.5 animate-spin" />
|
||||
) : (
|
||||
"Confirm"
|
||||
)}
|
||||
<Trash2 className="size-3.5" />
|
||||
Disconnect
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-[11px] px-2 rounded-lg"
|
||||
onClick={() => setConfirmDisconnectId(null)}
|
||||
disabled={disconnectingId === connector.id}
|
||||
>
|
||||
Cancel
|
||||
</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-red-50 hover:text-red-700 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-red-950 dark:hover:text-red-400 shrink-0"
|
||||
onClick={() => setConfirmDisconnectId(connector.id)}
|
||||
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)}
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
Disconnect
|
||||
Manage
|
||||
</Button>
|
||||
)
|
||||
) : (
|
||||
<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>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import { openEditorPanelAtom } from "@/atoms/editor/editor-panel.atom";
|
|||
import { ImagePreview, ImageRoot, ImageZoom } from "@/components/assistant-ui/image";
|
||||
import "katex/dist/katex.min.css";
|
||||
import { InlineCitation, UrlCitation } from "@/components/assistant-ui/inline-citation";
|
||||
import { useElectronAPI } from "@/hooks/use-platform";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
|
|
@ -30,6 +29,7 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { useElectronAPI } from "@/hooks/use-platform";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function MarkdownCodeBlockSkeleton() {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
AlertCircle,
|
||||
ArrowDownIcon,
|
||||
ArrowUpIcon,
|
||||
Camera,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Clipboard,
|
||||
|
|
@ -39,6 +40,7 @@ import { chatSessionStateAtom } from "@/atoms/chat/chat-session-state.atom";
|
|||
import {
|
||||
mentionedDocumentsAtom,
|
||||
} from "@/atoms/chat/mentioned-documents.atom";
|
||||
import { pendingUserImageDataUrlsAtom } from "@/atoms/chat/pending-user-images.atom";
|
||||
import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms";
|
||||
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
|
||||
import { membersAtom } from "@/atoms/members/members-query.atoms";
|
||||
|
|
@ -88,6 +90,7 @@ import { useCommentsSync } from "@/hooks/use-comments-sync";
|
|||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
import { useElectronAPI } from "@/hooks/use-platform";
|
||||
import { getMentionDocKey } from "@/lib/chat/mention-doc-key";
|
||||
import { captureDisplayToPngDataUrl } from "@/lib/chat/display-media-capture";
|
||||
import { SLIDEOUT_PANEL_OPENED_EVENT } from "@/lib/layout-events";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
|
@ -294,6 +297,32 @@ const ConnectToolsBanner: FC<{ isThreadEmpty: boolean }> = ({ isThreadEmpty }) =
|
|||
);
|
||||
};
|
||||
|
||||
const PendingScreenImageStrip: FC = () => {
|
||||
const [urls, setUrls] = useAtom(pendingUserImageDataUrlsAtom);
|
||||
if (urls.length === 0) return null;
|
||||
return (
|
||||
<div className="mx-3 mt-2 flex flex-wrap gap-2">
|
||||
{urls.map((url, index) => (
|
||||
<div
|
||||
key={url}
|
||||
className="group relative h-14 w-14 shrink-0 overflow-hidden rounded-md border border-border/50 bg-muted"
|
||||
>
|
||||
{/* biome-ignore lint/performance/noImgElement: data URL thumbnails from capture */}
|
||||
<img src={url} alt="" className="size-full object-cover" draggable={false} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setUrls((prev) => prev.filter((_, i) => i !== index))}
|
||||
className="absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-background/90 text-muted-foreground shadow-sm transition-opacity hover:text-foreground sm:opacity-0 sm:group-hover:opacity-100"
|
||||
aria-label="Remove screenshot"
|
||||
>
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ClipboardChip: FC<{ text: string; onDismiss: () => void }> = ({ text, onDismiss }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const isLong = text.length > 120;
|
||||
|
|
@ -730,6 +759,7 @@ const Composer: FC = () => {
|
|||
</div>
|
||||
)}
|
||||
<div className="aui-composer-attachment-dropzone flex w-full flex-col overflow-hidden rounded-2xl border-input bg-muted pt-2 outline-none transition-shadow">
|
||||
<PendingScreenImageStrip />
|
||||
{clipboardInitialText && (
|
||||
<ClipboardChip
|
||||
text={clipboardInitialText}
|
||||
|
|
@ -787,11 +817,23 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
},
|
||||
[]
|
||||
);
|
||||
const pendingScreenImages = useAtomValue(pendingUserImageDataUrlsAtom);
|
||||
const setPendingScreenImages = useSetAtom(pendingUserImageDataUrlsAtom);
|
||||
const electronAPI = useElectronAPI();
|
||||
|
||||
const isComposerTextEmpty = useAuiState(({ composer }) => {
|
||||
const text = composer.text?.trim() || "";
|
||||
return text.length === 0;
|
||||
});
|
||||
const isComposerEmpty = isComposerTextEmpty && mentionedDocuments.length === 0;
|
||||
const isComposerEmpty =
|
||||
isComposerTextEmpty && mentionedDocuments.length === 0 && pendingScreenImages.length === 0;
|
||||
|
||||
const handleScreenCapture = useCallback(async () => {
|
||||
const url = electronAPI?.captureFullScreen
|
||||
? await electronAPI.captureFullScreen()
|
||||
: await captureDisplayToPngDataUrl();
|
||||
if (url) setPendingScreenImages((prev) => [...prev, url]);
|
||||
}, [electronAPI, setPendingScreenImages]);
|
||||
|
||||
const { data: userConfigs } = useAtomValue(newLLMConfigsAtom);
|
||||
const { data: globalConfigs } = useAtomValue(globalNewLLMConfigsAtom);
|
||||
|
|
@ -1218,6 +1260,17 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<TooltipIconButton
|
||||
tooltip="Capture screen"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 rounded-full"
|
||||
aria-label="Capture screen"
|
||||
onClick={() => void handleScreenCapture()}
|
||||
>
|
||||
<Camera className="size-4" />
|
||||
</TooltipIconButton>
|
||||
<AuiIf condition={({ thread }) => !thread.isRunning}>
|
||||
<ComposerPrimitive.Send asChild disabled={isSendDisabled}>
|
||||
<TooltipIconButton
|
||||
|
|
@ -1227,7 +1280,7 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
: !hasModelConfigured
|
||||
? "Please select a model from the header to start chatting"
|
||||
: isComposerEmpty
|
||||
? "Enter a message to send"
|
||||
? "Enter a message or add a screenshot to send"
|
||||
: "Send message"
|
||||
}
|
||||
side="bottom"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue