Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
- **Live 与 Robotics**: 通过 WebSocket 支持文本、音频、JPEG、媒体结束、工具调用、恢复和中断
- **反指纹检测**: 为每个账户固定 Firefox 指纹与出口,官方 WAA 在 SpiderMonkey 的 Firefox 页面环境或 Camoufox 页面中运行
- **图形界面启动器**: 通过网页管理账户、服务启停、实时日志、模型、请求、用量统计和配置,界面提供深色与浅色两种配色,以及简体中文、繁體中文、English、日本語、한국어、Français 与 Deutsch 七种语言
- **服务总览**: 首页汇总账户健康、模型调用、冷却、最近请求和错误,按太平洋时区展示当天各账户的请求与 Token 用量;账户状态筛选显示数量
- **桌面版**: Windows、macOS 与 Linux 桌面应用在窗口中显示管理页面,关闭窗口后在系统托盘继续运行
- **在线更新**: 管理端“版本”页显示最新发行版本与更新说明,桌面版与 Linux、macOS 命令行版一键下载、校验并重启到新版本,Linux deb 安装时由系统弹出管理员认证
- **模块化架构**: Go 负责协议、调度、API 与管理端,SpiderMonkey 或 Camoufox 负责 WAA 运行时,Camoufox 负责隔离登录
Expand Down
1 change: 1 addition & 0 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ The three projects connect to AI Studio, the Gemini web app and Antigravity, and
- **Live and Robotics**: WebSocket text, audio, JPEG images, media end, tool calls, resumption, and interruption
- **Anti-Fingerprinting**: Each account keeps a stable Firefox fingerprint and network exit, and the official WAA runs in the Firefox page environment of SpiderMonkey or in a Camoufox page
- **GUI Launcher**: Manage accounts, service controls, live logs, models, requests, usage statistics, and configuration in the web UI, with dark and light color themes and seven languages: 简体中文, 繁體中文, English, 日本語, 한국어, Français, and Deutsch
- **Service Overview**: The landing page summarizes account health, model traffic, cooldowns, recent requests, and errors, with daily per-account request and token usage in Pacific time; account-state filters show counts
- **Desktop App**: Windows, macOS, and Linux desktop apps show the management UI in a window and keep running in the system tray after the window is closed
- **Online Update**: The Version page shows the latest release and its notes; the desktop apps and the Linux and macOS command-line builds download, verify, and restart into the new version in one click, and a Linux deb install asks for administrator authentication
- **Modular Architecture**: Go handles protocols, scheduling, APIs, and management; SpiderMonkey or Camoufox hosts WAA, and Camoufox hosts isolated login
Expand Down
Binary file added docs/images/overview-dark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 28 additions & 4 deletions web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ function lazyPanel(loader: AsyncComponentLoader) {
const UsagePanel = lazyPanel(() => import('@/components/UsagePanel.vue'))
const VersionPanel = lazyPanel(() => import('@/components/VersionPanel.vue'))
const AccessPanel = lazyPanel(() => import('@/components/AccessPanel.vue'))
const OverviewPanel = lazyPanel(() => import('@/components/OverviewPanel.vue'))
const validTabs: TabID[] = [
'overview',
'logs',
'accounts',
'access',
Expand All @@ -57,7 +59,7 @@ const validTabs: TabID[] = [
'settings',
'version',
]
const { tab: currentTab, select: selectTab } = useTabParam(validTabs, 'logs')
const { tab: currentTab, select: selectTab } = useTabParam(validTabs, 'overview')
const router = useRouter()
const route = useRoute()
// currentTab 在从试用页返回时同步目标管理页面
Expand All @@ -66,7 +68,7 @@ watch(
() => {
if (route.name === 'console') {
const tab = route.query.tab as TabID
currentTab.value = validTabs.includes(tab) ? tab : 'logs'
currentTab.value = validTabs.includes(tab) ? tab : 'overview'
}
},
)
Expand Down Expand Up @@ -185,7 +187,15 @@ watch(
{ flush: 'post' },
)

// refreshOverview 同时刷新总览使用的账户、模型、请求与冷却数据
const overviewRefreshKey = ref(0)
async function refreshOverview(): Promise<void> {
await Promise.all([loadAccountData(), loadRequestData()])
overviewRefreshKey.value++
}

const navigation: { id: TabID; label: TranslationKey; icon: IconName }[] = [
{ id: 'overview', label: 'nav.overview', icon: 'overview' },
{ id: 'logs', label: 'nav.logs', icon: 'logs' },
{ id: 'accounts', label: 'nav.accounts', icon: 'accounts' },
{ id: 'access', label: 'nav.access', icon: 'access' },
Expand Down Expand Up @@ -431,9 +441,23 @@ onMounted(revealCurrentTab)
</p>
</div>
<KeepAlive
include="LogsPanel,AccountsPanel,AccessPanel,ModelsTable,RequestsPanel,SettingsPanel,VersionPanel,UsagePanel"
include="OverviewPanel,LogsPanel,AccountsPanel,AccessPanel,ModelsTable,RequestsPanel,SettingsPanel,VersionPanel,UsagePanel"
>
<LogsPanel v-if="currentTab === 'logs'" :logs="logs" @clear="clearLogs" />
<OverviewPanel
v-if="currentTab === 'overview'"
:status="status"
:service-state="serviceState"
:accounts="accounts"
:models="models"
:cooldowns="cooldowns"
:requests="requests"
:refresh-key="overviewRefreshKey"
@refresh="refreshOverview"
@select="openTab"
@start="startService"
@stop="stopService"
/>
<LogsPanel v-else-if="currentTab === 'logs'" :logs="logs" @clear="clearLogs" />
<AccountsPanel
v-else-if="currentTab === 'accounts'"
:accounts="visibleAccounts"
Expand Down
37 changes: 29 additions & 8 deletions web/src/components/AccountsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ const selectedState = ref('')
const selectedTier = ref('')
const selectedEgress = ref('*')
const states = computed(() => [...new Set(props.accounts.map((account) => account.state))])
// stateCounts 是每种账户状态的数量,显示在状态筛选按钮上
const stateCounts = computed(() => {
const counts = new Map<AccountState, number>()
for (const account of props.accounts)
counts.set(account.state, (counts.get(account.state) ?? 0) + 1)
return counts
})
const tiers = computed(() => [...new Set(props.accounts.map((account) => account.benefit_tier))])
const egresses = computed(() => [...new Set(props.accounts.map((account) => account.egress))])

Expand Down Expand Up @@ -1051,16 +1058,30 @@ async function refreshAllAccounts(): Promise<void> {
:placeholder="t('accounts.search')"
class="min-w-0 flex-[1_1_12rem] rounded-md border border-line bg-canvas px-3 py-1.5 text-sm text-white focus:border-blue-500 focus:outline-none"
/>
<UiSelect
v-model="selectedState"
<div
class="flex flex-wrap items-center gap-1"
role="group"
:aria-label="t('accounts.filterState')"
class="rounded-md border border-line bg-canvas px-2 py-1.5 text-xs text-gray-200"
>
<option value="">{{ t('accounts.allStates') }}</option>
<option v-for="state in states" :key="state" :value="state">
{{ t(stateKeys[state]) }}
</option>
</UiSelect>
<button
v-for="state in ['', ...states] as ('' | AccountState)[]"
:key="state"
type="button"
:aria-pressed="selectedState === state"
class="flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition"
:class="
selectedState === state
? 'border-blue-500 bg-blue-600/15 text-blue-300'
: 'border-line text-gray-400 hover:text-white'
"
@click="selectedState = state"
>
{{ state === '' ? t('accounts.allStates') : t(stateKeys[state]) }}
<span class="text-gray-500 tabular-nums">{{
state === '' ? accounts.length : stateCounts.get(state)
}}</span>
</button>
</div>
<UiSelect
v-model="selectedTier"
:aria-label="t('accounts.filterTier')"
Expand Down
Loading