refactor: add shared types and improve ChatStatusBar with i18n

- Add electron/shared/types.ts as single source of truth for
  TokenUsage and AgentRuntimeStatus across main/preload/renderer
- Refactor ChatStatusBar.vue to use shared types and i18n keys
  instead of hardcoded phase text mappings
- Add phaseShort translations for zh-CN and en-US
This commit is contained in:
digua
2026-02-26 21:06:03 +08:00
parent e6849af698
commit 1b059a52cc
4 changed files with 50 additions and 72 deletions
+22
View File
@@ -0,0 +1,22 @@
/**
* Electron 主进程 / Preload / 渲染进程共享的 Agent 类型定义
*
* 此文件是 AgentRuntimeStatus、TokenUsage 等跨进程类型的唯一定义源。
* 所有使用方应从此处导入,避免重复定义导致类型漂移。
*/
export interface TokenUsage {
promptTokens: number
completionTokens: number
totalTokens: number
}
export interface AgentRuntimeStatus {
phase: 'preparing' | 'thinking' | 'tool_running' | 'responding' | 'completed' | 'aborted' | 'error'
round: number
toolsUsed: number
currentTool?: string
contextTokens: number
totalUsage: TokenUsage
updatedAt: number
}
@@ -6,32 +6,16 @@ import { useToast } from '@nuxt/ui/runtime/composables/useToast.js'
import { usePromptStore } from '@/stores/prompt' import { usePromptStore } from '@/stores/prompt'
import { useLayoutStore } from '@/stores/layout' import { useLayoutStore } from '@/stores/layout'
import { useLLMStore } from '@/stores/llm' import { useLLMStore } from '@/stores/llm'
import type { AgentRuntimeStatus } from '@electron/shared/types'
const { t, locale } = useI18n() const { t, locale } = useI18n()
const toast = useToast() const toast = useToast()
interface AgentRuntimeStatusView {
phase: 'preparing' | 'thinking' | 'tool_running' | 'responding' | 'completed' | 'aborted' | 'error'
round: number
toolsUsed: number
currentTool?: string
contextTokens: number
contextWindow: number
contextUsage: number
totalUsage: { promptTokens: number; completionTokens: number; totalTokens: number }
nodeCount?: number
tagCount?: number
segmentSize?: number
checkoutCount?: number
activeAnchorNodeId?: string | null
updatedAt: number
}
// Props // Props
const props = defineProps<{ const props = defineProps<{
chatType: 'group' | 'private' chatType: 'group' | 'private'
sessionTokenUsage: { totalTokens: number } sessionTokenUsage: { totalTokens: number }
agentStatus?: AgentRuntimeStatusView | null agentStatus?: AgentRuntimeStatus | null
hasLLMConfig: boolean hasLLMConfig: boolean
isCheckingConfig: boolean isCheckingConfig: boolean
}>() }>()
@@ -67,29 +51,7 @@ const agentPhaseText = computed(() => {
const agentPhaseShortText = computed(() => { const agentPhaseShortText = computed(() => {
if (!props.agentStatus) return '' if (!props.agentStatus) return ''
if (locale.value.startsWith('zh')) { return t(`ai.chat.statusBar.agent.phaseShort.${props.agentStatus.phase}`)
const map: Record<AgentRuntimeStatusView['phase'], string> = {
preparing: '准备',
thinking: '思考',
tool_running: '工具',
responding: '生成',
completed: '完成',
aborted: '停止',
error: '错误',
}
return map[props.agentStatus.phase]
}
const map: Record<AgentRuntimeStatusView['phase'], string> = {
preparing: 'Prep',
thinking: 'Think',
tool_running: 'Tool',
responding: 'Reply',
completed: 'Done',
aborted: 'Stop',
error: 'Err',
}
return map[props.agentStatus.phase]
}) })
const agentPhaseClass = computed(() => { const agentPhaseClass = computed(() => {
@@ -113,32 +75,14 @@ const agentPhaseClass = computed(() => {
} }
}) })
const contextUsagePercent = computed(() => {
if (!props.agentStatus) return 0
return Math.round((props.agentStatus.contextUsage || 0) * 100)
})
const contextUsageWidth = computed(() => `${Math.max(0, Math.min(100, contextUsagePercent.value))}%`)
function formatNumber(value: number): string { function formatNumber(value: number): string {
if (!Number.isFinite(value)) return '0' if (!Number.isFinite(value)) return '0'
return new Intl.NumberFormat().format(Math.max(0, Math.round(value))) return new Intl.NumberFormat().format(Math.max(0, Math.round(value)))
} }
const contextUsageText = computed(() => { const contextTokensText = computed(() => {
if (!props.agentStatus) return '--' if (!props.agentStatus) return '--'
return t('ai.chat.statusBar.agent.contextUsage', { return formatCompactNumber(props.agentStatus.contextTokens)
used: formatNumber(props.agentStatus.contextTokens),
total: formatNumber(props.agentStatus.contextWindow),
percent: contextUsagePercent.value,
})
})
const agentStatsText = computed(() => {
if (!props.agentStatus) return ''
const segment = props.agentStatus.segmentSize ?? 0
const tags = props.agentStatus.tagCount ?? 0
return t('ai.chat.statusBar.agent.stats', { segment, tags })
}) })
function formatCompactNumber(value: number): string { function formatCompactNumber(value: number): string {
@@ -155,8 +99,7 @@ const agentCompactTitle = computed(() => {
if (!props.agentStatus) return '' if (!props.agentStatus) return ''
return [ return [
`${t('ai.chat.statusBar.agent.label')}: ${agentPhaseText.value}`, `${t('ai.chat.statusBar.agent.label')}: ${agentPhaseText.value}`,
contextUsageText.value, `${t('ai.chat.statusBar.agent.contextTokens')}: ${formatNumber(props.agentStatus.contextTokens)}`,
agentStatsText.value,
`${t('ai.chat.statusBar.tokenUsageTitle')}: ${totalTokenUsageText.value}`, `${t('ai.chat.statusBar.tokenUsageTitle')}: ${totalTokenUsageText.value}`,
].join('\n') ].join('\n')
}) })
@@ -359,13 +302,8 @@ async function openAiLogFile() {
<span class="rounded px-1 py-0.5 text-[10px] font-medium" :class="agentPhaseClass"> <span class="rounded px-1 py-0.5 text-[10px] font-medium" :class="agentPhaseClass">
{{ agentPhaseShortText }} {{ agentPhaseShortText }}
</span> </span>
<div class="h-1.5 w-10 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700"> <UIcon name="i-heroicons-document-text" class="h-3 w-3 text-gray-400 dark:text-gray-500" />
<div <span class="text-[10px] text-gray-500 dark:text-gray-400">{{ contextTokensText }}</span>
class="h-full rounded-full bg-pink-500 transition-all duration-300"
:style="{ width: contextUsageWidth }"
/>
</div>
<span class="text-[10px] text-gray-500 dark:text-gray-400">{{ contextUsagePercent }}%</span>
</div> </div>
<div <div
+10 -1
View File
@@ -88,7 +88,7 @@
}, },
"agent": { "agent": {
"label": "Agent", "label": "Agent",
"contextUsage": "{used}/{total} ({percent}%)", "contextTokens": "Context",
"stats": "seg {segment} · tags {tags}", "stats": "seg {segment} · tags {tags}",
"phase": { "phase": {
"preparing": "Preparing", "preparing": "Preparing",
@@ -98,6 +98,15 @@
"completed": "Completed", "completed": "Completed",
"aborted": "Stopped", "aborted": "Stopped",
"error": "Error" "error": "Error"
},
"phaseShort": {
"preparing": "Prep",
"thinking": "Think",
"tool_running": "Tool",
"responding": "Reply",
"completed": "Done",
"aborted": "Stop",
"error": "Err"
} }
}, },
"tokenUsageTitle": "Total token usage in this session", "tokenUsageTitle": "Total token usage in this session",
+10 -1
View File
@@ -88,7 +88,7 @@
}, },
"agent": { "agent": {
"label": "Agent", "label": "Agent",
"contextUsage": "{used}/{total} ({percent}%)", "contextTokens": "上下文",
"stats": "段 {segment} · 标签 {tags}", "stats": "段 {segment} · 标签 {tags}",
"phase": { "phase": {
"preparing": "准备中", "preparing": "准备中",
@@ -98,6 +98,15 @@
"completed": "已完成", "completed": "已完成",
"aborted": "已停止", "aborted": "已停止",
"error": "错误" "error": "错误"
},
"phaseShort": {
"preparing": "准备",
"thinking": "思考",
"tool_running": "工具",
"responding": "生成",
"completed": "完成",
"aborted": "停止",
"error": "错误"
} }
}, },
"tokenUsageTitle": "本次会话累计 Token 使用量", "tokenUsageTitle": "本次会话累计 Token 使用量",