Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a03cda0
First pass at separate upsell dialog
brunobergher Sep 8, 2025
865149d
Revert PR #7188 - Restore temperature parameter to fix TabbyApi/ExLla…
daniel-lxs Sep 8, 2025
965dfc0
fix: reduce CodeBlock button z-index to prevent overlap with popovers…
daniel-lxs Sep 9, 2025
8ba47d1
Make ollama models info transport work like lmstudio (#7679)
ItsOnlyBinary Sep 9, 2025
c0c921c
feat: add click-to-edit, ESC-to-cancel, and fix padding consistency f…
roomote[bot] Sep 9, 2025
45f588d
Let people paste in the auth redirect url (#7805)
mrubens Sep 9, 2025
06bdcef
fix: resolve chat message edit/delete duplication issues (#7793)
daniel-lxs Sep 9, 2025
8d8f091
fix: add GIT_EDITOR env var to merge-resolver mode for non-interactiv…
daniel-lxs Sep 9, 2025
5ca5833
UI: Render reasoning as plain italic (match <thinking>) (#7752)
roomote[bot] Sep 9, 2025
150a7b9
Add taskSyncEnabled to userSettingsConfigSchema (#7827)
roomote[bot] Sep 9, 2025
d0b4b15
Release: v1.75.0 (#7829)
jr Sep 9, 2025
7322f51
fix: prevent negative cost values and improve label visibility in eva…
roomote[bot] Sep 9, 2025
62271c1
Fix Groq context window display (#7839)
mrubens Sep 10, 2025
740924a
feat: add DismissibleUpsell component for dismissible messages
roomote-agent Sep 10, 2025
a48e46f
fix: Apply PR feedback for DismissibleUpsell component
roomote-agent Sep 10, 2025
a993979
New Cloud upsell dialog in task share and cloud view, shared component
brunobergher Sep 10, 2025
50cc8f0
Properly working DismissibleUpsell
brunobergher Sep 10, 2025
cdcb768
Working upsell for long-running tasks
brunobergher Sep 10, 2025
6413a27
CTA in AutoApproveMenu
brunobergher Sep 10, 2025
effdff0
Home page CTA
brunobergher Sep 10, 2025
3b68f71
Fixes the autoapprove upsell and some tests
brunobergher Sep 10, 2025
a9f21c4
Visual and copy fixes
brunobergher Sep 10, 2025
937f8ab
Test fix
brunobergher Sep 10, 2025
ee1aba4
Translations
brunobergher Sep 10, 2025
653166a
Stray className attribute
brunobergher Sep 10, 2025
010a812
Merge conflicts
brunobergher Sep 10, 2025
67124c9
Cloud view fixes in a left-aligned layout
brunobergher Sep 10, 2025
64e0695
Removes unnecessary test
brunobergher Sep 10, 2025
1fa8129
Less flaky tests
brunobergher Sep 10, 2025
5c8085c
Fixes sharebutton behavior and updates associated tests
brunobergher Sep 10, 2025
dced5a4
Update webview-ui/src/i18n/locales/it/cloud.json
brunobergher Sep 10, 2025
1a8860e
Fix dismissed flicker
jr Sep 10, 2025
2848b26
Fix long task upsell
jr Sep 10, 2025
9610626
Resolve merge conflicts in localization files
jr Sep 10, 2025
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
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const globalSettingsSchema = z.object({
lastShownAnnouncementId: z.string().optional(),
customInstructions: z.string().optional(),
taskHistory: z.array(historyItemSchema).optional(),
dismissedUpsells: z.array(z.string()).optional(),

// Image generation settings (experimental) - flattened for simplicity
openRouterImageApiKey: z.string().optional(),
Expand Down
34 changes: 34 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3004,5 +3004,39 @@ export const webviewMessageHandler = async (

break
}
case "dismissUpsell": {
if (message.upsellId) {
try {
// Get current list of dismissed upsells
const dismissedUpsells = getGlobalState("dismissedUpsells") || []

// Add the new upsell ID if not already present
let updatedList = dismissedUpsells
if (!dismissedUpsells.includes(message.upsellId)) {
updatedList = [...dismissedUpsells, message.upsellId]
await updateGlobalState("dismissedUpsells", updatedList)
}

// Send updated list back to webview (use the already computed updatedList)
await provider.postMessageToWebview({
type: "dismissedUpsells",
list: updatedList,
})
} catch (error) {
// Fail silently as per Bruno's comment - it's OK to fail silently in this case
provider.log(`Failed to dismiss upsell: ${error instanceof Error ? error.message : String(error)}`)
}
}
break
}
case "getDismissedUpsells": {
// Send the current list of dismissed upsells to the webview
const dismissedUpsells = getGlobalState("dismissedUpsells") || []
await provider.postMessageToWebview({
type: "dismissedUpsells",
list: dismissedUpsells,
})
break
}
}
}
3 changes: 3 additions & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export interface ExtensionMessage {
| "showEditMessageDialog"
| "commands"
| "insertTextIntoTextarea"
| "dismissedUpsells"
text?: string
payload?: any // Add a generic payload for now, can refine later
action?:
Expand Down Expand Up @@ -199,6 +200,7 @@ export interface ExtensionMessage {
context?: string
commands?: Command[]
queuedMessages?: QueuedMessage[]
list?: string[] // For dismissedUpsells
}

export type ExtensionState = Pick<
Expand All @@ -209,6 +211,7 @@ export type ExtensionState = Pick<
// | "lastShownAnnouncementId"
| "customInstructions"
// | "taskHistory" // Optional in GlobalSettings, required here.
| "dismissedUpsells"
| "autoApprovalEnabled"
| "alwaysAllowReadOnly"
| "alwaysAllowReadOnlyOutsideWorkspace"
Expand Down
4 changes: 4 additions & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ export interface WebviewMessage {
| "queueMessage"
| "removeQueuedMessage"
| "editQueuedMessage"
| "dismissUpsell"
| "getDismissedUpsells"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
Expand Down Expand Up @@ -268,6 +270,8 @@ export interface WebviewMessage {
visibility?: ShareVisibility // For share visibility
hasContent?: boolean // For checkRulesDirectoryResult
checkOnly?: boolean // For deleteCustomMode check
upsellId?: string // For dismissUpsell
list?: string[] // For dismissedUpsells response
codeIndexSettings?: {
// Global state settings
codebaseIndexEnabled: boolean
Expand Down
26 changes: 26 additions & 0 deletions webview-ui/src/components/chat/AutoApproveMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { AutoApproveToggle, AutoApproveSetting, autoApproveSettingsConfig } from
import { StandardTooltip } from "@src/components/ui"
import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"
import DismissibleUpsell from "@src/components/common/DismissibleUpsell"
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"

interface AutoApproveMenuProps {
style?: React.CSSProperties
Expand All @@ -35,7 +38,12 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {

const { t } = useAppTranslation()

const { isOpen, openUpsell, closeUpsell, handleConnect } = useCloudUpsell({
autoOpenOnAuth: false,
})

const baseToggles = useAutoApprovalToggles()
const enabledCount = useMemo(() => Object.values(baseToggles).filter(Boolean).length, [baseToggles])

// AutoApproveMenu needs alwaysApproveResubmit in addition to the base toggles
const toggles = useMemo(
Expand Down Expand Up @@ -173,6 +181,23 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
</div>

<AutoApproveToggle {...toggles} onToggle={onAutoApproveToggle} />

{enabledCount > 7 && (
<>
<DismissibleUpsell
upsellId="autoApprovePowerUserA"
onClick={() => openUpsell()}
dismissOnClick={false}
variant="banner">
<Trans
i18nKey="cloud:upsell.autoApprovePowerUser"
components={{
learnMoreLink: <VSCodeLink href="#" />,
}}
/>
</DismissibleUpsell>
</>
)}
</div>
)}

Expand Down Expand Up @@ -240,6 +265,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
/>
</div>
</div>
<CloudUpsellDialog open={isOpen} onOpenChange={closeUpsell} onConnect={handleConnect} />
</div>
)
}
Expand Down
39 changes: 35 additions & 4 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { useDeepCompareEffect, useEvent, useMount } from "react-use"
import debounce from "debounce"
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
import removeMd from "remove-markdown"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import useSound from "use-sound"
import { LRUCache } from "lru-cache"
import { useTranslation } from "react-i18next"
import { Trans, useTranslation } from "react-i18next"

import { useDebounceEffect } from "@src/utils/useDebounceEffect"
import { appendImages } from "@src/utils/imageUtils"
Expand Down Expand Up @@ -37,10 +37,10 @@ import { useExtensionState } from "@src/context/ExtensionStateContext"
import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
import RooHero from "@src/components/welcome/RooHero"
import RooTips from "@src/components/welcome/RooTips"
import RooCloudCTA from "@src/components/welcome/RooCloudCTA"
import { StandardTooltip } from "@src/components/ui"
import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"
import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"

import TelemetryBanner from "../common/TelemetryBanner"
import VersionIndicator from "../common/VersionIndicator"
Expand All @@ -56,6 +56,9 @@ import SystemPromptWarning from "./SystemPromptWarning"
import ProfileViolationWarning from "./ProfileViolationWarning"
import { CheckpointWarning } from "./CheckpointWarning"
import { QueuedMessages } from "./QueuedMessages"
import DismissibleUpsell from "../common/DismissibleUpsell"
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
import { Cloud } from "lucide-react"

export interface ChatViewProps {
isHidden: boolean
Expand Down Expand Up @@ -208,6 +211,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
clineAskRef.current = clineAsk
}, [clineAsk])

const {
isOpen: isUpsellOpen,
openUpsell,
closeUpsell,
handleConnect,
} = useCloudUpsell({
autoOpenOnAuth: false,
})

// Keep inputValueRef in sync with inputValue state
useEffect(() => {
inputValueRef.current = inputValue
Expand Down Expand Up @@ -1831,7 +1843,25 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
{telemetrySetting === "unset" && <TelemetryBanner />}

<div className="mb-2.5">
{cloudIsAuthenticated || taskHistory.length < 4 ? <RooTips /> : <RooCloudCTA />}
{cloudIsAuthenticated || taskHistory.length < 4 ? (
<RooTips />
) : (
<>
<DismissibleUpsell
upsellId="taskList"
icon={<Cloud className="size-4 mt-0.5 shrink-0" />}
onClick={() => openUpsell()}
dismissOnClick={false}
className="bg-vscode-editor-background p-4 !text-base">
<Trans
i18nKey="cloud:upsell.taskList"
components={{
learnMoreLink: <VSCodeLink href="#" />,
}}
/>
</DismissibleUpsell>
</>
)}
</div>
{/* Show the task history preview if expanded and tasks exist */}
{taskHistory.length > 0 && isExpanded && <HistoryPreview />}
Expand Down Expand Up @@ -2013,6 +2043,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
)}

<div id="roo-portal" />
<CloudUpsellDialog open={isUpsellOpen} onOpenChange={closeUpsell} onConnect={handleConnect} />
</div>
)
}
Expand Down
99 changes: 31 additions & 68 deletions webview-ui/src/components/chat/ShareButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from "react"
import { useState, useEffect } from "react"
import { useTranslation } from "react-i18next"
import { SquareArrowOutUpRightIcon } from "lucide-react"

Expand All @@ -7,6 +7,8 @@ import { type HistoryItem, type ShareVisibility, TelemetryEventName } from "@roo
import { vscode } from "@/utils/vscode"
import { telemetryClient } from "@/utils/TelemetryClient"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useCloudUpsell } from "@/hooks/useCloudUpsell"
import { CloudUpsellDialog } from "@/components/cloud/CloudUpsellDialog"
import {
Button,
Popover,
Expand All @@ -16,10 +18,6 @@ import {
CommandList,
CommandItem,
CommandGroup,
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
StandardTooltip,
} from "@/components/ui"

Expand All @@ -31,29 +29,34 @@ interface ShareButtonProps {

export const ShareButton = ({ item, disabled = false, showLabel = false }: ShareButtonProps) => {
const [shareDropdownOpen, setShareDropdownOpen] = useState(false)
const [connectModalOpen, setConnectModalOpen] = useState(false)
const [shareSuccess, setShareSuccess] = useState<{ visibility: ShareVisibility; url: string } | null>(null)
const [wasConnectInitiatedFromShare, setWasConnectInitiatedFromShare] = useState(false)
const { t } = useTranslation()
const { sharingEnabled, cloudIsAuthenticated, cloudUserInfo } = useExtensionState()
const wasUnauthenticatedRef = useRef(false)
const initiatedAuthFromThisButtonRef = useRef(false)
const { cloudUserInfo } = useExtensionState()

// Use enhanced cloud upsell hook with auto-open on auth success
const {
isOpen: connectModalOpen,
openUpsell,
closeUpsell,
handleConnect,
isAuthenticated: cloudIsAuthenticated,
sharingEnabled,
} = useCloudUpsell({
onAuthSuccess: () => {
// Auto-open share dropdown after successful authentication
setShareDropdownOpen(true)
setWasConnectInitiatedFromShare(false)
},
})

// Track authentication state changes to auto-open popover after login
// Auto-open popover when user becomes authenticated after clicking Connect from share button
useEffect(() => {
if (!cloudIsAuthenticated || !sharingEnabled) {
wasUnauthenticatedRef.current = true
} else if (wasUnauthenticatedRef.current && cloudIsAuthenticated && sharingEnabled) {
// Only open dropdown if auth was initiated from this button
if (initiatedAuthFromThisButtonRef.current) {
// User just authenticated from this share button, send telemetry, close modal, and open the popover
telemetryClient.capture(TelemetryEventName.ACCOUNT_CONNECT_SUCCESS)
setConnectModalOpen(false)
setShareDropdownOpen(true)
initiatedAuthFromThisButtonRef.current = false // Reset the flag
}
wasUnauthenticatedRef.current = false
if (wasConnectInitiatedFromShare && cloudIsAuthenticated) {
setShareDropdownOpen(true)
setWasConnectInitiatedFromShare(false)
}
}, [cloudIsAuthenticated, sharingEnabled])
}, [wasConnectInitiatedFromShare, cloudIsAuthenticated])

// Listen for share success messages from the extension
useEffect(() => {
Expand Down Expand Up @@ -95,14 +98,9 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share
}

const handleConnectToCloud = () => {
// Send telemetry for connect to cloud action
telemetryClient.capture(TelemetryEventName.SHARE_CONNECT_TO_CLOUD_CLICKED)

// Mark that authentication was initiated from this button
initiatedAuthFromThisButtonRef.current = true
vscode.postMessage({ type: "rooCloudSignIn" })
setWasConnectInitiatedFromShare(true)
handleConnect()
setShareDropdownOpen(false)
setConnectModalOpen(false)
}

const handleShareButtonClick = () => {
Expand All @@ -111,7 +109,8 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share

if (!cloudIsAuthenticated) {
// Show modal for unauthenticated users
setConnectModalOpen(true)
openUpsell()
telemetryClient.capture(TelemetryEventName.SHARE_CONNECT_TO_CLOUD_CLICKED)
} else {
// Show popover for authenticated users
setShareDropdownOpen(true)
Expand Down Expand Up @@ -241,43 +240,7 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share
)}

{/* Connect to Cloud Modal */}
<Dialog open={connectModalOpen} onOpenChange={setConnectModalOpen}>
<DialogContent className="max-w-sm">
<DialogHeader className="text-center">
<DialogTitle className="text-lg font-medium text-vscode-foreground">
{t("cloud:cloudBenefitsTitle")}
</DialogTitle>
</DialogHeader>

<div className="flex flex-col space-y-6">
<div>
<p className="text-md text-vscode-descriptionForeground mb-4">
{t("cloud:cloudBenefitsSubtitle")}
</p>
<ul className="text-sm text-vscode-descriptionForeground space-y-2">
<li className="flex items-start">
<span className="mr-2 text-vscode-foreground">•</span>
{t("cloud:cloudBenefitSharing")}
</li>
<li className="flex items-start">
<span className="mr-2 text-vscode-foreground">•</span>
{t("cloud:cloudBenefitHistory")}
</li>
<li className="flex items-start">
<span className="mr-2 text-vscode-foreground">•</span>
{t("cloud:cloudBenefitMetrics")}
</li>
</ul>
</div>

<div className="flex flex-col gap-4">
<Button onClick={handleConnectToCloud} className="w-full">
{t("cloud:connect")}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
<CloudUpsellDialog open={connectModalOpen} onOpenChange={closeUpsell} onConnect={handleConnectToCloud} />
</>
)
}
Loading