mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-18 11:06:19 +00:00
Upvote BrowserOS launch
Upvote BrowserOS launch bak new agent prompt confetti bak -- confetti in new agent bak bak bak bak bak
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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<void> {
|
||||
// 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
|
||||
|
||||
@@ -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 <browser-state> tags for your internal reference to understand the page.
|
||||
System reminders appear in <system-reminder> tags for your internal guidance.
|
||||
</executor-mode>
|
||||
|
||||
@@ -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<void> {
|
||||
// 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
49
src/lib/tools/utils/CelebrationTool.ts
Normal file
49
src/lib/tools/utils/CelebrationTool.ts
Normal file
@@ -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}`
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
70
src/lib/utils/confetti.ts
Normal file
70
src/lib/utils/confetti.ts
Normal file
@@ -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;
|
||||
})();
|
||||
`;
|
||||
@@ -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<HTMLDivElement>([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<string[]>(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 */}
|
||||
<div className="relative z-0 flex flex-col items-center justify-center min-h-0 max-w-md w-full">
|
||||
{/* Main tagline with logo */}
|
||||
<div className="mb-8 flex flex-col items-center">
|
||||
<h2 className="text-3xl font-bold text-muted-foreground animate-fade-in-up flex items-baseline flex-wrap justify-center gap-2 text-center w-full">
|
||||
<span>Your <span className="text-brand">Agentic</span></span>
|
||||
<span>web assistant{' '}
|
||||
<img
|
||||
src="/assets/browseros.svg"
|
||||
alt="BrowserOS"
|
||||
className="w-7 h-7 inline-block align-text-bottom animate-fade-in-up"
|
||||
/>
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
{/* Fixed tagline near the top */}
|
||||
<div className="absolute left-0 right-0 top-16 md:top-20 flex items-center justify-center pointer-events-none select-none">
|
||||
<h2 className="text-3xl font-bold text-muted-foreground animate-fade-in-up flex items-baseline flex-wrap justify-center gap-2 text-center w-full">
|
||||
<span>Your <span className="text-brand">Agentic</span></span>
|
||||
<span>
|
||||
web assistant{' '}
|
||||
<img
|
||||
src="/assets/browseros.svg"
|
||||
alt="BrowserOS"
|
||||
className="w-7 h-7 inline-block align-text-bottom animate-fade-in-up"
|
||||
/>
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Main content - vertically centered (Examples remain centered) */}
|
||||
<div className="relative z-0 flex flex-col items-center justify-center min-h-0 max-w-md w-full">
|
||||
{/* Example Prompts */}
|
||||
<div className="mb-8 mt-2">
|
||||
<h3 className="text-lg font-semibold text-foreground mb-6 animate-fade-in-up" style={{ animationDelay: '0.4s' }}>
|
||||
@@ -240,9 +271,14 @@ export function MessageList({ messages, isProcessing = false, onScrollStateChang
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="group relative text-sm h-auto py-3 px-4 whitespace-normal bg-background/50 backdrop-blur-sm border-2 border-brand/30 hover:border-brand hover:bg-brand/5 smooth-hover smooth-transform hover:scale-105 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none overflow-hidden w-full message-enter"
|
||||
onClick={() => handleExampleClick(prompt)}
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleExampleClick(prompt)
|
||||
}}
|
||||
aria-label={`Use example: ${prompt}`}
|
||||
>
|
||||
{/* Animated background */}
|
||||
|
||||
Reference in New Issue
Block a user