From 8a92fa24b6bdcdafa70bdfd07a4d78f3d6671efd Mon Sep 17 00:00:00 2001 From: Felarof Date: Fri, 19 Sep 2025 13:29:17 -0700 Subject: [PATCH] Upvote BrowserOS launch Upvote BrowserOS launch bak new agent prompt confetti bak -- confetti in new agent bak bak bak bak bak --- src/lib/agent/BrowserAgent.prompt.ts | 12 ++++ src/lib/agent/BrowserAgent.ts | 48 ++++++++++---- src/lib/agent/NewAgent.prompt.ts | 11 ++++ src/lib/agent/NewAgent.ts | 30 +++++++-- src/lib/tools/NewTools.ts | 50 +++++++++++++++ src/lib/tools/utils/CelebrationTool.ts | 49 ++++++++++++++ src/lib/utils/confetti.ts | 70 ++++++++++++++++++++ src/sidepanel/components/MessageList.tsx | 82 +++++++++++++++++------- 8 files changed, 312 insertions(+), 40 deletions(-) create mode 100644 src/lib/tools/utils/CelebrationTool.ts create mode 100644 src/lib/utils/confetti.ts diff --git a/src/lib/agent/BrowserAgent.prompt.ts b/src/lib/agent/BrowserAgent.prompt.ts index 418252c3..292bbf20 100644 --- a/src/lib/agent/BrowserAgent.prompt.ts +++ b/src/lib/agent/BrowserAgent.prompt.ts @@ -17,6 +17,18 @@ export function generateSystemPrompt(toolDescriptions: string): string { 4. **NEVER** skip waiting for content to load 5. **NEVER** make assumptions without checking +## 📌 SPECIAL EXAMPLE TASKS +NOTE: When user says something like "Visit BrowserOS launch and upvote ❤️", execute these steps: +1. Navigate to https://dub.sh/browseros-launch +2. Click the upvote button +3. Use celebration_tool to show confetti + +NOTE: When user says something like "Go to Github and Star BrowserOS ⭐", execute these steps: +1. Navigate to https://git.new/browserOS +2. Click the star button (only if it is looking gray -- i.e., not starred) +3. Use celebration_tool to show confetti (only if star was clicked) + + ## 🔄 EXECUTION WORKFLOW ### UNDERSTANDING YOUR TASK TYPE The system automatically classifies tasks before you see them: diff --git a/src/lib/agent/BrowserAgent.ts b/src/lib/agent/BrowserAgent.ts index 3ad821ce..fa1e3497 100644 --- a/src/lib/agent/BrowserAgent.ts +++ b/src/lib/agent/BrowserAgent.ts @@ -65,6 +65,7 @@ import { createStorageTool } from '@/lib/tools/utils/StorageTool'; import { createExtractTool } from '@/lib/tools/extraction/ExtractTool'; import { createResultTool } from '@/lib/tools/result/ResultTool'; import { createHumanInputTool } from '@/lib/tools/utils/HumanInputTool'; +import { createCelebrationTool } from '@/lib/tools/utils/CelebrationTool'; import { createDateTool } from '@/lib/tools/utility/DateTool'; import { createMCPTool } from '@/lib/tools/mcp/MCPTool'; import { generateSystemPrompt, generateSingleTurnExecutionPrompt } from './BrowserAgent.prompt'; @@ -171,6 +172,23 @@ export class BrowserAgent { this.narrator?.cleanup(); } + /** + * Transform special example tasks into explicit instructions + * @param task - The original task string + * @returns The transformed task string + */ + private _transformSpecialTasks(task: string): string { + // Exact match for special example tasks + if (task === "Visit BrowserOS launch and upvote ❤️") { + return "Navigate to https://dub.sh/browseros-launch (it will redirect to the actual page) then click the upvote button then use celebration_tool to show confetti"; + } + if (task === "Go to GitHub and Star BrowserOS ⭐") { + return "Navigate to https://git.new/browserOS (it will redirect to the actual page) then click the star button if it is gray (not starred) then use celebration_tool to show confetti"; + } + // Return original task if not a special case + return task; + } + /** * Main entry point. * Orchestrates classification and delegates to the appropriate execution strategy. @@ -178,15 +196,18 @@ export class BrowserAgent { * @param metadata - Optional execution metadata for controlling execution mode */ async execute(task: string, metadata?: ExecutionMetadata): Promise { + // Transform special example tasks into explicit instructions + const transformedTask = this._transformSpecialTasks(task); + try { // 1. SETUP: Initialize system prompt and user task - this._initializeExecution(task); + this._initializeExecution(transformedTask); // 2. CHECK FOR PREDEFINED PLAN if (metadata?.executionMode === 'predefined' && metadata.predefinedPlan) { // Treat predefined plan as a fresh (non-follow-up) task: clear history and re-init this.messageManager.clear(); - this._initializeExecution(task); + this._initializeExecution(transformedTask); // Route predefined plan through the multi-step strategy using initial plan const predefined = metadata!.predefinedPlan!; this.pubsub.publishMessage(PubSub.createMessage(`Executing agent: ${predefined.name || 'Custom Agent'}`, 'thinking')); @@ -195,19 +216,19 @@ export class BrowserAgent { steps: predefined.steps.map(step => ({ action: step, reasoning: `Part of agent: ${predefined.name || 'Custom'}` })) }; if (predefined.goal) { - this.messageManager.addHuman(`User's goal is: ${predefined.goal} and this is the task: ${task}`); + this.messageManager.addHuman(`User's goal is: ${predefined.goal} and this is the task: ${transformedTask}`); } - await this._executeMultiStepStrategy(task, initialPlan); - await this._generateTaskResult(task); + await this._executeMultiStepStrategy(transformedTask, initialPlan); + await this._generateTaskResult(transformedTask); return; } else if (metadata?.executionMode === 'dynamic' && metadata?.source === 'newtab') { // For tasks initiated from new tab, show the startup message with task - this.pubsub.publishMessage(PubSub.createMessage(`Executing task: ${task}`, 'thinking')); + this.pubsub.publishMessage(PubSub.createMessage(`Executing task: ${transformedTask}`, 'thinking')); } // 3. STANDARD FLOW: CLASSIFY task type - const classification = await this._classifyTask(task); + const classification = await this._classifyTask(transformedTask); // Log classification result to console for visibility if (ENABLE_EVALS2) { @@ -217,9 +238,9 @@ export class BrowserAgent { // Clear message history if this is not a follow-up task if (!classification.is_followup_task) { this.messageManager.clear(); - this._initializeExecution(task); + this._initializeExecution(transformedTask); } - + let message: string; if (classification.is_followup_task && this.messageManager.getMessages().length > 0) { message = 'Following up on previous task...'; @@ -232,17 +253,17 @@ export class BrowserAgent { // 4. DELEGATE: Route to the correct execution strategy if (classification.is_simple_task) { - await this._executeSimpleTaskStrategy(task); + await this._executeSimpleTaskStrategy(transformedTask); } else { - await this._executeMultiStepStrategy(task); + await this._executeMultiStepStrategy(transformedTask); } // 5. FINALISE: Generate final result - await this._generateTaskResult(task); + await this._generateTaskResult(transformedTask); // Task completion is logged by NxtScape, not here } catch (error) { - this._handleExecutionError(error, task); + this._handleExecutionError(error, transformedTask); } finally { // Cleanup narrator service this.narrator?.cleanup(); @@ -302,6 +323,7 @@ export class BrowserAgent { this.toolManager.register(createStorageTool(this.executionContext)); this.toolManager.register(createExtractTool(this.executionContext)); this.toolManager.register(createHumanInputTool(this.executionContext)); + this.toolManager.register(createCelebrationTool(this.executionContext)); this.toolManager.register(createDateTool(this.executionContext)); // Result tool diff --git a/src/lib/agent/NewAgent.prompt.ts b/src/lib/agent/NewAgent.prompt.ts index b3e73422..d5828d53 100644 --- a/src/lib/agent/NewAgent.prompt.ts +++ b/src/lib/agent/NewAgent.prompt.ts @@ -36,6 +36,17 @@ CRITICAL OUTPUT RULES - NEVER VIOLATE THESE: 4. **NEVER** mention system reminders in your output 5. Browser state and system reminders are INTERNAL ONLY - treat them as invisible to the user +## 📌 SPECIAL EXAMPLE TASKS +NOTE: When user says something like "Visit BrowserOS launch and upvote ❤️", execute these steps: +1. Navigate to https://dub.sh/browseros-launch +2. Click the upvote button +3. Use celebration_tool to show confetti + +NOTE: When user says something like "Go to Github and Star BrowserOS ⭐", execute these steps: +1. Navigate to https://git.new/browserOS +2. Click the star button (only if it is looking gray -- i.e., not starred) +3. Use celebration_tool to show confetti (only if star was clicked) + The browser state appears in tags for your internal reference to understand the page. System reminders appear in tags for your internal guidance. diff --git a/src/lib/agent/NewAgent.ts b/src/lib/agent/NewAgent.ts index c774de3c..52e825cc 100644 --- a/src/lib/agent/NewAgent.ts +++ b/src/lib/agent/NewAgent.ts @@ -46,6 +46,7 @@ import { createExtractTool, createHumanInputTool, createDoneTool, + createCelebrationTool, createMoondreamVisualClickTool, createMoondreamVisualTypeTool, } from "@/lib/tools/NewTools"; @@ -258,8 +259,9 @@ export class NewAgent { // Utility tools this.toolManager.register(createExtractTool(this.executionContext)); this.toolManager.register(createHumanInputTool(this.executionContext)); + this.toolManager.register(createCelebrationTool(this.executionContext)); // Celebration/confetti tool this.toolManager.register(createDateTool(this.executionContext)); // Date/time utilities - + // External integration tools this.toolManager.register(createMCPTool(this.executionContext)); // MCP server integration @@ -273,10 +275,30 @@ export class NewAgent { ); } + /** + * Transform special example tasks into explicit instructions + * @param task - The original task string + * @returns The transformed task string + */ + private _transformSpecialTasks(task: string): string { + // Exact match for special example tasks + if (task === "Visit BrowserOS launch and upvote ❤️") { + return "Navigate to https://dub.sh/browseros-launch (it will redirect to the actual page) then click the upvote button then use celebration_tool to show confetti"; + } + if (task === "Go to GitHub and Star BrowserOS ⭐") { + return "Navigate to https://git.new/browserOS (it will redirect to the actual page) then click the star button if it is gray (not starred) then use celebration_tool to show confetti"; + } + // Return original task if not a special case + return task; + } + // There are basically two modes of operation: // 1. Dynamic planning - the agent plans and executes in a loop until done // 2. Predefined plan - the agent executes a predefined set of steps in a loop until all are done async execute(task: string, metadata?: ExecutionMetadata): Promise { + // Transform special example tasks into explicit instructions + const transformedTask = this._transformSpecialTasks(task); + try { this.executionContext.setExecutionMetrics({ ...this.executionContext.getExecutionMetrics(), @@ -285,12 +307,12 @@ export class NewAgent { Logging.log("NewAgent", `Starting execution`, "info"); await this._initialize(); - + // Check for predefined plan if (metadata?.executionMode === 'predefined' && metadata.predefinedPlan) { - await this._executePredefined(task, metadata.predefinedPlan); + await this._executePredefined(transformedTask, metadata.predefinedPlan); } else { - await this._executeDynamic(task); + await this._executeDynamic(transformedTask); } } catch (error) { this._handleExecutionError(error); diff --git a/src/lib/tools/NewTools.ts b/src/lib/tools/NewTools.ts index f964c73f..c307e234 100644 --- a/src/lib/tools/NewTools.ts +++ b/src/lib/tools/NewTools.ts @@ -13,6 +13,7 @@ import { PubSubChannel } from "@/lib/pubsub/PubSubChannel"; import { Logging } from "@/lib/utils/Logging"; import { invokeWithRetry } from "@/lib/utils/retryable"; import { HumanMessage, SystemMessage } from "@langchain/core/messages"; +import { CONFETTI_SCRIPT } from "@/lib/utils/confetti"; // Tool result schema - EXACT from NewAgent const ToolResultSchema = z.object({ @@ -30,6 +31,7 @@ export function createNewTools( context: ExecutionContext, ): DynamicStructuredTool[] { return [ + createClickTool(context), createTypeTool(context), createClearTool(context), createScrollTool(context), @@ -45,6 +47,7 @@ export function createNewTools( createExtractTool(context), createHumanInputTool(context), createDoneTool(context), + createCelebrationTool(context), createMoondreamVisualClickTool(context), createMoondreamVisualTypeTool(context), createClickAtCoordinatesTool(context), @@ -944,6 +947,53 @@ export function createDoneTool( }); } +// Celebration Tool +export function createCelebrationTool( + context: ExecutionContext, +): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: "celebration", + description: "Shows a confetti celebration animation on the current page. Use this to celebrate successful actions like upvoting or starring.", + schema: z.object({}), // No parameters needed + func: async () => { + try { + context.incrementMetric("toolCalls"); + + // Emit thinking message + context.getPubSub().publishMessage( + PubSubChannel.createMessage("🎉 Celebrating...", "thinking") + ); + + // Get current page from browserContext + const page = await context.browserContext.getCurrentPage(); + if (!page) { + return JSON.stringify({ + ok: false, + error: "No active page to show celebration" + }); + } + + // Use shared confetti script + + // Execute confetti script + await page.executeJavaScript(CONFETTI_SCRIPT); + + return JSON.stringify({ + ok: true, + output: "Confetti celebration shown!" + }); + + } catch (error) { + context.incrementMetric("errors"); + return JSON.stringify({ + ok: false, + error: `Failed to show celebration: ${error instanceof Error ? error.message : String(error)}` + }); + } + } + }); +} + // Moondream Visual Click Tool const MoondreamVisualClickInputSchema = z.object({ instruction: z diff --git a/src/lib/tools/utils/CelebrationTool.ts b/src/lib/tools/utils/CelebrationTool.ts new file mode 100644 index 00000000..583961b3 --- /dev/null +++ b/src/lib/tools/utils/CelebrationTool.ts @@ -0,0 +1,49 @@ +import { z } from "zod"; +import { DynamicStructuredTool } from "@langchain/core/tools"; +import { ExecutionContext } from "@/lib/runtime/ExecutionContext"; +import { Logging } from "@/lib/utils/Logging"; +import { CONFETTI_SCRIPT } from "@/lib/utils/confetti"; + +/** + * Creates a celebration tool for showing confetti animation + * @param context - The execution context + * @returns A DynamicStructuredTool for celebrations + */ +export function createCelebrationTool(context: ExecutionContext): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: "celebration_tool", + description: "Shows a confetti celebration animation on the current page. Use this to celebrate successful actions like upvoting or starring.", + schema: z.object({}), // No parameters needed + func: async () => { + try { + Logging.log("CelebrationTool", "Showing confetti celebration"); + + // Get current page + const page = await context.browserContext.getCurrentPage(); + if (!page) { + return JSON.stringify({ + ok: false, + output: "No active page to show celebration" + }); + } + + // Execute confetti script + await page.executeJavaScript(CONFETTI_SCRIPT); + + return JSON.stringify({ + ok: true, + output: "Confetti celebration shown!" + }); + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + Logging.log("CelebrationTool", `Failed to show celebration: ${errorMessage}`, "error"); + + return JSON.stringify({ + ok: false, + output: `Failed to show celebration: ${errorMessage}` + }); + } + } + }); +} \ No newline at end of file diff --git a/src/lib/utils/confetti.ts b/src/lib/utils/confetti.ts new file mode 100644 index 00000000..14429318 --- /dev/null +++ b/src/lib/utils/confetti.ts @@ -0,0 +1,70 @@ +/** + * Shared confetti animation script used across the application + */ + +// Confetti animation script - pure CSS/JS, no dependencies +export const CONFETTI_SCRIPT = ` +(() => { + // Color palette for confetti + const colors = ['#f44336','#e91e63','#9c27b0','#3f51b5','#2196f3','#00bcd4','#4caf50','#ffeb3b','#ff9800']; + const confettiCount = 400; + + // Create container for all confetti + const container = document.createElement('div'); + container.id = 'browseros-confetti-container'; + container.style.cssText = 'position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:999999;overflow:hidden'; + + // Create individual confetti pieces + for(let i = 0; i < confettiCount; i++) { + const confetto = document.createElement('div'); + const color = colors[Math.floor(Math.random() * colors.length)]; + const left = Math.random() * 100; + const animationDelay = Math.random() * 2; + const animationDuration = 3 + Math.random() * 2; + const size = 20 + Math.random() * 40; + + confetto.style.cssText = \` + position:absolute; + width:\${size}px; + height:\${size}px; + background:\${color}; + left:\${left}%; + top:-20px; + opacity:1; + transform:rotate(\${Math.random() * 360}deg); + animation:confetti-fall \${animationDuration}s linear \${animationDelay}s forwards; + border-radius:\${Math.random() > 0.5 ? '50%' : '0'}; + \`; + container.appendChild(confetto); + } + + // Add animation keyframes + const style = document.createElement('style'); + style.id = 'browseros-confetti-styles'; + style.textContent = \` + @keyframes confetti-fall { + 0% { + transform: translateY(0) rotate(0deg) scale(1); + opacity: 1; + } + 100% { + transform: translateY(calc(100vh + 20px)) rotate(720deg) scale(0); + opacity: 0; + } + } + \`; + document.head.appendChild(style); + document.body.appendChild(container); + + // Cleanup after animation completes + setTimeout(() => { + const containerEl = document.getElementById('browseros-confetti-container'); + const styleEl = document.getElementById('browseros-confetti-styles'); + if (containerEl) containerEl.remove(); + if (styleEl) styleEl.remove(); + }, 7000); + + // Return success + return true; +})(); +`; \ No newline at end of file diff --git a/src/sidepanel/components/MessageList.tsx b/src/sidepanel/components/MessageList.tsx index 6bd69058..9e96bac3 100644 --- a/src/sidepanel/components/MessageList.tsx +++ b/src/sidepanel/components/MessageList.tsx @@ -16,6 +16,11 @@ import { useAnalytics } from '../hooks/useAnalytics' import { cn } from '@/sidepanel/lib/utils' import { groupMessages } from '../utils/messageGrouping' import type { Message } from '../stores/chatStore' +import { useSidePanelPortMessaging } from '@/sidepanel/hooks' +import { MessageType } from '@/lib/types/messaging' +import { useChatStore } from '@/sidepanel/stores/chatStore' +import { useSettingsStore } from '@/sidepanel/stores/settingsStore' +import { useTabsStore } from '@/sidepanel/stores/tabsStore' interface MessageListProps { messages: Message[] @@ -27,10 +32,11 @@ interface MessageListProps { // Example prompts - showcasing BrowserOS capabilities const EXAMPLES = [ - "Open amazon.com and order Sensodyne toothpaste", - "Find top-rated headphones under $200", - "Go to GitHub and Star BrowserOS", - "Turn this article into a LinkedIn post", + "Visit BrowserOS launch and upvote ❤️", + // "Find top-rated headphones under $200", + "Go to GitHub and Star BrowserOS ⭐", + // "Turn this article into a LinkedIn post", + "Open amazon.com and order Sensodyne toothpaste 🪥", ] // Animation constants @@ -43,6 +49,10 @@ const DEFAULT_DISPLAY_COUNT = 4 // Fixed number of examples to show export function MessageList({ messages, isProcessing = false, onScrollStateChange, scrollToBottom: externalScrollToBottom, containerRef: externalContainerRef }: MessageListProps) { const { containerRef: internalContainerRef, isUserScrolling, scrollToBottom } = useAutoScroll([messages], externalContainerRef) const { trackFeature } = useAnalytics() + const { sendMessage } = useSidePanelPortMessaging() + const { upsertMessage, setProcessing } = useChatStore() + const { chatMode, setChatMode } = useSettingsStore() + const { getContextTabs, clearSelectedTabs } = useTabsStore() const [, setIsAtBottom] = useState(true) const [currentExamples] = useState(EXAMPLES) const [isAnimating] = useState(false) @@ -189,10 +199,30 @@ export function MessageList({ messages, isProcessing = false, onScrollStateChang } const handleExampleClick = (prompt: string) => { + // Prevent any event propagation that might interfere trackFeature('example_prompt', { prompt }) - // Create a custom event to set input value - const event = new CustomEvent('setInputValue', { detail: prompt }) - window.dispatchEvent(event) + + // Switch to Agent Mode for example runs + try { setChatMode(false) } catch { /* no-op */ } + + // Mirror ChatInput.submitTask behavior + const msgId = `user_${Date.now()}` + upsertMessage({ msgId, role: 'user', content: prompt, ts: Date.now() }) + setProcessing(true) + + // Collect selected context tabs (same behavior as ChatInput) + const contextTabs = getContextTabs() + const tabIds = contextTabs.length > 0 ? contextTabs.map(tab => tab.id) : undefined + + sendMessage(MessageType.EXECUTE_QUERY, { + query: prompt.trim(), + tabIds, + source: 'sidepanel', + chatMode: false + }) + + // Clear selected tabs after sending (mirror ChatInput) + try { clearSelectedTabs() } catch { /* no-op */ } } // Landing View @@ -203,22 +233,23 @@ export function MessageList({ messages, isProcessing = false, onScrollStateChang role="region" aria-label="Welcome screen with example prompts" > - {/* Main content - vertically centered */} -
- {/* Main tagline with logo */} -
-

- Your Agentic - web assistant{' '} - BrowserOS - -

-
+ {/* Fixed tagline near the top */} +
+

+ Your Agentic + + web assistant{' '} + BrowserOS + +

+
+ {/* Main content - vertically centered (Examples remain centered) */} +
{/* Example Prompts */}

@@ -240,9 +271,14 @@ export function MessageList({ messages, isProcessing = false, onScrollStateChang }`} >