Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions apps/web/components/add-document/connections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,11 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
const projects = (queryClient.getQueryData<Project[]>(["projects"]) ||
[]) as Project[]

const handleUpgrade = async () => {
const handleUpgrade = async (planId: "api_pro" | "api_max" = "api_pro") => {
setIsUpgrading(true)
try {
const result = await autumn.attach({
planId: "api_pro",
planId,
successUrl: window.location.href,
})
if (result?.paymentUrl) {
Expand Down Expand Up @@ -614,11 +614,21 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
</span>
)}
<Button
onClick={() => setGranolaModalOpen(true)}
disabled={!isMaxUser}
onClick={() => {
if (!isMaxUser) {
handleUpgrade("api_max")
return
}
setGranolaModalOpen(true)
}}
disabled={isUpgrading || autumn.isLoading}
className="bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 text-[14px] font-medium px-3 py-1.5 h-8"
>
Connect
{!isMaxUser
? isUpgrading || autumn.isLoading
? "Upgrading..."
: "Upgrade"
: "Connect"}
</Button>
</>
) : (
Expand Down Expand Up @@ -783,8 +793,14 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
</div>
</DropdownMenuItem>
<DropdownMenuItem
disabled={!isMaxUser}
onClick={() => setGranolaModalOpen(true)}
disabled={isUpgrading || autumn.isLoading}
onClick={() => {
if (!isMaxUser) {
handleUpgrade("api_max")
return
}
setGranolaModalOpen(true)
}}
className="flex items-start gap-2.5 px-3 py-2.5 rounded-md cursor-pointer text-white opacity-60 hover:opacity-100 hover:bg-[#293952]/40 focus:bg-[#293952]/40 focus:opacity-100 data-disabled:opacity-40 data-disabled:cursor-not-allowed data-disabled:hover:bg-transparent"
>
<Granola className="size-5 mt-0.5 shrink-0" />
Expand All @@ -798,7 +814,9 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
)}
</span>
<span className="text-[11px] text-[#737373] leading-tight">
Meeting notes & transcripts
{isMaxUser
? "Meeting notes & transcripts"
: "Upgrade to Max"}
</span>
</div>
</DropdownMenuItem>
Expand Down
103 changes: 102 additions & 1 deletion apps/web/components/chat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export function ChatSidebar({
const [attachmentDrafts, setAttachmentDrafts] = useState<
ChatAttachmentDraft[]
>([])
const [isChatDraggingFiles, setIsChatDraggingFiles] = useState(false)
const [selectedModel, setSelectedModel] = useState<ModelId>(
initialSelectedModel ?? "grok-4.3",
)
Expand Down Expand Up @@ -246,6 +247,7 @@ export function ChatSidebar({
null,
)
const messagesContainerRef = useRef<HTMLDivElement>(null)
const chatDragDepthRef = useRef(0)
const isScrolledToBottomRef = useRef(true)
const userJustSentRef = useRef(false)
const sentQueuedMessageRef = useRef<string | null>(null)
Expand Down Expand Up @@ -725,6 +727,77 @@ export function ChatSidebar({
[attachmentDrafts, currentChatId, uploadAttachmentDraft],
)

const hasDraggedFiles = useCallback(
(event: React.DragEvent) =>
Array.from(event.dataTransfer.types).includes("Files"),
[],
)

const resetChatFileDrag = useCallback(() => {
chatDragDepthRef.current = 0
setIsChatDraggingFiles(false)
}, [])

const handleChatDragEnter = useCallback(
(event: React.DragEvent) => {
if (!hasDraggedFiles(event)) return
event.preventDefault()
event.stopPropagation()

chatDragDepthRef.current += 1
if (status !== "submitted" && status !== "streaming") {
setIsChatDraggingFiles(true)
}
},
[hasDraggedFiles, status],
)

const handleChatDragOver = useCallback(
(event: React.DragEvent) => {
if (!hasDraggedFiles(event)) return
event.preventDefault()
event.stopPropagation()
event.dataTransfer.dropEffect =
status === "submitted" || status === "streaming" ? "none" : "copy"
},
[hasDraggedFiles, status],
)

const handleChatDragLeave = useCallback(
(event: React.DragEvent) => {
if (!hasDraggedFiles(event)) return
event.preventDefault()
event.stopPropagation()

chatDragDepthRef.current = Math.max(0, chatDragDepthRef.current - 1)
if (chatDragDepthRef.current === 0) {
setIsChatDraggingFiles(false)
}
},
[hasDraggedFiles],
)

const handleChatDrop = useCallback(
(event: React.DragEvent) => {
if (!hasDraggedFiles(event)) return
event.preventDefault()
event.stopPropagation()

resetChatFileDrag()
const files = event.dataTransfer.files
if (status !== "submitted" && status !== "streaming" && files.length) {
handleAddAttachmentFiles(files)
}
},
[handleAddAttachmentFiles, hasDraggedFiles, resetChatFileDrag, status],
)

useEffect(() => {
if (status === "submitted" || status === "streaming") {
resetChatFileDrag()
}
}, [resetChatFileDrag, status])

useEffect(() => {
if (pendingThreadLoad && currentChatId === pendingThreadLoad.id) {
setMessages(pendingThreadLoad.messages)
Expand Down Expand Up @@ -1765,6 +1838,27 @@ export function ChatSidebar({
</div>
) : null

const chatDropOverlay = isChatDraggingFiles ? (
<div
className={cn(
"pointer-events-none absolute inset-0 z-[80] grid place-items-center border border-dashed border-[#4B5563] bg-black/72 text-sm font-medium text-fg-primary backdrop-blur-sm",
isMobile || isPageDesktop ? "rounded-none" : "rounded-2xl",
)}
aria-hidden="true"
>
<div className="rounded-lg border border-white/10 bg-black/50 px-4 py-2 shadow-[0_12px_32px_rgba(0,0,0,0.35)]">
Drop files to attach
</div>
</div>
) : null
const chatDropTargetProps = {
onDragEnter: handleChatDragEnter,
onDragOver: handleChatDragOver,
onDragLeave: handleChatDragLeave,
onDrop: handleChatDrop,
onDragEnd: resetChatFileDrag,
}

const shell = (
<>
{showHeaderRow ? (
Expand Down Expand Up @@ -1983,6 +2077,7 @@ export function ChatSidebar({
onRetryAttachment={handleRetryAttachment}
canSend={canSendMessage}
attachmentAccept={CHAT_ATTACHMENT_ACCEPT}
disableFileDropZone
sendDisabled={isResponding && isQueueFull}
sendDisabledTooltip={`Queue is full (${CHAT_QUEUE_LIMIT} max)`}
activeStatus={
Expand Down Expand Up @@ -2063,7 +2158,9 @@ export function ChatSidebar({
layout === "page" ? { opacity: 0, y: 12 } : { x: "100px", opacity: 0 }
}
transition={{ duration: 0.3, ease: "easeOut", bounce: 0 }}
{...(!isPageDesktop ? chatDropTargetProps : {})}
>
{!isPageDesktop && chatDropOverlay}
{chatHistorySheet}
{isPageDesktop ? (
<div className="flex h-full min-h-0 w-full flex-1 flex-row">
Expand All @@ -2073,7 +2170,11 @@ export function ChatSidebar({
chatProject === AUTO_CHAT_SPACE_ID ? null : [chatProject]
}
/>
<div className="flex h-full min-h-0 w-full min-w-0 max-w-[min(720px,100%)] shrink-0 basis-[min(720px,50vw)] flex-col">
<div
{...chatDropTargetProps}
className="relative flex h-full min-h-0 w-full min-w-0 max-w-[min(720px,100%)] shrink-0 basis-[min(720px,50vw)] flex-col"
>
{chatDropOverlay}
{pageDesktopToolbarRow}
<div className="relative mx-auto flex h-full min-h-0 w-full min-w-0 max-w-[min(720px,100%)] flex-1 flex-col px-3 sm:px-4 md:px-0">
{shell}
Expand Down
31 changes: 18 additions & 13 deletions apps/web/components/chat/input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ interface ChatInputProps {
onRetryAttachment?: (id: string) => void
canSend?: boolean
attachmentAccept?: string
disableFileDropZone?: boolean
}

export default function ChatInput({
Expand All @@ -84,6 +85,7 @@ export default function ChatInput({
onRetryAttachment,
canSend,
attachmentAccept = CHAT_ATTACHMENT_ACCEPT,
disableFileDropZone = false,
}: ChatInputProps) {
const [isMultiline, setIsMultiline] = useState(false)
const [isExpanded, setIsExpanded] = useState(false)
Expand Down Expand Up @@ -200,11 +202,20 @@ export default function ChatInput({
</>
) : null

const dropOverlay = isDraggingFiles ? (
<div className="pointer-events-none absolute inset-1 z-10 grid place-items-center rounded-lg border border-dashed border-[#4B5563] bg-black/70 text-sm font-medium text-fg-primary backdrop-blur-sm">
Drop files to attach
</div>
) : null
const dropOverlay =
!disableFileDropZone && isDraggingFiles ? (
<div className="pointer-events-none absolute inset-1 z-10 grid place-items-center rounded-lg border border-dashed border-[#4B5563] bg-black/70 text-sm font-medium text-fg-primary backdrop-blur-sm">
Drop files to attach
</div>
) : null
const dropZoneProps = disableFileDropZone
? {}
: {
onDragEnter: handleDragEnter,
onDragOver: handleDragOver,
onDragLeave: handleDragLeave,
onDrop: handleDrop,
}

return (
<motion.div
Expand Down Expand Up @@ -328,10 +339,7 @@ export default function ChatInput({
{stackedToolbar ? (
<fieldset
aria-label="Chat input with file drop zone"
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
{...dropZoneProps}
className="relative z-30 m-0 flex min-w-0 flex-col gap-2 rounded-xl border-0 bg-surface-card/60 p-2 shadow-[0_16px_48px_rgba(0,0,0,0.34)] backdrop-blur-md transition-all duration-200 focus-within:ring-1 focus-within:ring-fg-primary/10"
>
{dropOverlay}
Expand Down Expand Up @@ -368,10 +376,7 @@ export default function ChatInput({
) : (
<fieldset
aria-label="Chat input with file drop zone"
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
{...dropZoneProps}
className={cn(
"relative m-0 flex min-w-0 flex-col gap-2 rounded-xl border-0 bg-surface-card/60 p-2 shadow-[0_16px_48px_rgba(0,0,0,0.34)] backdrop-blur-md transition-all duration-200 focus-within:ring-1 focus-within:ring-fg-primary/10",
isMultiline && "flex-col",
Expand Down
20 changes: 13 additions & 7 deletions apps/web/components/integrations-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2425,10 +2425,11 @@ export function IntegrationsView({
}
}

const handleUpgrade = async (planId: "api_pro" | "api_max" = "api_pro") => {
const handleUpgrade = async (planId?: unknown) => {
const checkoutPlanId = planId === "api_max" ? "api_max" : "api_pro"
try {
const result = await autumn.attach({
planId,
planId: checkoutPlanId,
successUrl: `${window.location.origin}/?view=integrations`,
})
if (result?.paymentUrl) {
Expand Down Expand Up @@ -2741,7 +2742,7 @@ export function IntegrationsView({
}
if (claudeCodeConnected) return
if (claudeCodeNeedsPro) {
handleUpgrade()
handleUpgrade("api_pro")
return
}
createPluginKeyMutation.mutate("claude_code")
Expand Down Expand Up @@ -2818,7 +2819,7 @@ export function IntegrationsView({
title="Connect another agent"
onClick={() => {
if (needsProUpgrade) {
handleUpgrade()
handleUpgrade("api_pro")
return
}
trackCard(item)
Expand All @@ -2843,14 +2844,19 @@ export function IntegrationsView({
<FinishSetupButton
onClick={() => {
trackCard(item)
if (!PLUGIN_CATALOG[item.pluginId]?.usesOAuth) {
if (connectingPlugin) return
createPluginKeyMutation.mutate(item.pluginId)
return
}
setFinishSetupPluginId(item.pluginId)
}}
/>
)
}
if (needsProUpgrade) {
return (
<PillButton onClick={() => handleUpgrade()}>
<PillButton onClick={() => handleUpgrade("api_pro")}>
<Zap className="size-3.5 text-[#4BA0FA]" /> Upgrade
</PillButton>
)
Expand Down Expand Up @@ -3020,7 +3026,7 @@ export function IntegrationsView({
<PillButton
onClick={() => {
if (needsProUpgrade) {
handleUpgrade()
handleUpgrade("api_pro")
return
}
trackCard(item)
Expand Down Expand Up @@ -3465,7 +3471,7 @@ export function IntegrationsView({
</div>
<div className="flex shrink-0 items-center justify-between gap-2">
{connectedDialogNeedsPro ? (
<PillButton onClick={() => handleUpgrade()}>
<PillButton onClick={() => handleUpgrade("api_pro")}>
<Zap className="size-3.5 text-[#4BA0FA]" /> Upgrade to connect
more
</PillButton>
Expand Down
Loading