Skip to content
Merged
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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,28 @@ Write each change in both `### English` and `### 中文` under `## Unreleased`.

### English

- Paginate runtime logs on Logs the same way as request history, newest first
- Filter the Models catalog by provider and paginate the list
- Fix WorkBuddy Global model catalogs: use the Global host from account region, accept CLI agent names besides exact `cli`, and surface catalog errors instead of an empty list
- Replace Access playground account and model tiles with compact side-by-side dropdowns
- Shorten the add-account dialog into two steps: pick type and name first, then sign in; hide concurrency and priority behind advanced options, and switch the type picker to a dropdown when more than six providers are registered
- Set account name, max concurrency, priority, and WorkBuddy drop-system-prompt before login, and edit name, concurrency, and priority on account cards
- Force-refresh account quotas when the console refresh button is used, bypassing the Qoder 15s quota cache
- Keep the Accounts toolbar visible while refresh is loading and show card skeletons instead of stale quota meters
- Drop the colored left accent on account cards; status stays on the chip and runtime meter

### 中文

- 日志页运行日志支持分页,交互与请求历史一致,最新记录在前
- 模型目录可按供应商筛选,并支持分页
- 修复 WorkBuddy 国际版模型目录:按账号区域打到国际站,识别不止精确 `cli` 的 CLI agent,失败时返回明确错误而不再显示空列表
- Access 调试台的账号和模型选择改为并排下拉框,避免模型过多时撑开页面
- 添加账号改为两步:先选类型和名称,再登录;并发和优先级收进高级选项,供应商超过 6 个时改用下拉
- 添加账号时先设置名称、最大并发、优先级,以及 WorkBuddy 的丢弃系统提示词;卡片上也可改名称、并发和优先级
- 控制台点刷新时强制重新拉取账号额度,绕过 Qoder 15 秒额度缓存
- 账号页刷新时保留顶部操作栏,卡片区域显示骨架屏,不再把旧额度留在画面上
- 去掉账号卡片左侧的彩色状态条,状态只留在 Chip 和运行短柱上

## 0.2.14 - 2026-08-28

### English
Expand Down
13 changes: 8 additions & 5 deletions docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@

- 圆角 8px 或更小,除非现有组件已用更大值
- 账号/资源列表卡片优先使用浅边框和平面表面,不强制最小高度,不额外叠加强阴影
- 账号卡片保持操作台密度:单行身份(名称 + provider/auth/UID),状态 Chip 只出现一次,运行状态用 12 格短柱、额度用 `scaleX` 填充条;并发/优先级/重启收成一行辅助数字
- 账号卡片保持操作台密度:单行身份(名称可直接改 + provider/auth/UID),状态 Chip 只出现一次,运行状态用 12 格短柱、额度用 `scaleX` 填充条;并发和优先级可在卡片上改,重启次数仍是只读辅助数字
- 添加账号是两步向导:第一步选类型、名称和可选高级选项,第二步再选登录方式。类型 ≤ 6 用两列 tile,超过则用下拉;未知 provider 直接用后端 descriptor 的 label,不要静默丢掉
- 账号网格用 `lg:grid-cols-2 xl:grid-cols-3`,卡片间距 `gap-2.5`,内边距约 12px
- 主卡片只有在确实需要抬升层级时才用 `shadow-card`
- 边框克制使用;避免边框 + 阴影 + 有色背景同时出现
Expand Down Expand Up @@ -396,7 +397,9 @@ Each Qoder daemon exposes `GET /admin/quota` (console API key required). The dae
qodercli `qoderApi` singleton captured via the `quotaApi` needle in `worker/src/compat.mjs`,
which fetches `openapi:/api/v2/quota/usage` with a plain Bearer token — no WASM encode. The
CLI already caches this endpoint for 15s and de-dupes concurrent fetches, so the daemon does
not add its own cache.
not add its own cache. Console header refresh calls `GET /api/overview?refresh=1`, which
asks the daemon for `/admin/quota?refresh=1` and bypasses that 15s cache. Silent account
mutations still use the cached snapshot.

Go `refreshOne` fetches quota after health for hot/ready accounts and stores a
`QuotaSnapshot` on the pool item. Quota is display-only: fetch failures are swallowed and
Expand All @@ -419,8 +422,8 @@ Keep the menu short. Login is a gate, not a nav item.
| `/login` | no | Console password |
| `/` | Overview | Runtime pulse + request stats |
| `/accounts` | Accounts | Qoder login + pool |
| `/providers` | Models | Catalog + per-model context-window defaults |
| `/access` | Access | Base URL + quick chat; model tiles follow the selected account catalog |
| `/providers` | Models | Catalog + per-model context-window defaults; filter by provider and paginate |
| `/access` | Access | Base URL + quick chat; account and model dropdowns follow the selected account catalog |
| `/logs` | Logs | Request history + runtime process output |
| `/system` | System | Next-version update + SQLite protection |
| `/auth` | redirect | Legacy → `/accounts` |
Expand Down Expand Up @@ -448,7 +451,7 @@ Rules:
- `/api/logs/*` requires the SQLite API key.
- Request history list accepts `account`, `status`, `stream`, `error_kind`, `model`, `q`, `from`, `to`, `limit`, and `offset`. The console `/logs` page paginates this list and exposes those filters.
- `GET /api/logs/stats` aggregates counts, success rate, latency percentiles, tokens, error mix, and a time series for Overview. Windows are 1h / 24h / 7d; series buckets are 15 minutes, hourly, or daily.
- Runtime snapshot accepts `account` in addition to `level` and `q`.
- Runtime snapshot accepts `account`, `level`, `q`, `limit`, and `offset`. Results are newest-first. The console `/logs` runtime tab paginates this list; live polling stays on page 1.

## Managed update

Expand Down
4 changes: 4 additions & 0 deletions docs/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ Sub2API-style SQLite account registry while preserving the working Qoder executi
- [x] Add account CRUD endpoints
- [x] Add browser OAuth, PAT, native credential import/export endpoints
- [x] Add account creation and import UI using HeroUI only
- [x] Split add-account into type/name then login; hide concurrency and priority behind advanced options
- [x] Persist and edit account name, max inflight, priority, and WorkBuddy drop-system-prompt from the console
- [x] Filter the Models catalog by provider and paginate the list
- [x] Show UID, auth type, enabled state, runtime status, cooldown, and last error
- [x] Require the generated API key for every account and credential operation
- [x] Persist per-model context-window defaults and edit them from the Models page
Expand Down Expand Up @@ -159,6 +162,7 @@ output without copying sub2api billing, audit-body storage, or ops preaggregatio
- [x] Add console `/logs` with Request history and Runtime tabs
- [x] Purge request history at 7 days or 20_000 rows
- [x] Paginate request history and filter by time, account, model, stream, and error kind
- [x] Paginate runtime logs newest-first with `limit` / `offset`

## Phase K — account quota display

Expand Down
4 changes: 2 additions & 2 deletions docs/PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ WorkBuddy2API 把 **WorkBuddy CN / CodeBuddy**(`copilot.tencent.com` / `www.co
}
```

也兼容扁平 JSON。`domain` 含 `workbuddy.ai` 视为 global,否则 CN。
也兼容扁平 JSON。`domain` 含 `workbuddy.ai` 或 `workbuddy`(且不含 `codebuddy.cn`)视为 global,否则 CN。账号 `region=global` 时,catalog / chat / billing 即使凭证 domain 仍是 CN 也会打到 `www.workbuddy.ai`。

刷新:`POST {chatBase}/v2/plugin/auth/token/refresh`,头里带 `X-Refresh-Token`。chat 请求禁止带这个头。`12153` / `Offline user session not found` 视为 session 死亡,账号应禁用并要求重登。

Expand All @@ -99,7 +99,7 @@ WorkBuddy2API 把 **WorkBuddy CN / CodeBuddy**(`copilot.tencent.com` / `www.co

`GET {chatBase}/console/enterprises/personal/models`

只暴露 `agents[].name == "cli"` 的模型 ID,再和 `data.models` 交叉,跳过 `disabled`。字段用 `maxInputTokens` / `maxOutputTokens`,不是 `contextWindow`。
只暴露 CLI agent 的模型 ID(`cli` / `CLI` / `codebuddy` / `workbuddy`),再和 `data.models` 交叉,跳过 `disabled`。上游没有 agents 时,退回全部未禁用模型。字段用 `maxInputTokens` / `maxOutputTokens`,不是 `contextWindow`。按账号拉目录失败时,`GET /api/models?account=` 返回明确错误,不把空列表当成「没有模型」。

参考仓库失败时回退静态表。本仓库对 Qoder 的原则是 **catalog 失败就报错,不猜**。WorkBuddy 应沿用同一原则:动态目录失败返回明确错误,不内置一份会过期的模型白名单。

Expand Down
6 changes: 5 additions & 1 deletion frontend/src/api/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export type RuntimeLogEntry = {
export type RuntimeLogList = {
items: RuntimeLogEntry[]
count: number
total?: number
limit?: number
offset?: number
}

export type RequestStatsWindow = {
Expand Down Expand Up @@ -169,10 +172,11 @@ export function clearRequestLogs() {
return api<{ ok: boolean; deleted: number }>('/api/logs/requests', { method: 'DELETE' })
}

export function fetchRuntimeLogs(query: { after?: number; limit?: number; level?: string; q?: string; account?: string } = {}) {
export function fetchRuntimeLogs(query: { after?: number; limit?: number; offset?: number; level?: string; q?: string; account?: string } = {}) {
const params = new URLSearchParams()
if (query.after != null) params.set('after', String(query.after))
if (query.limit != null) params.set('limit', String(query.limit))
if (query.offset != null) params.set('offset', String(query.offset))
if (query.level) params.set('level', query.level)
if (query.q) params.set('q', query.q)
if (query.account) params.set('account', query.account)
Expand Down
25 changes: 21 additions & 4 deletions frontend/src/api/overview.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { api } from './client'
import type { Overview } from './types'

export function fetchOverview(keyOverride?: string) {
return api<Overview>('/api/overview', {}, keyOverride)
export function fetchOverview(keyOverride?: string, options?: { refreshQuota?: boolean }) {
const path = options?.refreshQuota ? '/api/overview?refresh=1' : '/api/overview'
return api<Overview>(path, {}, keyOverride)
}

export function startDeviceLogin(accountId?: string) {
Expand Down Expand Up @@ -87,8 +88,24 @@ export function fetchProviders() {
return api<{ data?: ProviderDescriptor[] }>('/api/providers')
}

export function createAccount(name: string, provider = 'qoder', region = 'global') {
return api('/api/accounts', { method: 'POST', body: JSON.stringify({ name, provider, region, enabled: true, max_inflight: 4 }) })
export function createAccount(
name: string,
provider = 'qoder',
region = 'global',
options?: { max_inflight?: number; priority?: number; drop_system_prompt?: boolean },
) {
return api('/api/accounts', {
method: 'POST',
body: JSON.stringify({
name,
provider,
region,
enabled: true,
max_inflight: options?.max_inflight ?? 4,
priority: options?.priority ?? 50,
drop_system_prompt: options?.drop_system_prompt,
}),
})
}

export function updateAccount(accountId: string, input: Record<string, unknown>) {
Expand Down
Loading
Loading