feat(ext): add lang setting

This commit is contained in:
Simon
2026-02-14 16:10:46 +08:00
parent 66cc9e0a3c
commit b89228693d
5 changed files with 53 additions and 19 deletions

2
package-lock.json generated
View File

@@ -11074,7 +11074,7 @@
}, },
"packages/extension": { "packages/extension": {
"name": "@page-agent/ext", "name": "@page-agent/ext",
"version": "0.1.6", "version": "0.1.7",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@page-agent/core": "1.3.0", "@page-agent/core": "1.3.0",

View File

@@ -7,7 +7,7 @@ import type { BrowserState, PageController } from '@page-agent/page-controller'
import chalk from 'chalk' import chalk from 'chalk'
import * as zod from 'zod' import * as zod from 'zod'
import { type PageAgentConfig } from './config' import { type PageAgentConfig, type SupportedLanguage } from './config'
import { DEFAULT_MAX_STEPS } from './config/constants' import { DEFAULT_MAX_STEPS } from './config/constants'
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw' import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
import { tools } from './tools' import { tools } from './tools'
@@ -24,6 +24,7 @@ import type {
import { assert, normalizeResponse, uid, waitFor } from './utils' import { assert, normalizeResponse, uid, waitFor } from './utils'
export { type PageAgentConfig } export { type PageAgentConfig }
export type { SupportedLanguage }
export { tool, type PageAgentTool } from './tools' export { tool, type PageAgentTool } from './tools'
export type * from './types' export type * from './types'

View File

@@ -1,7 +1,7 @@
{ {
"name": "@page-agent/ext", "name": "@page-agent/ext",
"private": true, "private": true,
"version": "0.1.6", "version": "0.1.7",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "wxt", "dev": "wxt",

View File

@@ -1,22 +1,34 @@
/** /**
* React hook for using AgentController * React hook for using AgentController
*/ */
import type { AgentActivity, AgentStatus, HistoricalEvent } from '@page-agent/core' import type {
AgentActivity,
AgentStatus,
HistoricalEvent,
SupportedLanguage,
} from '@page-agent/core'
import type { LLMConfig } from '@page-agent/llms' import type { LLMConfig } from '@page-agent/llms'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { MultiPageAgent } from './MultiPageAgent' import { MultiPageAgent } from './MultiPageAgent'
import { DEMO_CONFIG } from './constants' import { DEMO_CONFIG } from './constants'
/** Language preference: undefined means follow system */
export type LanguagePreference = SupportedLanguage | undefined
export interface ExtConfig extends LLMConfig {
language?: LanguagePreference
}
export interface UseAgentResult { export interface UseAgentResult {
status: AgentStatus status: AgentStatus
history: HistoricalEvent[] history: HistoricalEvent[]
activity: AgentActivity | null activity: AgentActivity | null
currentTask: string currentTask: string
config: LLMConfig | null config: ExtConfig | null
execute: (task: string) => Promise<void> execute: (task: string) => Promise<void>
stop: () => void stop: () => void
configure: (config: LLMConfig) => Promise<void> configure: (config: ExtConfig) => Promise<void>
} }
export function useAgent(): UseAgentResult { export function useAgent(): UseAgentResult {
@@ -25,16 +37,17 @@ export function useAgent(): UseAgentResult {
const [history, setHistory] = useState<HistoricalEvent[]>([]) const [history, setHistory] = useState<HistoricalEvent[]>([])
const [activity, setActivity] = useState<AgentActivity | null>(null) const [activity, setActivity] = useState<AgentActivity | null>(null)
const [currentTask, setCurrentTask] = useState('') const [currentTask, setCurrentTask] = useState('')
const [config, setConfig] = useState<LLMConfig | null>(null) const [config, setConfig] = useState<ExtConfig | null>(null)
useEffect(() => { useEffect(() => {
chrome.storage.local.get('llmConfig').then((result) => { chrome.storage.local.get(['llmConfig', 'language']).then((result) => {
if (result.llmConfig) { const llmConfig = (result.llmConfig as LLMConfig) ?? DEMO_CONFIG
setConfig(result.llmConfig as LLMConfig) const language = (result.language as SupportedLanguage) || undefined
} else { const full = { ...llmConfig, language }
if (!result.llmConfig) {
chrome.storage.local.set({ llmConfig: DEMO_CONFIG }) chrome.storage.local.set({ llmConfig: DEMO_CONFIG })
setConfig(DEMO_CONFIG)
} }
setConfig(full)
}) })
}, []) }, [])
@@ -87,9 +100,14 @@ export function useAgent(): UseAgentResult {
agentRef.current?.stop() agentRef.current?.stop()
}, []) }, [])
const configure = useCallback(async (newConfig: LLMConfig) => { const configure = useCallback(async ({ language, ...llmConfig }: ExtConfig) => {
await chrome.storage.local.set({ llmConfig: newConfig }) await chrome.storage.local.set({ llmConfig })
setConfig(newConfig) if (language) {
await chrome.storage.local.set({ language })
} else {
await chrome.storage.local.remove('language')
}
setConfig({ ...llmConfig, language })
}, []) }, [])
return { return {

View File

@@ -1,15 +1,15 @@
import type { LLMConfig } from '@page-agent/llms'
import { Copy, CornerUpLeft, Eye, EyeOff, HatGlasses, Home, Loader2, Scale } 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 { 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 type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
interface ConfigPanelProps { interface ConfigPanelProps {
config: LLMConfig | null config: ExtConfig | null
onSave: (config: LLMConfig) => Promise<void> onSave: (config: ExtConfig) => Promise<void>
onClose: () => void onClose: () => void
} }
@@ -17,6 +17,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const [apiKey, setApiKey] = useState(config?.apiKey || DEMO_API_KEY) const [apiKey, setApiKey] = useState(config?.apiKey || DEMO_API_KEY)
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL) const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
const [model, setModel] = useState(config?.model || DEMO_MODEL) const [model, setModel] = useState(config?.model || DEMO_MODEL)
const [language, setLanguage] = useState<LanguagePreference>(config?.language)
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)
@@ -28,6 +29,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
setApiKey(config?.apiKey || DEMO_API_KEY) setApiKey(config?.apiKey || DEMO_API_KEY)
setBaseURL(config?.baseURL || DEMO_BASE_URL) setBaseURL(config?.baseURL || DEMO_BASE_URL)
setModel(config?.model || DEMO_MODEL) setModel(config?.model || DEMO_MODEL)
setLanguage(config?.language)
}, [config]) }, [config])
// Poll for user auth token every second until found // Poll for user auth token every second until found
@@ -65,7 +67,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const handleSave = async () => { const handleSave = async () => {
setSaving(true) setSaving(true)
try { try {
await onSave({ apiKey, baseURL, model }) await onSave({ apiKey, baseURL, model, language })
} finally { } finally {
setSaving(false) setSaving(false)
} }
@@ -165,6 +167,19 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
</div> </div>
</div> </div>
<div className="flex flex-col gap-1.5">
<label className="text-xs text-muted-foreground">Language</label>
<select
value={language ?? ''}
onChange={(e) => setLanguage((e.target.value || undefined) as LanguagePreference)}
className="h-8 text-xs rounded-md border border-input bg-background px-2 cursor-pointer"
>
<option value="">System</option>
<option value="en-US">English</option>
<option value="zh-CN">中文</option>
</select>
</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 cursor-pointer"> <Button variant="outline" onClick={onClose} className="flex-1 h-8 text-xs cursor-pointer">
Cancel Cancel