feat(usage): 在用量查询页面添加服务端时间显示

- 在 UI 中添加服务端时间显示组件,包含样式和国际化支持
- 修改用量 API 返回数据,始终包含 serverTime 字段
- 更新前端 JavaScript 以解析并显示服务端时间
- 新增 OpenClaw 配置指南文档(中/英/日文)
This commit is contained in:
hex2077 2026-02-01 22:43:28 +08:00
parent d9f3a8002f
commit d6c2bd7919
11 changed files with 692 additions and 6 deletions

213
OPENCLAW_CONFIG_GUIDE-JA.md Normal file
View file

@ -0,0 +1,213 @@
# OpenClaw 設定ガイド
OpenClaw で AIClient-2-API を使用するためのクイック設定ガイド。
---
## 前提条件
1. AIClient-2-API サービスを起動
2. Web UI (`http://localhost:3000`) で少なくとも1つのプロバイダーを設定
3. 設定ファイルから API Key を記録
4. OpenClaw をインストール
- Docker バージョン:[justlikemaki/openclaw-docker-cn-im](https://hub.docker.com/r/justlikemaki/openclaw-docker-cn-im)
- または他のインストール方法を使用
---
## 設定方法
### 方法1OpenAI プロトコル(推奨)
**使用例**Gemini モデルを使用する場合
```json5
{
env: {
AICLIENT2API_KEY: "your-api-key"
},
agents: {
defaults: {
model: { primary: "aiclient2api/gemini-3-flash-preview" },
models: {
"aiclient2api/gemini-3-flash-preview": { alias: "Gemini 3 Flash" }
}
}
},
models: {
mode: "merge",
providers: {
aiclient2api: {
baseUrl: "http://localhost:3000/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [
{
id: "gemini-3-flash-preview",
name: "Gemini 3 Flash Preview",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1000000,
maxTokens: 8192
}
]
}
}
}
}
```
### 方法2Claude プロトコル
**使用例**Prompt Caching などの機能を持つ Claude モデルを使用する場合
```json5
{
env: {
AICLIENT2API_KEY: "your-api-key"
},
agents: {
defaults: {
model: { primary: "aiclient2api/claude-sonnet-4-5" },
models: {
"aiclient2api/claude-sonnet-4-5": { alias: "Claude Sonnet 4.5" }
}
}
},
models: {
mode: "merge",
providers: {
aiclient2api: {
baseUrl: "http://localhost:3000",
apiKey: "${AICLIENT2API_KEY}",
api: "anthropic-messages",
models: [
{
id: "claude-sonnet-4-5",
name: "Claude Sonnet 4.5",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 8192
}
]
}
}
}
}
```
---
## プロバイダーの指定(オプション)
ルーティングパラメータで特定のプロバイダーを指定:
```json5
{
models: {
providers: {
// Kiro ClaudeOpenAI プロトコル)
"aiclient2api-kiro": {
baseUrl: "http://localhost:3000/claude-kiro-oauth/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [...]
},
// Kiro ClaudeClaude プロトコル)
"aiclient2api-kiro-claude": {
baseUrl: "http://localhost:3000/claude-kiro-oauth",
apiKey: "${AICLIENT2API_KEY}",
api: "anthropic-messages",
models: [...]
},
// Gemini CLIOpenAI プロトコル)
"aiclient2api-gemini": {
baseUrl: "http://localhost:3000/gemini-cli-oauth/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [...]
},
// AntigravityOpenAI プロトコル)
"aiclient2api-antigravity": {
baseUrl: "http://localhost:3000/gemini-antigravity/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [...]
}
}
}
}
```
---
## フォールバックの設定
```json5
{
agents: {
defaults: {
model: {
primary: "aiclient2api/claude-sonnet-4-5",
fallbacks: [
"aiclient2api/gemini-3-flash-preview"
]
}
}
}
}
```
---
## よく使うコマンド
```bash
# すべてのモデルをリスト表示
openclaw models list
# モデルを切り替え
openclaw models set aiclient2api/claude-sonnet-4-5
# 特定のモデルでチャット
openclaw chat --model aiclient2api/gemini-3-flash-preview "あなたの質問"
```
---
## プロトコル比較
| 機能 | OpenAI プロトコル | Claude プロトコル |
|------|------------------|------------------|
| Base URL | `http://localhost:3000/v1` | `http://localhost:3000` |
| API タイプ | `openai-completions` | `anthropic-messages` |
| サポートモデル | すべてのモデル | Claude のみ |
| 特殊機能 | - | Prompt Caching、Extended Thinking |
---
## よくある質問
**Q: 接続に失敗しますか?**
- AIClient-2-API サービスが実行中であることを確認
- Base URL が正しいか確認OpenAI プロトコルには `/v1` サフィックスが必要)
- `localhost` の代わりに `127.0.0.1` を使用してみる
**Q: 401 エラー?**
- API Key が正しく設定されているか確認
- 環境変数 `AICLIENT2API_KEY` が設定されているか確認
**Q: モデルが利用できない?**
- AIClient-2-API Web UI でプロバイダーが設定されているか確認
- `openclaw gateway restart` を実行してゲートウェイを再起動
- `openclaw models list` を実行してモデルリストを確認
---
詳細については、[AIClient-2-API ドキュメント](./README-JA.md) を参照してください

213
OPENCLAW_CONFIG_GUIDE-ZH.md Normal file
View file

@ -0,0 +1,213 @@
# OpenClaw 配置指南
在 OpenClaw 中使用 AIClient-2-API 的快速配置指南。
---
## 前置准备
1. 启动 AIClient-2-API 服务
2. 在 Web UI (`http://localhost:3000`) 配置至少一个提供商
3. 记录配置文件中的 API Key
4. 安装 OpenClaw
- Docker 版本:[justlikemaki/openclaw-docker-cn-im](https://hub.docker.com/r/justlikemaki/openclaw-docker-cn-im)
- 或使用其他安装方式
---
## 配置方式
### 方式一OpenAI 协议(推荐)
**适用场景**:使用 Gemini 模型
```json5
{
env: {
AICLIENT2API_KEY: "your-api-key"
},
agents: {
defaults: {
model: { primary: "aiclient2api/gemini-3-flash-preview" },
models: {
"aiclient2api/gemini-3-flash-preview": { alias: "Gemini 3 Flash" }
}
}
},
models: {
mode: "merge",
providers: {
aiclient2api: {
baseUrl: "http://localhost:3000/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [
{
id: "gemini-3-flash-preview",
name: "Gemini 3 Flash Preview",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1000000,
maxTokens: 8192
}
]
}
}
}
}
```
### 方式二Claude 协议
**适用场景**:使用 Claude 模型,需要 Prompt Caching 等特性
```json5
{
env: {
AICLIENT2API_KEY: "your-api-key"
},
agents: {
defaults: {
model: { primary: "aiclient2api/claude-sonnet-4-5" },
models: {
"aiclient2api/claude-sonnet-4-5": { alias: "Claude Sonnet 4.5" }
}
}
},
models: {
mode: "merge",
providers: {
aiclient2api: {
baseUrl: "http://localhost:3000",
apiKey: "${AICLIENT2API_KEY}",
api: "anthropic-messages",
models: [
{
id: "claude-sonnet-4-5",
name: "Claude Sonnet 4.5",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 8192
}
]
}
}
}
}
```
---
## 指定提供商(可选)
通过路由参数指定特定提供商:
```json5
{
models: {
providers: {
// Kiro 提供的 Claude (OpenAI 协议)
"aiclient2api-kiro": {
baseUrl: "http://localhost:3000/claude-kiro-oauth/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [...]
},
// Kiro 提供的 Claude (Claude 协议)
"aiclient2api-kiro-claude": {
baseUrl: "http://localhost:3000/claude-kiro-oauth",
apiKey: "${AICLIENT2API_KEY}",
api: "anthropic-messages",
models: [...]
},
// Gemini CLI (OpenAI 协议)
"aiclient2api-gemini": {
baseUrl: "http://localhost:3000/gemini-cli-oauth/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [...]
},
// Antigravity (OpenAI 协议)
"aiclient2api-antigravity": {
baseUrl: "http://localhost:3000/gemini-antigravity/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [...]
}
}
}
}
```
---
## 配置 Fallback
```json5
{
agents: {
defaults: {
model: {
primary: "aiclient2api/claude-sonnet-4-5",
fallbacks: [
"aiclient2api/gemini-3-flash-preview"
]
}
}
}
}
```
---
## 常用命令
```bash
# 列出所有模型
openclaw models list
# 切换模型
openclaw models set aiclient2api/claude-sonnet-4-5
# 使用指定模型对话
openclaw chat --model aiclient2api/gemini-3-flash-preview "你的问题"
```
---
## 协议对比
| 特性 | OpenAI 协议 | Claude 协议 |
|------|------------|------------|
| Base URL | `http://localhost:3000/v1` | `http://localhost:3000` |
| API 类型 | `openai-completions` | `anthropic-messages` |
| 支持模型 | 所有模型 | 仅 Claude |
| 特殊特性 | - | Prompt Caching、Extended Thinking |
---
## 常见问题
**Q: 连接失败?**
- 确认 AIClient-2-API 服务运行中
- 检查 Base URL 是否正确OpenAI 协议需要 `/v1` 后缀)
- 尝试使用 `127.0.0.1` 替代 `localhost`
**Q: 401 错误?**
- 检查 API Key 是否正确配置
- 确认环境变量 `AICLIENT2API_KEY` 已设置
**Q: 模型不可用?**
- 在 AIClient-2-API Web UI 确认已配置对应提供商
- 运行 `openclaw gateway restart` 重启网关
- 运行 `openclaw models list` 验证模型列表
---
更多信息请参考 [AIClient-2-API 文档](../README-ZH.md)

213
OPENCLAW_CONFIG_GUIDE.md Normal file
View file

@ -0,0 +1,213 @@
# OpenClaw Configuration Guide
Quick configuration guide for using AIClient-2-API with OpenClaw.
---
## Prerequisites
1. Start AIClient-2-API service
2. Configure at least one provider in Web UI (`http://localhost:3000`)
3. Note the API Key from configuration file
4. Install OpenClaw
- Docker version: [justlikemaki/openclaw-docker-cn-im](https://hub.docker.com/r/justlikemaki/openclaw-docker-cn-im)
- Or use other installation methods
---
## Configuration Methods
### Method 1: OpenAI Protocol (Recommended)
**Use Case**: For Gemini models
```json5
{
env: {
AICLIENT2API_KEY: "your-api-key"
},
agents: {
defaults: {
model: { primary: "aiclient2api/gemini-3-flash-preview" },
models: {
"aiclient2api/gemini-3-flash-preview": { alias: "Gemini 3 Flash" }
}
}
},
models: {
mode: "merge",
providers: {
aiclient2api: {
baseUrl: "http://localhost:3000/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [
{
id: "gemini-3-flash-preview",
name: "Gemini 3 Flash Preview",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1000000,
maxTokens: 8192
}
]
}
}
}
}
```
### Method 2: Claude Protocol
**Use Case**: For Claude models with features like Prompt Caching
```json5
{
env: {
AICLIENT2API_KEY: "your-api-key"
},
agents: {
defaults: {
model: { primary: "aiclient2api/claude-sonnet-4-5" },
models: {
"aiclient2api/claude-sonnet-4-5": { alias: "Claude Sonnet 4.5" }
}
}
},
models: {
mode: "merge",
providers: {
aiclient2api: {
baseUrl: "http://localhost:3000",
apiKey: "${AICLIENT2API_KEY}",
api: "anthropic-messages",
models: [
{
id: "claude-sonnet-4-5",
name: "Claude Sonnet 4.5",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 8192
}
]
}
}
}
}
```
---
## Specify Provider (Optional)
Specify a specific provider via routing parameters:
```json5
{
models: {
providers: {
// Kiro Claude (OpenAI Protocol)
"aiclient2api-kiro": {
baseUrl: "http://localhost:3000/claude-kiro-oauth/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [...]
},
// Kiro Claude (Claude Protocol)
"aiclient2api-kiro-claude": {
baseUrl: "http://localhost:3000/claude-kiro-oauth",
apiKey: "${AICLIENT2API_KEY}",
api: "anthropic-messages",
models: [...]
},
// Gemini CLI (OpenAI Protocol)
"aiclient2api-gemini": {
baseUrl: "http://localhost:3000/gemini-cli-oauth/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [...]
},
// Antigravity (OpenAI Protocol)
"aiclient2api-antigravity": {
baseUrl: "http://localhost:3000/gemini-antigravity/v1",
apiKey: "${AICLIENT2API_KEY}",
api: "openai-completions",
models: [...]
}
}
}
}
```
---
## Configure Fallback
```json5
{
agents: {
defaults: {
model: {
primary: "aiclient2api/claude-sonnet-4-5",
fallbacks: [
"aiclient2api/gemini-3-flash-preview"
]
}
}
}
}
```
---
## Common Commands
```bash
# List all models
openclaw models list
# Switch model
openclaw models set aiclient2api/claude-sonnet-4-5
# Chat with specific model
openclaw chat --model aiclient2api/gemini-3-flash-preview "your question"
```
---
## Protocol Comparison
| Feature | OpenAI Protocol | Claude Protocol |
|---------|----------------|-----------------|
| Base URL | `http://localhost:3000/v1` | `http://localhost:3000` |
| API Type | `openai-completions` | `anthropic-messages` |
| Supported Models | All models | Claude only |
| Special Features | - | Prompt Caching, Extended Thinking |
---
## FAQ
**Q: Connection failed?**
- Confirm AIClient-2-API service is running
- Check if Base URL is correct (OpenAI protocol needs `/v1` suffix)
- Try using `127.0.0.1` instead of `localhost`
**Q: 401 error?**
- Check if API Key is correctly configured
- Confirm environment variable `AICLIENT2API_KEY` is set
**Q: Model unavailable?**
- Confirm provider is configured in AIClient-2-API Web UI
- Run `openclaw gateway restart` to restart gateway
- Run `openclaw models list` to verify model list
---
For more information, see [AIClient-2-API Documentation](./README.md)

View file

@ -18,7 +18,7 @@
[![GitHub stars](https://img.shields.io/github/stars/justlovemaki/AIClient-2-API.svg?style=flat&label=Star)](https://github.com/justlovemaki/AIClient-2-API/stargazers)
[![GitHub issues](https://img.shields.io/github/issues/justlovemaki/AIClient-2-API.svg)](https://github.com/justlovemaki/AIClient-2-API/issues)
[中文](./README-ZH.md) | [English](./README.md) | [**👉 日本語**](./README-JA.md) | [**📚 完全ドキュメント**](https://aiproxy.justlikemaki.vip/ja/)
[**🔧 OpenClaw 設定**](./OPENCLAW_CONFIG_GUIDE-JA.md) | [中文](./README-ZH.md) | [English](./README.md) | [**👉 日本語**](./README-JA.md) | [**📚 完全ドキュメント**](https://aiproxy.justlikemaki.vip/ja/)
</div>

View file

@ -18,7 +18,7 @@
[![GitHub stars](https://img.shields.io/github/stars/justlovemaki/AIClient-2-API.svg?style=flat&label=Star)](https://github.com/justlovemaki/AIClient-2-API/stargazers)
[![GitHub issues](https://img.shields.io/github/issues/justlovemaki/AIClient-2-API.svg)](https://github.com/justlovemaki/AIClient-2-API/issues)
[**👉 中文**](./README-ZH.md) | [English](./README.md) | [日本語](./README-JA.md) | [**📚 完整文档**](https://aiproxy.justlikemaki.vip/zh/)
[**🔧 OpenClaw 配置**](./OPENCLAW_CONFIG_GUIDE-ZH.md) | [**👉 中文**](./README-ZH.md) | [English](./README.md) | [日本語](./README-JA.md) | [**📚 完整文档**](https://aiproxy.justlikemaki.vip/zh/)
</div>

View file

@ -18,7 +18,7 @@
[![GitHub stars](https://img.shields.io/github/stars/justlovemaki/AIClient-2-API.svg?style=flat&label=Star)](https://github.com/justlovemaki/AIClient-2-API/stargazers)
[![GitHub issues](https://img.shields.io/github/issues/justlovemaki/AIClient-2-API.svg)](https://github.com/justlovemaki/AIClient-2-API/issues)
[中文](./README-ZH.md) | [**👉 English**](./README.md) | [日本語](./README-JA.md) | [**📚 Documentation**](https://aiproxy.justlikemaki.vip/en/)
[**🔧 OpenClaw Config**](./OPENCLAW_CONFIG_GUIDE.md) | [中文](./README-ZH.md) | [**👉 English**](./README.md) | [日本語](./README-JA.md) | [**📚 Documentation**](https://aiproxy.justlikemaki.vip/en/)
</div>

View file

@ -269,8 +269,14 @@ export async function handleGetUsage(req, res, currentConfig, providerPoolManage
await writeUsageCache(usageResults);
}
// Always include current server time
const finalResults = {
...usageResults,
serverTime: new Date().toISOString()
};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(usageResults));
res.end(JSON.stringify(finalResults));
return true;
} catch (error) {
logger.error('[UI API] Failed to get usage:', error);
@ -300,7 +306,7 @@ export async function handleGetProviderUsage(req, res, currentConfig, providerPo
const cachedData = await readProviderUsageCache(providerType);
if (cachedData) {
logger.info(`[Usage API] Returning cached usage data for ${providerType}`);
usageResults = cachedData;
usageResults = { ...cachedData, fromCache: true };
}
}
@ -312,8 +318,14 @@ export async function handleGetProviderUsage(req, res, currentConfig, providerPo
await updateProviderUsageCache(providerType, usageResults);
}
// Always include current server time
const finalResults = {
...usageResults,
serverTime: new Date().toISOString()
};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(usageResults));
res.end(JSON.stringify(finalResults));
return true;
} catch (error) {
logger.error(`[UI API] Failed to get usage for ${providerType}:`, error);

View file

@ -515,6 +515,7 @@ const translations = {
// Usage
'usage.title': '用量查询',
'usage.refresh': '刷新用量',
'usage.serverTime': '服务端时间',
'usage.lastUpdate': '上次更新: {time}',
'usage.lastUpdateCache': '缓存时间: {time}',
'usage.supportedProvidersPrefix': '支持用量查询的提供商:',
@ -1285,6 +1286,7 @@ const translations = {
// Usage
'usage.title': 'Usage Query',
'usage.refresh': 'Refresh Usage',
'usage.serverTime': 'Server Time',
'usage.lastUpdate': 'Last Update: {time}',
'usage.lastUpdateCache': 'Cache Time: {time}',
'usage.supportedProvidersPrefix': 'Providers supporting usage query:',

View file

@ -92,6 +92,14 @@ export async function loadUsage() {
// 渲染用量数据
renderUsageData(data, contentEl);
// 更新服务端系统时间
if (data.serverTime) {
const serverTimeEl = document.getElementById('serverTimeValue');
if (serverTimeEl) {
serverTimeEl.textContent = new Date(data.serverTime).toLocaleString(getCurrentLanguage());
}
}
// 更新最后更新时间
if (lastUpdateEl) {
const timeStr = new Date(data.timestamp || Date.now()).toLocaleString(getCurrentLanguage());
@ -155,6 +163,14 @@ export async function refreshUsage() {
// 渲染用量数据
renderUsageData(data, contentEl);
// 更新服务端系统时间
if (data.serverTime) {
const serverTimeEl = document.getElementById('serverTimeValue');
if (serverTimeEl) {
serverTimeEl.textContent = new Date(data.serverTime).toLocaleString(getCurrentLanguage());
}
}
// 更新最后更新时间
if (lastUpdateEl) {
const timeStr = new Date().toLocaleString(getCurrentLanguage());

View file

@ -18,6 +18,20 @@
.usage-last-update {
font-size: 0.875rem;
color: var(--text-secondary);
margin-left: auto;
}
.server-time-display {
font-size: 0.875rem;
color: var(--text-secondary);
margin-left: 1.5rem;
padding-left: 1.5rem;
border-left: 1px solid var(--border-color);
}
.server-time-display i {
color: var(--primary-color);
margin-right: 0.25rem;
}
.usage-info-banner {

View file

@ -8,6 +8,9 @@
<i class="fas fa-sync-alt"></i> <span data-i18n="usage.refresh">刷新用量</span>
</button>
<span class="usage-last-update" id="usageLastUpdate" data-i18n="usage.lastUpdate" data-i18n-params='{"time":"--"}'>上次更新: --</span>
<span class="server-time-display" id="serverTimeDisplay">
<span data-i18n="usage.serverTime">服务端时间</span>: <span id="serverTimeValue">--</span>
</span>
</div>
<div class="usage-info-banner">