Compare commits

..

7 Commits

Author SHA1 Message Date
Simon a834d37f28 fix(ext): inject versions 2026-02-02 21:11:23 +08:00
Simon 0c3be817c9 style(ext): attribution; dark mode 2026-02-02 21:07:31 +08:00
Simon b8999fdb7c style(ext): improve config panel 2026-02-02 19:51:48 +08:00
Simon 9ba95e96b6 feat(ext): detect user lang 2026-02-02 19:29:30 +08:00
Simon 4459026297 feat: set default lang to english; fix ext lang setting 2026-02-02 19:17:15 +08:00
Simon 98d3016395 style(ext): remove raw section from ResultCard 2026-02-02 19:16:07 +08:00
Simon 15ea488719 fix(ext): save config to storage 2026-02-02 18:48:16 +08:00
11 changed files with 165 additions and 26 deletions
+1 -3
View File
@@ -417,10 +417,8 @@ export class PageAgentCore extends EventTarget {
return this.config.customSystemPrompt return this.config.customSystemPrompt
} }
let systemPrompt = SYSTEM_PROMPT
const targetLanguage = this.config.language === 'zh-CN' ? '中文' : 'English' const targetLanguage = this.config.language === 'zh-CN' ? '中文' : 'English'
systemPrompt = systemPrompt.replace( const systemPrompt = SYSTEM_PROMPT.replace(
/Default working language: \*\*.*?\*\*/, /Default working language: \*\*.*?\*\*/,
`Default working language: **${targetLanguage}**` `Default working language: **${targetLanguage}**`
) )
+1 -1
View File
@@ -10,7 +10,7 @@ You excel at following tasks:
</intro> </intro>
<language_settings> <language_settings>
- Default working language: **中文** - Default working language: **English**
- Use the language that user is using. Return in user's language. - Use the language that user is using. Return in user's language.
</language_settings> </language_settings>
+16 -1
View File
@@ -5,6 +5,12 @@ import { TabsController } from './TabsController'
import SYSTEM_PROMPT from './system_prompt.md?raw' import SYSTEM_PROMPT from './system_prompt.md?raw'
import { createTabTools } from './tabTools' 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 * MultiPageAgent
* - use with extension * - use with extension
@@ -12,10 +18,19 @@ import { createTabTools } from './tabTools'
*/ */
export class MultiPageAgent extends PageAgentCore { export class MultiPageAgent extends PageAgentCore {
constructor(config: Omit<PageAgentConfig, 'pageController'>) { constructor(config: Omit<PageAgentConfig, 'pageController'>) {
// multi page controller
const tabsController = new TabsController() const tabsController = new TabsController()
const pageController = new RemotePageController(tabsController) const pageController = new RemotePageController(tabsController)
const customTools = createTabTools(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}**`
)
/** /**
* When the agent is in side-panel and user closed the side-panel. * When the agent is in side-panel and user closed the side-panel.
* There is no chance for isAgentRunning to be set false. * There is no chance for isAgentRunning to be set false.
@@ -29,7 +44,7 @@ export class MultiPageAgent extends PageAgentCore {
...config, ...config,
pageController: pageController as any, pageController: pageController as any,
customTools: customTools, customTools: customTools,
customSystemPrompt: SYSTEM_PROMPT, customSystemPrompt: systemPrompt,
onBeforeTask: async (agent) => { onBeforeTask: async (agent) => {
await tabsController.init(agent.task) await tabsController.init(agent.task)
@@ -10,7 +10,7 @@ You excel at following tasks:
</intro> </intro>
<language_settings> <language_settings>
- Default working language: **中文** - Default working language: **English**
- Use the language that user is using. Return in user's language. - Use the language that user is using. Return in user's language.
</language_settings> </language_settings>
+1
View File
@@ -88,6 +88,7 @@ export function useAgent(): UseAgentResult {
}, []) }, [])
const configure = useCallback(async (newConfig: LLMConfig) => { const configure = useCallback(async (newConfig: LLMConfig) => {
await chrome.storage.local.set({ llmConfig: newConfig })
setConfig(newConfig) setConfig(newConfig)
}, []) }, [])
+1 -1
View File
@@ -40,7 +40,7 @@
} }
.dark { .dark {
--background: oklch(0.145 0 0); --background: oklch(0.19 0 0);
--foreground: oklch(0.985 0 0); --foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0); --card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0); --card-foreground: oklch(0.985 0 0);
@@ -82,7 +82,12 @@ export default function App() {
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<StatusDot status={status} /> <StatusDot status={status} />
<Button variant="ghost" size="icon-sm" onClick={() => setShowConfig(true)}> <Button
variant="ghost"
size="icon-sm"
onClick={() => setShowConfig(true)}
className="cursor-pointer"
>
<Settings className="size-3.5" /> <Settings className="size-3.5" />
</Button> </Button>
</div> </div>
@@ -142,7 +147,7 @@ export default function App() {
variant="default" variant="default"
onClick={() => handleSubmit()} onClick={() => handleSubmit()}
disabled={!inputValue.trim()} disabled={!inputValue.trim()}
className="size-7" className="size-7 cursor-pointer"
> >
<Send className="size-3" /> <Send className="size-3" />
</InputGroupButton> </InputGroupButton>
@@ -1,11 +1,15 @@
import type { LLMConfig } from '@page-agent/llms' import type { LLMConfig } from '@page-agent/llms'
import { Copy, Loader2 } from 'lucide-react' import { Copy, CornerUpLeft, Eye, EyeOff, HatGlasses, Home, Loader2, Scale } from 'lucide-react'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { siGithub } from 'simple-icons'
import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL } from '@/agent/constants' import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL } from '@/agent/constants'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
declare const __EXT_VERSION__: string
declare const __CORE_VERSION__: string
interface ConfigPanelProps { interface ConfigPanelProps {
config: LLMConfig | null config: LLMConfig | null
onSave: (config: LLMConfig) => Promise<void> onSave: (config: LLMConfig) => Promise<void>
@@ -19,6 +23,8 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [userAuthToken, setUserAuthToken] = useState<string>('') const [userAuthToken, setUserAuthToken] = useState<string>('')
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
const [showToken, setShowToken] = useState(false)
const [showApiKey, setShowApiKey] = useState(false)
// Update local state when config prop changes // Update local state when config prop changes
useEffect(() => { useEffect(() => {
@@ -69,8 +75,18 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
} }
return ( return (
<div className="flex flex-col gap-4 p-4"> <div className="flex flex-col gap-4 p-4 relative">
<h2 className="text-base font-semibold">Settings</h2> <div className="flex items-center justify-between">
<h2 className="text-base font-semibold">Settings</h2>
<Button
variant="ghost"
size="icon-sm"
onClick={onClose}
className="absolute top-2 right-3 cursor-pointer"
>
<CornerUpLeft className="size-3.5" />
</Button>
</div>
{/* User Auth Token Section */} {/* User Auth Token Section */}
<div className="flex flex-col gap-1.5 p-3 bg-muted/50 rounded-md border"> <div className="flex flex-col gap-1.5 p-3 bg-muted/50 rounded-md border">
@@ -81,9 +97,24 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Input <Input
readOnly readOnly
value={userAuthToken || 'Loading...'} value={
userAuthToken
? showToken
? userAuthToken
: `${userAuthToken.slice(0, 4)}${''.repeat(userAuthToken.length - 8)}${userAuthToken.slice(-4)}`
: 'Loading...'
}
className="text-xs h-8 font-mono bg-background" className="text-xs h-8 font-mono bg-background"
/> />
<Button
variant="outline"
size="icon"
className="h-8 w-8 shrink-0 cursor-pointer"
onClick={() => setShowToken(!showToken)}
disabled={!userAuthToken}
>
{showToken ? <EyeOff className="size-3" /> : <Eye className="size-3" />}
</Button>
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
@@ -118,23 +149,98 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<label className="text-xs text-muted-foreground">API Key</label> <label className="text-xs text-muted-foreground">API Key</label>
<Input <div className="flex gap-2 items-center">
type="password" <Input
placeholder="sk-..." type={showApiKey ? 'text' : 'password'}
value={apiKey} placeholder="sk-..."
onChange={(e) => setApiKey(e.target.value)} value={apiKey}
className="text-xs h-8" onChange={(e) => setApiKey(e.target.value)}
/> className="text-xs h-8"
/>
<Button
variant="outline"
size="icon"
className="h-8 w-8 shrink-0 cursor-pointer"
onClick={() => setShowApiKey(!showApiKey)}
>
{showApiKey ? <EyeOff className="size-3" /> : <Eye className="size-3" />}
</Button>
</div>
</div> </div>
<div className="flex gap-2 mt-2"> <div className="flex gap-2 mt-2">
<Button variant="outline" onClick={onClose} className="flex-1 h-8 text-xs"> <Button variant="outline" onClick={onClose} className="flex-1 h-8 text-xs cursor-pointer">
Cancel Cancel
</Button> </Button>
<Button onClick={handleSave} disabled={saving} className="flex-1 h-8 text-xs"> <Button
onClick={handleSave}
disabled={saving}
className="flex-1 h-8 text-xs cursor-pointer"
>
{saving ? <Loader2 className="size-3 animate-spin" /> : 'Save'} {saving ? <Loader2 className="size-3 animate-spin" /> : 'Save'}
</Button> </Button>
</div> </div>
{/* Footer */}
<div className="mt-4 mb-4 pt-4 border-t border-border/50 flex gap-2 justify-between text-[10px] text-muted-foreground">
<div className="flex flex-col justify-between">
<a
href="https://github.com/alibaba/page-agent"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 hover:text-foreground"
>
<svg role="img" viewBox="0 0 24 24" className="size-3 fill-current">
<path d={siGithub.path} />
</svg>
<span>Source Code</span>
</a>
<a
href="https://alibaba.github.io/page-agent/"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 hover:text-foreground"
>
<Home className="size-3" />
<span>Home Page</span>
</a>
<a
href="https://github.com/alibaba/page-agent/blob/main/packages/extension/PRIVACY.md"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 hover:text-foreground"
>
<HatGlasses className="size-3" />
<span>Privacy Policy</span>
</a>
</div>
<div className="flex flex-col items-end">
<span>
Extension <span className="font-mono">v{__EXT_VERSION__}</span>
</span>
<span>
PageAgent <span className="font-mono">v{__CORE_VERSION__}</span>
</span>
</div>
</div>
{/* attribute */}
<div className="text-[10px] text-muted-foreground bg-background fixed bottom-0 w-full flex justify-around">
<span className="leading-loose">
Built with ♥️ by{' '}
<a
href="https://github.com/gaomeng1900"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
@Simon
</a>
</span>
</div>
</div> </div>
) )
} }
@@ -315,9 +315,7 @@ export function EventCard({ event }: { event: HistoricalEvent }) {
<ResultCard <ResultCard
success={input?.success ?? true} success={input?.success ?? true}
text={input?.text || event.action.output || ''} text={input?.text || event.action.output || ''}
> />
<RawSection rawRequest={event.rawRequest} rawResponse={event.rawResponse} />
</ResultCard>
</> </>
) )
} }
@@ -6,6 +6,16 @@ import { ErrorBoundary } from './components/ErrorBoundary'
import '@/assets/index.css' import '@/assets/index.css'
// Sync dark mode with system preference
const syncDarkMode = () => {
document.documentElement.classList.toggle(
'dark',
matchMedia('(prefers-color-scheme: dark)').matches
)
}
syncDarkMode()
matchMedia('(prefers-color-scheme: dark)').addEventListener('change', syncDarkMode)
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<ErrorBoundary> <ErrorBoundary>
+7 -1
View File
@@ -1,10 +1,12 @@
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import { mkdirSync } from 'node:fs' import { mkdirSync, readFileSync } from 'node:fs'
import { defineConfig } from 'wxt' import { defineConfig } from 'wxt'
const chromeProfile = '.wxt/chrome-data' const chromeProfile = '.wxt/chrome-data'
mkdirSync(chromeProfile, { recursive: true }) mkdirSync(chromeProfile, { recursive: true })
const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'))
// See https://wxt.dev/api/config.html // See https://wxt.dev/api/config.html
export default defineConfig({ export default defineConfig({
srcDir: 'src', srcDir: 'src',
@@ -16,6 +18,10 @@ export default defineConfig({
}, },
vite: () => ({ vite: () => ({
plugins: [tailwindcss()], plugins: [tailwindcss()],
define: {
__EXT_VERSION__: JSON.stringify(pkg.version),
__CORE_VERSION__: JSON.stringify(pkg.dependencies['@page-agent/core']),
},
optimizeDeps: { optimizeDeps: {
force: true, force: true,
}, },