feat: improve i18n for panel
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
import { type SupportedLanguage, locales } from './locales'
|
import {
|
||||||
import type { TranslationKey, TranslationParams, TranslationSchema } from './types'
|
type SupportedLanguage,
|
||||||
|
type TranslationKey,
|
||||||
|
type TranslationParams,
|
||||||
|
type TranslationSchema,
|
||||||
|
locales,
|
||||||
|
} from './locales'
|
||||||
|
|
||||||
export class I18n {
|
export class I18n {
|
||||||
private language: SupportedLanguage
|
private language: SupportedLanguage
|
||||||
|
|||||||
@@ -1,7 +1,55 @@
|
|||||||
import type { TranslationSchema } from './types'
|
// English translations (base/reference language)
|
||||||
|
const enUS = {
|
||||||
|
ui: {
|
||||||
|
panel: {
|
||||||
|
ready: 'Ready',
|
||||||
|
thinking: 'Thinking...',
|
||||||
|
paused: 'Paused',
|
||||||
|
taskInput: 'Enter new task, describe steps in detail, press Enter to submit',
|
||||||
|
userAnswerPrompt: 'Please answer the question above, press Enter to submit',
|
||||||
|
taskTerminated: 'Task terminated',
|
||||||
|
taskCompleted: 'Task completed',
|
||||||
|
continueExecution: 'Continue execution',
|
||||||
|
userAnswer: 'User answer: {{input}}',
|
||||||
|
question: 'Question: {{question}}',
|
||||||
|
waitingPlaceholder: 'Waiting for task to start...',
|
||||||
|
pause: 'Pause',
|
||||||
|
continue: 'Continue',
|
||||||
|
stop: 'Stop',
|
||||||
|
expand: 'Expand history',
|
||||||
|
collapse: 'Collapse history',
|
||||||
|
step: 'Step {{number}} · {{time}}{{duration}}',
|
||||||
|
},
|
||||||
|
tools: {
|
||||||
|
clicking: 'Clicking element [{{index}}]...',
|
||||||
|
inputting: 'Inputting text to element [{{index}}]...',
|
||||||
|
selecting: 'Selecting option "{{text}}"...',
|
||||||
|
scrolling: 'Scrolling page...',
|
||||||
|
waiting: 'Waiting {{seconds}} seconds...',
|
||||||
|
done: 'Task done',
|
||||||
|
clicked: '🖱️ Clicked element [{{index}}]',
|
||||||
|
inputted: '⌨️ Inputted text "{{text}}"',
|
||||||
|
selected: '☑️ Selected option "{{text}}"',
|
||||||
|
scrolled: '🛞 Page scrolled',
|
||||||
|
waited: '⌛️ Wait completed',
|
||||||
|
executing: 'Executing {{toolName}}...',
|
||||||
|
resultSuccess: 'success',
|
||||||
|
resultFailure: 'failed',
|
||||||
|
resultError: 'error',
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
elementNotFound: 'No interactive element found at index {{index}}',
|
||||||
|
taskRequired: 'Task description is required',
|
||||||
|
executionFailed: 'Task execution failed',
|
||||||
|
notInputElement: 'Element is not an input or textarea',
|
||||||
|
notSelectElement: 'Element is not a select element',
|
||||||
|
optionNotFound: 'Option "{{text}}" not found',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
// 中文翻译(作为基准)
|
// Chinese translations (must match the structure of enUS)
|
||||||
const zhCN: TranslationSchema = {
|
const zhCN = {
|
||||||
ui: {
|
ui: {
|
||||||
panel: {
|
panel: {
|
||||||
ready: '准备就绪',
|
ready: '准备就绪',
|
||||||
@@ -13,6 +61,8 @@ const zhCN: TranslationSchema = {
|
|||||||
taskCompleted: '任务结束',
|
taskCompleted: '任务结束',
|
||||||
continueExecution: '继续执行',
|
continueExecution: '继续执行',
|
||||||
userAnswer: '用户回答: {{input}}',
|
userAnswer: '用户回答: {{input}}',
|
||||||
|
question: '询问: {{question}}',
|
||||||
|
waitingPlaceholder: '等待任务开始...',
|
||||||
pause: '暂停',
|
pause: '暂停',
|
||||||
continue: '继续',
|
continue: '继续',
|
||||||
stop: '终止',
|
stop: '终止',
|
||||||
@@ -33,6 +83,9 @@ const zhCN: TranslationSchema = {
|
|||||||
scrolled: '🛞 页面滚动完成',
|
scrolled: '🛞 页面滚动完成',
|
||||||
waited: '⌛️ 等待完成',
|
waited: '⌛️ 等待完成',
|
||||||
executing: '正在执行 {{toolName}}...',
|
executing: '正在执行 {{toolName}}...',
|
||||||
|
resultSuccess: '成功',
|
||||||
|
resultFailure: '失败',
|
||||||
|
resultError: '错误',
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
elementNotFound: '未找到索引为 {{index}} 的交互元素',
|
elementNotFound: '未找到索引为 {{index}} 的交互元素',
|
||||||
@@ -45,54 +98,29 @@ const zhCN: TranslationSchema = {
|
|||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
// 英文翻译(必须符合相同的结构)
|
// Type definitions generated from English base structure (but with string values)
|
||||||
const enUS: TranslationSchema = {
|
type DeepStringify<T> = {
|
||||||
ui: {
|
[K in keyof T]: T[K] extends string ? string : T[K] extends object ? DeepStringify<T[K]> : T[K]
|
||||||
panel: {
|
}
|
||||||
ready: 'Ready',
|
|
||||||
thinking: 'Thinking...',
|
export type TranslationSchema = DeepStringify<typeof enUS>
|
||||||
paused: 'Paused',
|
|
||||||
taskInput: 'Enter new task, describe steps in detail, press Enter to submit',
|
// Utility type: Extract all nested paths from translation object
|
||||||
userAnswerPrompt: 'Please answer the question above, press Enter to submit',
|
type NestedKeyOf<ObjectType extends object> = {
|
||||||
taskTerminated: 'Task terminated',
|
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
|
||||||
taskCompleted: 'Task completed',
|
? `${Key}` | `${Key}.${NestedKeyOf<ObjectType[Key]>}`
|
||||||
continueExecution: 'Continue execution',
|
: `${Key}`
|
||||||
userAnswer: 'User answer: {{input}}',
|
}[keyof ObjectType & (string | number)]
|
||||||
pause: 'Pause',
|
|
||||||
continue: 'Continue',
|
// Extract all possible key paths from translation structure
|
||||||
stop: 'Stop',
|
export type TranslationKey = NestedKeyOf<TranslationSchema>
|
||||||
expand: 'Expand history',
|
|
||||||
collapse: 'Collapse history',
|
// Parameterized translation types
|
||||||
step: 'Step {{number}} · {{time}}{{duration}}',
|
export type TranslationParams = Record<string, string | number>
|
||||||
},
|
|
||||||
tools: {
|
|
||||||
clicking: 'Clicking element [{{index}}]...',
|
|
||||||
inputting: 'Inputting text to element [{{index}}]...',
|
|
||||||
selecting: 'Selecting option "{{text}}"...',
|
|
||||||
scrolling: 'Scrolling page...',
|
|
||||||
waiting: 'Waiting {{seconds}} seconds...',
|
|
||||||
done: 'Task done',
|
|
||||||
clicked: '🖱️ Clicked element [{{index}}]',
|
|
||||||
inputted: '⌨️ Inputted text "{{text}}"',
|
|
||||||
selected: '☑️ Selected option "{{text}}"',
|
|
||||||
scrolled: '🛞 Page scrolled',
|
|
||||||
waited: '⌛️ Wait completed',
|
|
||||||
executing: '正在执行 {{toolName}}...',
|
|
||||||
},
|
|
||||||
errors: {
|
|
||||||
elementNotFound: 'No interactive element found at index {{index}}',
|
|
||||||
taskRequired: 'Task description is required',
|
|
||||||
executionFailed: 'Task execution failed',
|
|
||||||
notInputElement: 'Element is not an input or textarea',
|
|
||||||
notSelectElement: 'Element is not a select element',
|
|
||||||
optionNotFound: 'Option "{{text}}" not found',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as const
|
|
||||||
|
|
||||||
export const locales = {
|
export const locales = {
|
||||||
'zh-CN': zhCN,
|
|
||||||
'en-US': enUS,
|
'en-US': enUS,
|
||||||
|
'zh-CN': zhCN,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type SupportedLanguage = keyof typeof locales
|
export type SupportedLanguage = keyof typeof locales
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
// 定义翻译数据的结构类型
|
|
||||||
export interface TranslationSchema {
|
|
||||||
ui: {
|
|
||||||
panel: {
|
|
||||||
ready: string
|
|
||||||
thinking: string
|
|
||||||
paused: string
|
|
||||||
taskInput: string
|
|
||||||
userAnswerPrompt: string
|
|
||||||
taskTerminated: string
|
|
||||||
taskCompleted: string
|
|
||||||
continueExecution: string
|
|
||||||
userAnswer: string
|
|
||||||
pause: string
|
|
||||||
continue: string
|
|
||||||
stop: string
|
|
||||||
expand: string
|
|
||||||
collapse: string
|
|
||||||
step: string
|
|
||||||
}
|
|
||||||
tools: {
|
|
||||||
clicking: string
|
|
||||||
inputting: string
|
|
||||||
selecting: string
|
|
||||||
scrolling: string
|
|
||||||
waiting: string
|
|
||||||
done: string
|
|
||||||
clicked: string
|
|
||||||
inputted: string
|
|
||||||
selected: string
|
|
||||||
scrolled: string
|
|
||||||
waited: string
|
|
||||||
executing: string
|
|
||||||
}
|
|
||||||
errors: {
|
|
||||||
elementNotFound: string
|
|
||||||
taskRequired: string
|
|
||||||
executionFailed: string
|
|
||||||
notInputElement: string
|
|
||||||
notSelectElement: string
|
|
||||||
optionNotFound: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 工具类型:提取嵌套对象的所有路径
|
|
||||||
type NestedKeyOf<ObjectType extends object> = {
|
|
||||||
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
|
|
||||||
? `${Key}` | `${Key}.${NestedKeyOf<ObjectType[Key]>}`
|
|
||||||
: `${Key}`
|
|
||||||
}[keyof ObjectType & (string | number)]
|
|
||||||
|
|
||||||
// 从翻译结构中提取所有可能的key路径
|
|
||||||
export type TranslationKey = NestedKeyOf<TranslationSchema>
|
|
||||||
|
|
||||||
// 参数化翻译的类型
|
|
||||||
export type TranslationParams = Record<string, string | number>
|
|
||||||
@@ -70,10 +70,8 @@ export class Panel {
|
|||||||
// Update state to `running`
|
// Update state to `running`
|
||||||
this.#update({
|
this.#update({
|
||||||
type: 'output',
|
type: 'output',
|
||||||
displayText: `询问: ${question}`,
|
displayText: this.#pageAgent.i18n.t('ui.panel.question', { question }),
|
||||||
})
|
}) // Expand history panel
|
||||||
|
|
||||||
// Expand history panel
|
|
||||||
if (!this.#isExpanded) {
|
if (!this.#isExpanded) {
|
||||||
this.#expand()
|
this.#expand()
|
||||||
}
|
}
|
||||||
@@ -132,7 +130,7 @@ export class Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 隐藏面板
|
* Hide panel
|
||||||
*/
|
*/
|
||||||
#hide(): void {
|
#hide(): void {
|
||||||
this.wrapper.style.opacity = '0'
|
this.wrapper.style.opacity = '0'
|
||||||
@@ -141,7 +139,7 @@ export class Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置状态
|
* Reset state
|
||||||
*/
|
*/
|
||||||
#reset(): void {
|
#reset(): void {
|
||||||
this.#state.reset()
|
this.#state.reset()
|
||||||
@@ -168,44 +166,44 @@ export class Panel {
|
|||||||
|
|
||||||
// Update status display
|
// Update status display
|
||||||
if (this.#pageAgent.paused) {
|
if (this.#pageAgent.paused) {
|
||||||
this.#statusText.textContent = '暂停中,稍后'
|
this.#statusText.textContent = this.#pageAgent.i18n.t('ui.panel.paused')
|
||||||
this.#updateStatusIndicator('thinking') // Use existing thinking state
|
this.#updateStatusIndicator('thinking') // Use existing thinking state
|
||||||
} else {
|
} else {
|
||||||
this.#statusText.textContent = '继续执行'
|
this.#statusText.textContent = this.#pageAgent.i18n.t('ui.panel.continueExecution')
|
||||||
this.#updateStatusIndicator('tool_executing') // Restore to execution state
|
this.#updateStatusIndicator('tool_executing') // Restore to execution state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新暂停按钮状态
|
* Update pause button state
|
||||||
*/
|
*/
|
||||||
#updatePauseButton(): void {
|
#updatePauseButton(): void {
|
||||||
if (this.#pageAgent.paused) {
|
if (this.#pageAgent.paused) {
|
||||||
this.#pauseButton.textContent = '▶'
|
this.#pauseButton.textContent = '▶'
|
||||||
this.#pauseButton.title = '继续'
|
this.#pauseButton.title = this.#pageAgent.i18n.t('ui.panel.continue')
|
||||||
this.#pauseButton.classList.add(styles.paused)
|
this.#pauseButton.classList.add(styles.paused)
|
||||||
} else {
|
} else {
|
||||||
this.#pauseButton.textContent = '⏸︎'
|
this.#pauseButton.textContent = '⏸︎'
|
||||||
this.#pauseButton.title = '暂停'
|
this.#pauseButton.title = this.#pageAgent.i18n.t('ui.panel.pause')
|
||||||
this.#pauseButton.classList.remove(styles.paused)
|
this.#pauseButton.classList.remove(styles.paused)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 终止 Agent
|
* Stop Agent
|
||||||
*/
|
*/
|
||||||
#stopAgent(): void {
|
#stopAgent(): void {
|
||||||
// Update status display
|
// Update status display
|
||||||
this.#update({
|
this.#update({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
displayText: '任务已终止',
|
displayText: this.#pageAgent.i18n.t('ui.panel.taskTerminated'),
|
||||||
})
|
})
|
||||||
|
|
||||||
this.#pageAgent.dispose()
|
this.#pageAgent.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交任务
|
* Submit task
|
||||||
*/
|
*/
|
||||||
#submitTask() {
|
#submitTask() {
|
||||||
const input = this.#taskInput.value.trim()
|
const input = this.#taskInput.value.trim()
|
||||||
@@ -223,13 +221,13 @@ export class Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理用户回答
|
* Handle user answer
|
||||||
*/
|
*/
|
||||||
#handleUserAnswer(input: string): void {
|
#handleUserAnswer(input: string): void {
|
||||||
// Add user input to history
|
// Add user input to history
|
||||||
this.#update({
|
this.#update({
|
||||||
type: 'input',
|
type: 'input',
|
||||||
displayText: `用户回答: ${input}`,
|
displayText: this.#pageAgent.i18n.t('ui.panel.userAnswer', { input }),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
@@ -243,12 +241,12 @@ export class Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示输入区域
|
* Show input area
|
||||||
*/
|
*/
|
||||||
#showInputArea(placeholder?: string): void {
|
#showInputArea(placeholder?: string): void {
|
||||||
// Clear input field
|
// Clear input field
|
||||||
this.#taskInput.value = ''
|
this.#taskInput.value = ''
|
||||||
this.#taskInput.placeholder = placeholder || '输入新任务,详细描述步骤,回车提交'
|
this.#taskInput.placeholder = placeholder || this.#pageAgent.i18n.t('ui.panel.taskInput')
|
||||||
this.#inputSection.classList.remove(styles.hidden)
|
this.#inputSection.classList.remove(styles.hidden)
|
||||||
// Focus on input field
|
// Focus on input field
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -257,14 +255,14 @@ export class Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 隐藏输入区域
|
* Hide input area
|
||||||
*/
|
*/
|
||||||
#hideInputArea(): void {
|
#hideInputArea(): void {
|
||||||
this.#inputSection.classList.add(styles.hidden)
|
this.#inputSection.classList.add(styles.hidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查是否应该显示输入区域
|
* Check if input area should be shown
|
||||||
*/
|
*/
|
||||||
#shouldShowInputArea(): boolean {
|
#shouldShowInputArea(): boolean {
|
||||||
// Always show input area if waiting for user input
|
// Always show input area if waiting for user input
|
||||||
@@ -294,23 +292,23 @@ export class Panel {
|
|||||||
stepNumber: 0,
|
stepNumber: 0,
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
type: 'thinking',
|
type: 'thinking',
|
||||||
displayText: '等待任务开始...',
|
displayText: this.#pageAgent.i18n.t('ui.panel.waitingPlaceholder'),
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="${styles.header}">
|
<div class="${styles.header}">
|
||||||
<div class="${styles.statusSection}">
|
<div class="${styles.statusSection}">
|
||||||
<div class="${styles.indicator} ${styles.thinking}"></div>
|
<div class="${styles.indicator} ${styles.thinking}"></div>
|
||||||
<div class="${styles.statusText}">准备就绪</div>
|
<div class="${styles.statusText}">${this.#pageAgent.i18n.t('ui.panel.ready')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="${styles.controls}">
|
<div class="${styles.controls}">
|
||||||
<button class="${styles.controlButton} ${styles.expandButton}" title="展开历史">
|
<button class="${styles.controlButton} ${styles.expandButton}" title="${this.#pageAgent.i18n.t('ui.panel.expand')}">
|
||||||
▼
|
▼
|
||||||
</button>
|
</button>
|
||||||
<button class="${styles.controlButton} ${styles.pauseButton}" title="暂停">
|
<button class="${styles.controlButton} ${styles.pauseButton}" title="${this.#pageAgent.i18n.t('ui.panel.pause')}">
|
||||||
⏸︎
|
⏸︎
|
||||||
</button>
|
</button>
|
||||||
<button class="${styles.controlButton} ${styles.stopButton}" title="终止">
|
<button class="${styles.controlButton} ${styles.stopButton}" title="${this.#pageAgent.i18n.t('ui.panel.stop')}">
|
||||||
X
|
X
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -459,11 +457,12 @@ export class Panel {
|
|||||||
if (step.type === 'completed') {
|
if (step.type === 'completed') {
|
||||||
// Check if this is a result from done tool
|
// Check if this is a result from done tool
|
||||||
if (step.toolName === 'done') {
|
if (step.toolName === 'done') {
|
||||||
// @todo not right
|
|
||||||
// Judge success or failure based on result
|
// Judge success or failure based on result
|
||||||
|
const failureKeyword = this.#pageAgent.i18n.t('ui.tools.resultFailure')
|
||||||
|
const errorKeyword = this.#pageAgent.i18n.t('ui.tools.resultError')
|
||||||
const isSuccess =
|
const isSuccess =
|
||||||
!step.toolResult ||
|
!step.toolResult ||
|
||||||
(!step.toolResult.includes('失败') && !step.toolResult.includes('错误'))
|
(!step.toolResult.includes(failureKeyword) && !step.toolResult.includes(errorKeyword))
|
||||||
typeClass = isSuccess ? styles.doneSuccess : styles.doneError
|
typeClass = isSuccess ? styles.doneSuccess : styles.doneError
|
||||||
statusIcon = isSuccess ? '🎉' : '❌'
|
statusIcon = isSuccess ? '🎉' : '❌'
|
||||||
} else {
|
} else {
|
||||||
@@ -488,6 +487,13 @@ export class Panel {
|
|||||||
statusIcon = '🧠'
|
statusIcon = '🧠'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const durationText = step.duration ? ` · ${step.duration}ms` : ''
|
||||||
|
const stepLabel = this.#pageAgent.i18n.t('ui.panel.step', {
|
||||||
|
number: step.stepNumber.toString(),
|
||||||
|
time,
|
||||||
|
duration: durationText,
|
||||||
|
})
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="${styles.historyItem} ${typeClass}">
|
<div class="${styles.historyItem} ${typeClass}">
|
||||||
<div class="${styles.historyContent}">
|
<div class="${styles.historyContent}">
|
||||||
@@ -495,8 +501,7 @@ export class Panel {
|
|||||||
<span>${step.displayText}</span>
|
<span>${step.displayText}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="${styles.historyMeta}">
|
<div class="${styles.historyMeta}">
|
||||||
步骤 ${step.stepNumber} · ${time}
|
${stepLabel}
|
||||||
${step.duration ? ` · ${step.duration}ms` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@@ -504,7 +509,7 @@ export class Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取工具执行时的显示文本
|
* Get display text for tool execution
|
||||||
*/
|
*/
|
||||||
export function getToolExecutingText(toolName: string, args: any, i18n: I18n): string {
|
export function getToolExecutingText(toolName: string, args: any, i18n: I18n): string {
|
||||||
switch (toolName) {
|
switch (toolName) {
|
||||||
@@ -526,7 +531,7 @@ export function getToolExecutingText(toolName: string, args: any, i18n: I18n): s
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取工具完成时的显示文本
|
* Get display text for tool completion
|
||||||
*/
|
*/
|
||||||
export function getToolCompletedText(toolName: string, args: any, i18n: I18n): string | null {
|
export function getToolCompletedText(toolName: string, args: any, i18n: I18n): string | null {
|
||||||
switch (toolName) {
|
switch (toolName) {
|
||||||
|
|||||||
Reference in New Issue
Block a user