mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-17 02:25:57 +00:00
feat: inject stop button to pages controlled by agent (#334)
* chore: baseline setup * feat(agent): When the agent is running, right now we inject an orange glow. See the `apps/age Task ID: TOiaMuDz * fix: clean up agent storage * fix: improve the stop button style * fix: type issues with stopAgentStorage --------- Co-authored-by: BrowserOS Coding Agent <coding-agent@browseros.com> Co-authored-by: Dani Akash <DaniAkash@users.noreply.github.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -181,3 +181,6 @@ log.txt
|
||||
|
||||
# Testing iteration temp files
|
||||
tmp/
|
||||
|
||||
# Coding agent artifacts
|
||||
.agent/
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
syncScheduledJobs,
|
||||
} from '@/lib/schedules/scheduleStorage'
|
||||
import { searchActionsStorage } from '@/lib/search-actions/searchActionsStorage'
|
||||
import { stopAgentStorage } from '@/lib/stop-agent/stop-agent-storage'
|
||||
import { scheduledJobRuns } from './scheduledJobRuns'
|
||||
|
||||
export default defineBackground(() => {
|
||||
@@ -69,6 +70,13 @@ export default defineBackground(() => {
|
||||
url: chrome.runtime.getURL('app.html#/home'),
|
||||
})
|
||||
}
|
||||
|
||||
if (message?.type === 'stop-agent' && message?.conversationId) {
|
||||
stopAgentStorage.setValue({
|
||||
conversationId: message.conversationId,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
sessionStorage.watch(async (newSession) => {
|
||||
|
||||
@@ -2,10 +2,13 @@ import type { GlowMessage } from './GlowMessage'
|
||||
|
||||
const GLOW_OVERLAY_ID = 'browseros-glow-overlay'
|
||||
const GLOW_STYLES_ID = 'browseros-glow-styles'
|
||||
const GLOW_STOP_BTN_ID = 'browseros-glow-stop-btn'
|
||||
|
||||
const GLOW_THICKNESS = 1.0
|
||||
const GLOW_OPACITY = 0.6
|
||||
|
||||
let activeConversationId: string | null = null
|
||||
|
||||
function injectStyles(): void {
|
||||
if (document.getElementById(GLOW_STYLES_ID)) {
|
||||
return
|
||||
@@ -45,6 +48,11 @@ function injectStyles(): void {
|
||||
to { opacity: ${GLOW_OPACITY}; }
|
||||
}
|
||||
|
||||
@keyframes browseros-glow-btn-fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
#${GLOW_OVERLAY_ID} {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
@@ -59,6 +67,34 @@ function injectStyles(): void {
|
||||
browseros-glow-pulse 3s ease-in-out infinite,
|
||||
browseros-glow-fade-in 420ms cubic-bezier(0.22, 1, 0.36, 1) forwards !important;
|
||||
}
|
||||
|
||||
#${GLOW_STOP_BTN_ID} {
|
||||
position: fixed !important;
|
||||
bottom: 24px !important;
|
||||
left: 50% !important;
|
||||
transform: translateX(-50%) !important;
|
||||
width: 48px !important;
|
||||
height: 48px !important;
|
||||
border-radius: 50% !important;
|
||||
background: rgba(220, 38, 38, 0.95) !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
pointer-events: auto !important;
|
||||
cursor: pointer !important;
|
||||
z-index: 2147483647 !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
line-height: 1 !important;
|
||||
padding: 0 !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
|
||||
opacity: 0;
|
||||
animation: browseros-glow-btn-fade-in 420ms cubic-bezier(0.22, 1, 0.36, 1) forwards !important;
|
||||
}
|
||||
|
||||
#${GLOW_STOP_BTN_ID}:hover {
|
||||
background: rgba(185, 28, 28, 1) !important;
|
||||
}
|
||||
`
|
||||
const appendStyle = () => document.head.appendChild(style)
|
||||
|
||||
@@ -76,6 +112,21 @@ function startGlow(): void {
|
||||
const overlay = document.createElement('div')
|
||||
overlay.id = GLOW_OVERLAY_ID
|
||||
|
||||
const button = document.createElement('button')
|
||||
button.id = GLOW_STOP_BTN_ID
|
||||
button.innerHTML =
|
||||
'<svg width="16" height="16" viewBox="0 0 16 16" fill="white" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="14" height="14" rx="2"/></svg>'
|
||||
button.addEventListener('click', () => {
|
||||
if (activeConversationId) {
|
||||
browser.runtime.sendMessage({
|
||||
type: 'stop-agent',
|
||||
conversationId: activeConversationId,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
overlay.appendChild(button)
|
||||
|
||||
const appendOverlay = () => document.body.appendChild(overlay)
|
||||
|
||||
if (document.body) {
|
||||
@@ -96,8 +147,6 @@ export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
runAt: 'document_start',
|
||||
main() {
|
||||
let activeConversationId: string | null = null
|
||||
|
||||
browser.runtime.onMessage.addListener(
|
||||
(message: GlowMessage, _sender, sendResponse) => {
|
||||
if (
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useAgentServerUrl } from '@/lib/browseros/useBrowserOSProviders'
|
||||
import type { ChatAction } from '@/lib/chat-actions/types'
|
||||
import {
|
||||
CONVERSATION_RESET_EVENT,
|
||||
GLOW_STOP_CLICKED_EVENT,
|
||||
MESSAGE_DISLIKE_EVENT,
|
||||
MESSAGE_LIKE_EVENT,
|
||||
MESSAGE_SENT_EVENT,
|
||||
@@ -23,6 +24,7 @@ import { useGraphqlQuery } from '@/lib/graphql/useGraphqlQuery'
|
||||
import { useLlmProviders } from '@/lib/llm-providers/useLlmProviders'
|
||||
import { track } from '@/lib/metrics/track'
|
||||
import { searchActionsStorage } from '@/lib/search-actions/searchActionsStorage'
|
||||
import { stopAgentStorage } from '@/lib/stop-agent/stop-agent-storage'
|
||||
import { selectedWorkspaceStorage } from '@/lib/workspace/workspace-storage'
|
||||
import type { ChatMode } from './chatTypes'
|
||||
import { GetConversationWithMessagesDocument } from './graphql/chatSessionDocument'
|
||||
@@ -400,6 +402,18 @@ export const useChatSession = () => {
|
||||
return () => unwatch()
|
||||
}, [])
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: only need to run this once
|
||||
useEffect(() => {
|
||||
const unwatch = stopAgentStorage.watch((signal) => {
|
||||
if (signal && signal.conversationId === conversationIdRef.current) {
|
||||
stop()
|
||||
track(GLOW_STOP_CLICKED_EVENT)
|
||||
stopAgentStorage.setValue(null)
|
||||
}
|
||||
})
|
||||
return () => unwatch()
|
||||
}, [])
|
||||
|
||||
const handleSelectProvider = (provider: Provider) => {
|
||||
track(PROVIDER_SELECTED_EVENT, {
|
||||
provider_id: provider.id,
|
||||
|
||||
@@ -122,6 +122,9 @@ export const SIDEPANEL_MODE_CHANGED_EVENT = 'sidepanel.mode.changed'
|
||||
/** @public */
|
||||
export const SIDEPANEL_STOP_CLICKED_EVENT = 'sidepanel.generation.stopped'
|
||||
|
||||
/** @public */
|
||||
export const GLOW_STOP_CLICKED_EVENT = 'glow.generation.stopped'
|
||||
|
||||
/** @public */
|
||||
export const SIDEPANEL_MESSAGE_COPIED_EVENT = 'sidepanel.message.copied'
|
||||
|
||||
|
||||
19
apps/agent/lib/stop-agent/stop-agent-storage.ts
Normal file
19
apps/agent/lib/stop-agent/stop-agent-storage.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { storage } from '@wxt-dev/storage'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface StopAgentStorage {
|
||||
conversationId: string
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const stopAgentStorage = storage.defineItem<StopAgentStorage | null>(
|
||||
'local:stop-agent',
|
||||
{
|
||||
fallback: null,
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user