mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-17 02:25:57 +00:00
feat: new tab tips section (#304)
* fix: tips * fix: show tips only 1/5 times * fix: guard against empty tips array in getRandomTip Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: biome exhaustive deps in SurveyChat voice effect Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -89,7 +89,7 @@ export const Chat: FC<Props> = ({
|
||||
})
|
||||
voice.clearTranscript()
|
||||
}
|
||||
}, [voice.transcript, voice.isTranscribing, voice.clearTranscript])
|
||||
}, [voice])
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -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 = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mounted && !isSuggestionsVisible && <NewTabTip />}
|
||||
|
||||
{/* Top sites */}
|
||||
{!isSuggestionsVisible && <TopSites />}
|
||||
|
||||
|
||||
48
apps/agent/entrypoints/newtab/index/NewTabTip.tsx
Normal file
48
apps/agent/entrypoints/newtab/index/NewTabTip.tsx
Normal file
@@ -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 (
|
||||
<AnimatePresence>
|
||||
{visible && tip && (
|
||||
<motion.div
|
||||
className="flex items-center justify-center"
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -4 }}
|
||||
transition={{ duration: 0.4, ease: [0.23, 1, 0.32, 1] }}
|
||||
>
|
||||
<div className="group flex max-w-lg items-center gap-2.5 rounded-lg border border-border/50 px-3.5 py-2">
|
||||
<Lightbulb className="h-3.5 w-3.5 flex-shrink-0 text-[var(--accent-orange)]" />
|
||||
<p className="text-muted-foreground text-xs leading-relaxed">
|
||||
<span className="font-semibold text-[var(--accent-orange)]">
|
||||
Tip:
|
||||
</span>{' '}
|
||||
{tip.text}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDismiss}
|
||||
className="flex-shrink-0 rounded-sm p-0.5 text-muted-foreground/50 opacity-0 transition-all hover:text-muted-foreground group-hover:opacity-100"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
90
apps/agent/entrypoints/newtab/index/tips.ts
Normal file
90
apps/agent/entrypoints/newtab/index/tips.ts
Normal file
@@ -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)]
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user