98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
import { type PageAgentConfig, PageAgentCore } from '@page-agent/core'
|
|
|
|
import { RemotePageController } from './RemotePageController'
|
|
import { TabsController } from './TabsController'
|
|
import SYSTEM_PROMPT from './system_prompt.md?raw'
|
|
import { createTabTools } from './tabTools'
|
|
|
|
/** Detect user language from browser settings */
|
|
function detectLanguage(): 'en-US' | 'zh-CN' {
|
|
const lang = navigator.language || navigator.languages?.[0] || 'en-US'
|
|
return lang.startsWith('zh') ? 'zh-CN' : 'en-US'
|
|
}
|
|
|
|
/**
|
|
* MultiPageAgent
|
|
* - use with extension
|
|
* - can be used from a side panel or a content script
|
|
*/
|
|
export class MultiPageAgent extends PageAgentCore {
|
|
constructor(config: Omit<PageAgentConfig, 'pageController'> & { includeInitialTab?: boolean }) {
|
|
// multi page controller
|
|
const tabsController = new TabsController()
|
|
const pageController = new RemotePageController(tabsController)
|
|
const customTools = createTabTools(tabsController)
|
|
|
|
// system prompt - auto-detect language if not specified
|
|
const language = config.language ?? detectLanguage()
|
|
const targetLanguage = language === 'zh-CN' ? '中文' : 'English'
|
|
const systemPrompt = SYSTEM_PROMPT.replace(
|
|
/Default working language: \*\*.*?\*\*/,
|
|
`Default working language: **${targetLanguage}**`
|
|
)
|
|
|
|
// include initial tab for controlling
|
|
const includeInitialTab = config.includeInitialTab ?? true
|
|
|
|
/**
|
|
* When the agent is in side-panel and user closed the side-panel.
|
|
* There is no chance for isAgentRunning to be set false.
|
|
* (unload event doesn't work well in side panel.)
|
|
* (I'm trying not to use long-lived connection because the lifecycle of a sw is hard to predict.)
|
|
* This heartbeat mechanism acts as a backup.
|
|
*/
|
|
let heartBeatInterval: null | number = null
|
|
|
|
super({
|
|
...config,
|
|
pageController: pageController as any,
|
|
customTools: customTools,
|
|
customSystemPrompt: systemPrompt,
|
|
|
|
onBeforeTask: async (agent) => {
|
|
await tabsController.init(agent.task, includeInitialTab)
|
|
|
|
heartBeatInterval = window.setInterval(() => {
|
|
chrome.storage.local.set({
|
|
agentHeartbeat: Date.now(),
|
|
})
|
|
}, 1_000)
|
|
|
|
await chrome.storage.local.set({
|
|
isAgentRunning: true,
|
|
})
|
|
},
|
|
|
|
onAfterTask: async () => {
|
|
if (heartBeatInterval) {
|
|
window.clearInterval(heartBeatInterval)
|
|
heartBeatInterval = null
|
|
}
|
|
|
|
await chrome.storage.local.set({
|
|
isAgentRunning: false,
|
|
})
|
|
},
|
|
|
|
onBeforeStep: async (agent) => {
|
|
// make sure the current tab is loaded before the step starts
|
|
await tabsController.waitUntilTabLoaded(tabsController.currentTabId!)
|
|
},
|
|
|
|
onDispose: () => {
|
|
if (heartBeatInterval) {
|
|
window.clearInterval(heartBeatInterval)
|
|
heartBeatInterval = null
|
|
}
|
|
|
|
chrome.storage.local.set({
|
|
isAgentRunning: false,
|
|
})
|
|
|
|
// no need to dispose tabsController and pageController
|
|
// as they do not keep references
|
|
},
|
|
})
|
|
}
|
|
}
|