diff --git a/apps/agent/entrypoints/app/jtbd-agent/SurveyChat.tsx b/apps/agent/entrypoints/app/jtbd-agent/SurveyChat.tsx index bab46fabb..a701d14f7 100644 --- a/apps/agent/entrypoints/app/jtbd-agent/SurveyChat.tsx +++ b/apps/agent/entrypoints/app/jtbd-agent/SurveyChat.tsx @@ -89,7 +89,7 @@ export const Chat: FC = ({ }) voice.clearTranscript() } - }, [voice.transcript, voice.isTranscribing, voice.clearTranscript]) + }, [voice]) const handleSubmit = (e: FormEvent) => { e.preventDefault() diff --git a/apps/agent/entrypoints/newtab/index/NewTab.tsx b/apps/agent/entrypoints/newtab/index/NewTab.tsx index 07aea6bbe..92de1f8ba 100644 --- a/apps/agent/entrypoints/newtab/index/NewTab.tsx +++ b/apps/agent/entrypoints/newtab/index/NewTab.tsx @@ -53,6 +53,7 @@ import { useSuggestions, } from './lib/suggestions/useSuggestions' import { NewTabBranding } from './NewTabBranding' +import { NewTabTip } from './NewTabTip' import { ScheduleResults } from './ScheduleResults' import { SearchSuggestions } from './SearchSuggestions' import { ShortcutsDialog } from './ShortcutsDialog' @@ -597,6 +598,8 @@ export const NewTab = () => { + {mounted && !isSuggestionsVisible && } + {/* Top sites */} {!isSuggestionsVisible && } diff --git a/apps/agent/entrypoints/newtab/index/NewTabTip.tsx b/apps/agent/entrypoints/newtab/index/NewTabTip.tsx new file mode 100644 index 000000000..afaf71cc2 --- /dev/null +++ b/apps/agent/entrypoints/newtab/index/NewTabTip.tsx @@ -0,0 +1,48 @@ +import { Lightbulb, X } from 'lucide-react' +import { AnimatePresence, motion } from 'motion/react' +import { type FC, useMemo, useState } from 'react' +import { NEWTAB_TIP_DISMISSED_EVENT } from '@/lib/constants/analyticsEvents' +import { track } from '@/lib/metrics/track' +import { dismissTip, getRandomTip, shouldShowTip } from './tips' + +export const NewTabTip: FC = () => { + const tip = useMemo(() => getRandomTip(), []) + const [visible, setVisible] = useState(() => tip !== null && shouldShowTip()) + + const handleDismiss = () => { + setVisible(false) + dismissTip() + if (tip) track(NEWTAB_TIP_DISMISSED_EVENT, { tip_id: tip.id }) + } + + return ( + + {visible && tip && ( + +
+ +

+ + Tip: + {' '} + {tip.text} +

+ +
+
+ )} +
+ ) +} diff --git a/apps/agent/entrypoints/newtab/index/tips.ts b/apps/agent/entrypoints/newtab/index/tips.ts new file mode 100644 index 000000000..a7e5f6e37 --- /dev/null +++ b/apps/agent/entrypoints/newtab/index/tips.ts @@ -0,0 +1,90 @@ +export interface Tip { + id: string + text: string + shortcut?: string +} + +export const TIP_SHOW_PROBABILITY = 0.2 + +const TIP_DISMISSED_KEY = 'tip-dismissed-session' + +export const TIPS: Tip[] = [ + { + id: 'chat-any-page', + text: 'Press Option+K to open the AI Chat panel on any webpage — it includes full page context.', + shortcut: '⌥K', + }, + { + id: 'compare-models', + text: 'Press Cmd+Shift+U to open LLM Hub and query multiple AI models side-by-side.', + shortcut: '⌘⇧U', + }, + { + id: 'switch-models', + text: 'Press Option+L to cycle between AI providers without closing the chat panel.', + shortcut: '⌥L', + }, + { + id: 'screenshot-chat', + text: 'Use the Image button in Chat to capture the visible page and ask visual questions about it.', + }, + { + id: 'copy-page-content', + text: 'Click the Copy button in Chat to grab all webpage text and paste it into your prompt.', + }, + { + id: 'cowork-mode', + text: 'Enable Cowork and select a folder to let the agent browse the web AND create files in a single task.', + }, + { + id: 'scheduled-tasks', + text: 'Set up Scheduled Tasks to run the agent on a timer — results appear right here on your New Tab.', + }, + { + id: 'background-tasks', + text: 'Scheduled tasks run in a hidden window so they never interrupt your browsing.', + }, + { + id: 'claude-code-mcp', + text: 'Connect BrowserOS to Claude Code with the MCP integration for full browser control from your terminal.', + }, + { + id: 'mcp-servers', + text: 'Add MCP servers for Google Calendar, Gmail, Notion, and more to build multi-service workflows.', + }, + { + id: 'import-chrome', + text: 'Go to chrome://settings/importData to import bookmarks, passwords, and history from Chrome in one click.', + }, + { + id: 'ad-blocking', + text: 'BrowserOS comes with uBlock Origin pre-enabled — blocking 10x more ads than Chrome out of the box.', + }, + { + id: 'at-mention-tabs', + text: 'Type @ in the search bar to mention and attach open tabs as context for your AI queries.', + }, + { + id: 'workflows', + text: 'For complex repeatable tasks, build visual Workflows instead of one-off prompts for consistent results.', + }, + { + id: 'model-selection', + text: 'Use Gemini Flash for quick questions and Claude Opus for complex multi-step agent tasks.', + }, +] + +export const shouldShowTip = (): boolean => { + const dismissed = sessionStorage.getItem(TIP_DISMISSED_KEY) + if (dismissed) return false + return Math.random() < TIP_SHOW_PROBABILITY +} + +export const dismissTip = () => { + sessionStorage.setItem(TIP_DISMISSED_KEY, Date.now().toString()) +} + +export const getRandomTip = (): Tip | null => { + if (TIPS.length === 0) return null + return TIPS[Math.floor(Math.random() * TIPS.length)] +} diff --git a/apps/agent/lib/constants/analyticsEvents.ts b/apps/agent/lib/constants/analyticsEvents.ts index 9423c53c7..95f87b2ef 100644 --- a/apps/agent/lib/constants/analyticsEvents.ts +++ b/apps/agent/lib/constants/analyticsEvents.ts @@ -95,6 +95,9 @@ export const NEWTAB_TAB_REMOVED_EVENT = 'newtab.tab.removed' /** @public */ export const NEWTAB_APPS_OPENED_EVENT = 'newtab.apps.opened' +/** @public */ +export const NEWTAB_TIP_DISMISSED_EVENT = 'newtab.tip.dismissed' + /** @public */ export const WORKFLOW_DELETED_EVENT = 'settings.workflow.deleted'