opencode-claude-code-ua 0.3.6 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -90
- package/index.mjs +2 -6
- package/lib/ua-protocol.mjs +8 -0
- package/package.json +1 -1
- package/setup.mjs +1 -1
- package/src/account-usage.mjs +33 -0
- package/src/accounts.mjs +96 -23
- package/src/key-usage.mjs +73 -0
- package/src/newapi.mjs +106 -13
- package/src/plugin.mjs +4 -34
- package/src/scheduler.mjs +2 -2
- package/src/setup-server.mjs +16 -3
- package/src/ua-plugin.mjs +57 -14
- package/src/ua-runtime.mjs +1 -1
- package/src/ua-service.mjs +38 -6
- package/src/ua-store.mjs +4 -1
- package/src/ua-usage.mjs +64 -0
- package/ui/accounts.css +1 -1
- package/ui/accounts.html +21 -11
- package/ui/accounts.js +230 -26
- package/ui/pro.css +1 -0
- package/ui/pro.html +10 -11
- package/ui/pro.js +82 -12
package/src/ua-service.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
2
|
import { createUATestRunner } from './ua-tests.mjs'
|
|
3
3
|
import { validateTemplate } from './ua-transform.mjs'
|
|
4
|
-
import { lookupUAModels, readModelCatalog } from './ua-runtime.mjs'
|
|
4
|
+
import { lookupUAModels, readModelCatalog, modelQueryScope } from './ua-runtime.mjs'
|
|
5
5
|
import { createUAModelCatalog } from './ua-model-catalog.mjs'
|
|
6
|
+
import { matchesAccountSite } from '../lib/ua-protocol.mjs'
|
|
7
|
+
import { createKeyUsage } from './key-usage.mjs'
|
|
6
8
|
|
|
7
9
|
const digest = value => createHash('sha256').update(JSON.stringify(value)).digest('hex')
|
|
8
10
|
const failure = (message, status = 400) => Object.assign(new Error(message), { status })
|
|
9
11
|
|
|
10
|
-
export function createUAService({ store, legacyStore, readAuth, fetchImpl, modelFetchFor, modelCatalog = createUAModelCatalog(store.path), onConnectionSaved, runtime = () => ({ mode: 'standalone', observed: [] }) }) {
|
|
12
|
+
export function createUAService({ store, legacyStore, accountManager, readAuth, fetchImpl, modelFetchFor, modelCatalog = createUAModelCatalog(store.path), onConnectionSaved, syncConnections, runtime = () => ({ mode: 'standalone', observed: [] }) }) {
|
|
11
13
|
const test = createUATestRunner({ store, readAuth, fetchImpl, modelFetchFor })
|
|
12
14
|
const initialized = () => store.initialize()
|
|
13
15
|
return {
|
|
@@ -21,7 +23,15 @@ export function createUAService({ store, legacyStore, readAuth, fetchImpl, model
|
|
|
21
23
|
connection.auth.key = ''
|
|
22
24
|
connection.auth.value = ''
|
|
23
25
|
}
|
|
24
|
-
|
|
26
|
+
const siteAccounts = accountManager ? (await accountManager.list()).accounts.map(account => ({ id: account.id, name: account.name,
|
|
27
|
+
siteUrl: account.siteUrl, siteType: account.siteType ?? 'newapi', userId: account.userId, enabled: account.enabled,
|
|
28
|
+
checkinEnabled: account.checkinEnabled, schedule: account.schedule })) : []
|
|
29
|
+
return { ...snapshot, modelCatalogs, siteAccounts, runtime: await runtime(snapshot) }
|
|
30
|
+
},
|
|
31
|
+
async accountConnections() {
|
|
32
|
+
await initialized()
|
|
33
|
+
return (await store.snapshot()).connections.map(connection => ({ id: connection.id, name: connection.name, baseURL: connection.baseURL,
|
|
34
|
+
revision: connection.revision, enabled: connection.enabled, newApiAccountId: connection.newApiAccountId ?? '' }))
|
|
25
35
|
},
|
|
26
36
|
async execute(action, data, { signal } = {}) {
|
|
27
37
|
await initialized()
|
|
@@ -29,6 +39,15 @@ export function createUAService({ store, legacyStore, readAuth, fetchImpl, model
|
|
|
29
39
|
if (action === 'templates/save') return store.saveTemplate(data)
|
|
30
40
|
if (action === 'templates/archive') return store.archiveTemplate(data)
|
|
31
41
|
if (action === 'templates/remove') return store.removeTemplate(data)
|
|
42
|
+
if (action === 'connections/usage') {
|
|
43
|
+
if (data.confirmNetwork !== true) throw failure('读取连接余额需要明确确认')
|
|
44
|
+
const snapshot = await store.snapshot({ secrets: true })
|
|
45
|
+
const connection = snapshot.connections.find(item => item.id === data.connectionId)
|
|
46
|
+
if (!connection || !connection.enabled) throw failure('连接不存在或已停用', 404)
|
|
47
|
+
const usageFetch = modelFetchFor ? modelFetchFor(await modelQueryScope(connection, readAuth)) : fetchImpl
|
|
48
|
+
const usage = await createKeyUsage({ fetchImpl: usageFetch, readAuth })(connection)
|
|
49
|
+
return { connectionId: connection.id, revision: connection.revision, usage, at: new Date().toISOString() }
|
|
50
|
+
}
|
|
32
51
|
if (action === 'connections/models') {
|
|
33
52
|
if (data.confirmNetwork !== true) throw failure('获取模型需要明确确认')
|
|
34
53
|
const connection = await store.prepareConnection(data)
|
|
@@ -44,7 +63,7 @@ export function createUAService({ store, legacyStore, readAuth, fetchImpl, model
|
|
|
44
63
|
if (error.code === 'upstream-http') throw Object.assign(failure(`${error.message},请检查地址、协议和凭据`), { code: error.code })
|
|
45
64
|
if ([400, 409].includes(error.status)) {
|
|
46
65
|
if (['dns', 'connection-refused', 'connection-reset', 'timeout', 'tls', 'network'].includes(error.code)) {
|
|
47
|
-
const route = runtime().mode === 'host' ? '当前管理会话的代理可能与宿主账号代理不同。' : '独立工具不继承 Magpie 的账号代理;可从 Magpie 打开 UA PRO 管理后核对。'
|
|
66
|
+
const route = (await runtime()).mode === 'host' ? '当前管理会话的代理可能与宿主账号代理不同。' : '独立工具不继承 Magpie 的账号代理;可从 Magpie 打开 UA PRO 管理后核对。'
|
|
48
67
|
error.message += `。${route}`
|
|
49
68
|
}
|
|
50
69
|
throw error
|
|
@@ -52,11 +71,24 @@ export function createUAService({ store, legacyStore, readAuth, fetchImpl, model
|
|
|
52
71
|
throw failure('模型列表读取未完成,请重新打开管理页后重试', 400)
|
|
53
72
|
}
|
|
54
73
|
}
|
|
74
|
+
if (action === 'connections/sync') {
|
|
75
|
+
if (!syncConnections) throw failure('独立工具不能直接写入 Magpie;请从宿主打开 UA PRO 管理后同步', 409)
|
|
76
|
+
return syncConnections()
|
|
77
|
+
}
|
|
55
78
|
if (action === 'connections/save') {
|
|
79
|
+
if (data.connection?.newApiAccountId) {
|
|
80
|
+
if (!accountManager) throw failure('站点账号管理尚未初始化', 409)
|
|
81
|
+
const account = await accountManager.get(data.connection.newApiAccountId)
|
|
82
|
+
if (!matchesAccountSite(data.connection.baseURL, account)) throw Object.assign(failure('余额与签到只能绑定同一站点的账号'), { fields: { newApiAccountId: '所选账号属于其他站点' } })
|
|
83
|
+
}
|
|
56
84
|
const result = await store.saveConnection(data)
|
|
57
85
|
if (onConnectionSaved) {
|
|
58
|
-
try {
|
|
59
|
-
|
|
86
|
+
try {
|
|
87
|
+
const host = await onConnectionSaved(result.connection)
|
|
88
|
+
if (host?.status === 'reload-required') return { ...result, registration: 'pending-reload', activation: 'pending-reload', host, message: host.message }
|
|
89
|
+
if (host && host.status !== 'registered') return { ...result, registration: 'pending-reload', activation: 'pending-reload', host, message: host.message || '连接已保存,宿主账号尚未确认,请同步或重载后核对' }
|
|
90
|
+
return { ...result, registration: 'registered', activation: 'pending-runtime-observation', ...(host ? { host } : {}) }
|
|
91
|
+
} catch { return { ...result, registration: 'pending-reload', activation: 'pending-reload', message: '连接已保存,宿主注册未确认,请重载插件后核对' } }
|
|
60
92
|
}
|
|
61
93
|
return { ...result, registration: 'pending-host-load', activation: 'pending-runtime-observation' }
|
|
62
94
|
}
|
package/src/ua-store.mjs
CHANGED
|
@@ -149,7 +149,7 @@ function normalizeModelBindings(raw) {
|
|
|
149
149
|
|
|
150
150
|
function normalizeConnectionShape(raw) {
|
|
151
151
|
if (!isObject(raw)) throw fail('连接必须是对象', 400, 'connection');
|
|
152
|
-
onlyKeys(raw, ['id', 'name', 'enabled', 'revision', 'siteId', 'independent', 'baseURL', 'protocol', 'apiPaths', 'modelProtocols', 'models', 'auth', 'binding', 'modelBindings', 'secrets'], 'connection');
|
|
152
|
+
onlyKeys(raw, ['id', 'name', 'enabled', 'revision', 'siteId', 'independent', 'baseURL', 'protocol', 'apiPaths', 'modelProtocols', 'models', 'auth', 'binding', 'modelBindings', 'secrets', 'newApiAccountId'], 'connection');
|
|
153
153
|
let id;
|
|
154
154
|
if (raw.id !== undefined && raw.id !== '') {
|
|
155
155
|
if (!isText(raw.id) || !STABLE_ID.test(raw.id.trim())) throw fail('连接 id 无效', 400, 'id');
|
|
@@ -214,10 +214,13 @@ function normalizeConnectionShape(raw) {
|
|
|
214
214
|
|
|
215
215
|
const binding = raw.binding === undefined ? undefined : normalizeBinding(raw.binding);
|
|
216
216
|
const modelBindings = raw.modelBindings === undefined ? {} : normalizeModelBindings(raw.modelBindings);
|
|
217
|
+
const newApiAccountId = raw.newApiAccountId;
|
|
218
|
+
if (newApiAccountId !== undefined && (typeof newApiAccountId !== 'string' || newApiAccountId.length > 200 || /[\x00-\x1f\x7f-\x9f]/.test(newApiAccountId))) throw fail('余额与签到账号绑定无效', 400, 'newApiAccountId');
|
|
217
219
|
|
|
218
220
|
return {
|
|
219
221
|
id, name: raw.name.trim(), enabled, revision, siteId, independent, baseURL, protocol,
|
|
220
222
|
apiPaths, modelProtocols, models: [...modelsRaw], auth, binding, modelBindings,
|
|
223
|
+
...(newApiAccountId !== undefined ? { newApiAccountId: newApiAccountId.trim() } : {}),
|
|
221
224
|
};
|
|
222
225
|
}
|
|
223
226
|
|
package/src/ua-usage.mjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { matchesAccountSite } from '../lib/ua-protocol.mjs'
|
|
2
|
+
import { accountUsage, accountCheckin } from './account-usage.mjs'
|
|
3
|
+
|
|
4
|
+
export function createUAUsage({ providerId, input = {}, options = {}, accountManager, getConnection, resolveAccount, queryKeyUsage }) {
|
|
5
|
+
async function selected(getAuth, provider) {
|
|
6
|
+
const auth = typeof getAuth === 'function' ? await getAuth() : null
|
|
7
|
+
const connection = auth ? await getConnection(auth, provider) : null
|
|
8
|
+
if (!connection) return { accountId: null, connection, auth }
|
|
9
|
+
if (typeof auth?.metadata?.newApiAccountId === 'string') return { accountId: auth.metadata.newApiAccountId.trim() || null, connection, auth }
|
|
10
|
+
const slot = input.magpie?.account?.(providerId) ?? await resolveAccount?.(auth, connection)
|
|
11
|
+
if (typeof slot === 'string' && Object.hasOwn(options.accountBindings ?? {}, slot)) return { accountId: options.accountBindings[slot], connection, auth }
|
|
12
|
+
return { accountId: connection.newApiAccountId?.trim() || null, connection, auth }
|
|
13
|
+
}
|
|
14
|
+
async function boundAccount(selection) {
|
|
15
|
+
const account = await accountManager.get(selection.accountId)
|
|
16
|
+
if (selection.connection && !matchesAccountSite(selection.connection.baseURL, account)) throw new Error('站点绑定不匹配')
|
|
17
|
+
return account
|
|
18
|
+
}
|
|
19
|
+
const unavailable = message => ({ signIn: 'kept', error: message, windows: [{ name: '站点账号状态', aside: true, display: message }] })
|
|
20
|
+
async function keyFallback(selection, usage, message) {
|
|
21
|
+
if (selection.connection && queryKeyUsage) {
|
|
22
|
+
try {
|
|
23
|
+
const keyUsage = await queryKeyUsage(selection.connection, selection.auth)
|
|
24
|
+
if (keyUsage.balance !== undefined || keyUsage.windows?.some(window => Number.isFinite(window.amount))) {
|
|
25
|
+
return { ...keyUsage, error: `${message};显示已配置连接的 Key 用量,签到凭据需单独核对`,
|
|
26
|
+
windows: [...(keyUsage.windows ?? []), { name: '站点账号状态', aside: true, display: message }] }
|
|
27
|
+
}
|
|
28
|
+
} catch { /* An optional Key reading must not discard the known account snapshot. */ }
|
|
29
|
+
}
|
|
30
|
+
return usage
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
async usage(getAuth, provider = {}) {
|
|
34
|
+
try {
|
|
35
|
+
const selection = await selected(getAuth, provider)
|
|
36
|
+
if (selection.accountId) {
|
|
37
|
+
let account
|
|
38
|
+
try { account = await accountManager.get(selection.accountId) }
|
|
39
|
+
catch { return keyFallback(selection, unavailable('绑定的站点账号无法读取'), '绑定的站点账号无法读取') }
|
|
40
|
+
if (!matchesAccountSite(selection.connection.baseURL, account)) throw new Error('站点绑定不匹配')
|
|
41
|
+
if (!account.enabled) return keyFallback(selection, unavailable('余额与签到账号已停用;聊天不受影响'), '站点账号已停用')
|
|
42
|
+
let refreshed
|
|
43
|
+
try { refreshed = await accountManager.refresh(selection.accountId) }
|
|
44
|
+
catch { return keyFallback(selection, unavailable('站点账号余额未更新'), '站点账号余额未更新') }
|
|
45
|
+
const usage = accountUsage(refreshed)
|
|
46
|
+
return !refreshed.snapshot || refreshed.status?.stale
|
|
47
|
+
? keyFallback(selection, usage, `站点账号余额未更新:${refreshed.status?.error || '尚无账号读数'}`) : usage
|
|
48
|
+
}
|
|
49
|
+
const connection = selection.connection ?? (selection.auth ? await getConnection(selection.auth, provider) : null)
|
|
50
|
+
if (connection && queryKeyUsage) return queryKeyUsage(connection, selection.auth)
|
|
51
|
+
return { signIn: 'kept', windows: [{ name: '可选余额', display: '当前宿主账号未绑定站点账号;聊天不受影响', aside: true }] }
|
|
52
|
+
} catch { return { signIn: 'kept', error: '余额读取未完成,请检查站点账号或连接设置', windows: [{ name: '可选余额', display: '余额读取未完成;模型 API key 不受影响', aside: true }] } }
|
|
53
|
+
},
|
|
54
|
+
async checkin(getAuth, provider = {}) {
|
|
55
|
+
try {
|
|
56
|
+
const selection = await selected(getAuth, provider)
|
|
57
|
+
if (!selection.accountId) return { outcome: 'inactive', message: '请先在 UA PRO 中绑定同站点的余额与签到账号;模型 API key 不替代登录凭据' }
|
|
58
|
+
const account = await boundAccount(selection)
|
|
59
|
+
if (!account.enabled || !account.checkinEnabled) return accountCheckin(account)
|
|
60
|
+
return accountCheckin(await accountManager.checkin(selection.accountId))
|
|
61
|
+
} catch { return { outcome: 'failed', message: '绑定的站点账号签到未完成,请检查账号管理;模型 API key 未改变' } }
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
}
|
package/ui/accounts.css
CHANGED
|
@@ -72,4 +72,4 @@ fieldset{min-width:0;margin:0;padding:0;border:0}
|
|
|
72
72
|
@media(max-width:560px){.dialog-footer-actions{flex-direction:column;align-items:stretch}#account-editor .dialog-header{padding:14px 16px 10px}#account-scroll{padding:14px 16px}#account-editor .dialog-footer{padding:10px 16px;gap:8px}.dialog-footer-note .hint{font-size:11px}#account-editor .field input:not([type=checkbox]),#account-editor .field select{font-size:16px}}
|
|
73
73
|
@supports(max-height:90dvh){#account-form{max-height:90dvh}}
|
|
74
74
|
@media(max-height:500px){#account-editor .dialog-header p,#account-editor .dialog-footer-note>.hint:first-child{display:none}#account-editor .dialog-header{padding-top:10px;padding-bottom:6px}#account-editor .dialog-footer{padding-top:8px;padding-bottom:8px;flex-direction:row}}
|
|
75
|
-
@media(max-width:560px){.account-table-scroll{overflow:visible;border:0;border-radius:0;background:transparent}.account-table-scroll table,.account-table-scroll tbody,.account-table-scroll tr,.account-table-scroll td{display:block;width:auto}.account-table-scroll thead{display:none}.account-table-scroll tbody tr{margin-bottom:12px;padding:14px 16px;border:1px solid var(--line);border-radius:8px;background:var(--paper)}.account-table-scroll td{padding:0 0 10px;border:0}.account-table-scroll td:last-child{padding-bottom:0}.account-table-scroll td:not(:first-child)::before{content:attr(data-label);display:block;margin-bottom:2px;color:var(--muted);font-size:11px}.account-table-scroll .row-actions{max-width:none}}
|
|
75
|
+
@media(max-width:560px){.account-table-scroll{overflow:visible;border:0;border-radius:0;background:transparent}.account-table-scroll table,.account-table-scroll tbody,.account-table-scroll tr,.account-table-scroll td{display:block;width:auto;min-width:0}.account-table-scroll thead{display:none}.account-table-scroll tbody tr{margin-bottom:12px;padding:14px 16px;border:1px solid var(--line);border-radius:8px;background:var(--paper)}.account-table-scroll td{padding:0 0 10px;border:0}.account-table-scroll td:last-child{padding-bottom:0}.account-table-scroll td:not(:first-child)::before{content:attr(data-label);display:block;margin-bottom:2px;color:var(--muted);font-size:11px}.account-table-scroll .row-actions{max-width:none}}
|
package/ui/accounts.html
CHANGED
|
@@ -4,23 +4,32 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<meta name="color-scheme" content="light dark">
|
|
7
|
-
<title>账号余额与签到 ·
|
|
7
|
+
<title>账号余额与签到 · UA PRO</title>
|
|
8
8
|
<script src="./theme.js"></script>
|
|
9
9
|
<link rel="stylesheet" href="./accounts.css">
|
|
10
10
|
<link rel="icon" type="image/svg+xml" href="./icon.svg">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="accounts-shell" class="accounts-shell">
|
|
14
|
-
<header class="accounts-header"><a class="brand" href="./"><img class="brand-icon" src="./icon.svg" width="44" height="44" alt=""><span>Magpie<br><strong>
|
|
14
|
+
<header class="accounts-header"><a class="brand" href="./pro"><img class="brand-icon" src="./icon.svg" width="44" height="44" alt=""><span>Magpie<br><strong>UA PRO</strong></span></a><nav aria-label="UA PRO 页面"><a href="./pro">UA PRO</a><a href="./accounts" aria-current="page">账号余额与签到</a></nav><div class="theme-control"><label for="theme-mode">外观</label><select id="theme-mode" data-theme-control><option value="system">跟随系统</option><option value="dark">夜间模式</option><option value="light">浅色模式</option></select></div><button id="accounts-done" class="secondary" type="button">完成并关闭</button></header>
|
|
15
15
|
<main class="accounts-main">
|
|
16
|
-
<div class="accounts-title"><div><h1>每个账号,独立记账</h1><p>管理 New API 站点余额、累计消耗与签到奖励,不改变现有模型路由。</p></div><button id="account-new" class="primary" type="button">添加账号</button></div>
|
|
16
|
+
<div class="accounts-title"><div><h1>每个账号,独立记账</h1><p>管理 New API / AgentRouter 站点余额、累计消耗与签到奖励,不改变现有模型路由。</p></div><button id="account-new" class="primary" type="button">添加账号</button></div>
|
|
17
17
|
<div id="accounts-error" class="accounts-error" role="alert" hidden tabindex="-1"></div>
|
|
18
|
-
<div id="account-totals" class="account-totals" aria-label="
|
|
18
|
+
<div id="account-totals" class="account-totals" aria-label="按单位汇总站点账号,不含连接 Key 余额"></div>
|
|
19
19
|
<div class="account-toolbar">
|
|
20
|
-
<div class="account-toolbar-status"><p id="accounts-status" role="status">正在读取本地账号…</p><button id="accounts-retry" class="secondary" type="button" hidden>重新读取账号列表</button><button id="accounts-ack" class="secondary" type="button" hidden>已核对结果,继续操作</button></div>
|
|
20
|
+
<div class="account-toolbar-status"><p id="accounts-status" role="status">正在读取本地账号…</p><a id="accounts-connections-link" href="./pro#connections">在连接与模型选择对应签到账号</a><button id="accounts-retry" class="secondary" type="button" hidden>重新读取账号列表</button><button id="accounts-ack" class="secondary" type="button" hidden>已核对结果,继续操作</button></div>
|
|
21
21
|
<div class="bulk-actions"><button id="refresh-all" class="secondary" type="button" disabled>刷新全部余额</button><button id="checkin-all" class="secondary" type="button" disabled>签到全部启用账号</button><span id="bulk-reason" class="bulk-reason" role="status" hidden></span></div>
|
|
22
22
|
</div>
|
|
23
|
-
<
|
|
23
|
+
<section id="connection-balances" class="account-notes" aria-labelledby="connection-balances-title">
|
|
24
|
+
<div class="account-toolbar">
|
|
25
|
+
<div class="account-toolbar-status"><div><h2 id="connection-balances-title">已配置连接余额/Key用量(不依赖签到凭据)</h2><p id="connection-balance-status" role="status">不会自动访问站点;请手动刷新连接 Key 用量。</p></div></div>
|
|
26
|
+
<div class="bulk-actions"><button id="connection-refresh-all" class="secondary" type="button" disabled>刷新全部连接 Key 用量</button></div>
|
|
27
|
+
</div>
|
|
28
|
+
<div id="connection-balance-list" class="account-table-scroll">
|
|
29
|
+
<table id="connection-balance-table"><thead><tr><th scope="col">连接 / 站点</th><th scope="col">Key 余额</th><th scope="col">Key 用量</th><th scope="col">最近查询</th></tr></thead><tbody id="connection-balance-rows"></tbody></table>
|
|
30
|
+
</div>
|
|
31
|
+
</section>
|
|
32
|
+
<div id="account-empty" class="account-empty" hidden><h2>还没有站点账号</h2><p>添加站点地址、站点类型和系统访问令牌或 Cookie;AgentRouter 还可填写登录用户名与密码。模型 API key 不能代替站点账号凭据。</p></div>
|
|
24
33
|
<div id="accounts-table-scroll" class="account-table-scroll"><table id="accounts-table"><thead><tr><th scope="col">账号 / 站点</th><th scope="col">剩余额度</th><th scope="col">累计消耗</th><th scope="col">相较上次成功刷新</th><th scope="col">签到</th><th scope="col">操作</th></tr></thead><tbody id="account-list"></tbody></table></div>
|
|
25
34
|
<div class="account-notes"><p>增量从两次成功读取计算,不等于“今日用量”。首次读取无基线;计数回退时重新建立基线。查询失败保留旧读数并标记缓存。</p><p>不同单位分别汇总,不做汇率转换。自动签到需在账号中单独开启;请勿同时启用宿主的另一套自动签到计划。验证码请到站点完成。</p></div>
|
|
26
35
|
</main>
|
|
@@ -31,20 +40,21 @@
|
|
|
31
40
|
<div id="account-form-error" class="accounts-error" role="alert" hidden tabindex="-1"></div>
|
|
32
41
|
<div id="editor-reconciliation" class="reconciliation-summary" role="status" hidden></div>
|
|
33
42
|
<div id="account-scroll"><fieldset id="account-fields">
|
|
34
|
-
<div class="field"><label for="account-name">账号名称</label><input id="account-name" required placeholder="便于识别的名称" autocomplete="off"></div>
|
|
35
|
-
<div class="field"><label for="site-url"
|
|
36
|
-
<div class="field-grid"><div class="field"><label for="user-id">用户 ID</label><input id="user-id" inputmode="numeric" required placeholder="站点个人资料中的数字 ID"></div><div class="field"><label for="auth-mode"
|
|
43
|
+
<div class="field-grid"><div class="field"><label for="account-name">账号名称</label><input id="account-name" required placeholder="便于识别的名称" autocomplete="off"></div><div class="field"><label for="site-type">站点类型</label><select id="site-type"><option value="newapi">NewAPI(Cookie/令牌签到)</option><option value="agentrouter">AgentRouter(登录即签到)</option></select><p class="hint">NewAPI 用 Cookie/令牌签到;AgentRouter 用登录即签到。</p></div></div>
|
|
44
|
+
<div class="field"><label for="site-url">站点地址</label><input id="site-url" type="url" required placeholder="https://你的站点地址" autocomplete="off" spellcheck="false"><p class="hint">填写站点根地址或部署基础路径,不是模型 API 的 /v1 地址。</p></div>
|
|
45
|
+
<div class="field-grid"><div class="field"><label for="user-id">用户 ID</label><input id="user-id" inputmode="numeric" required placeholder="站点个人资料中的数字 ID"><p class="hint">NewAPI 必填数字 ID;AgentRouter 可留空,由登录结果确定。</p></div><div class="field"><label for="auth-mode">余额查询凭据</label><select id="auth-mode"><option value="token">系统访问令牌</option><option value="cookie">登录 Cookie</option></select><p class="hint">仅用于只读余额查询,不用于 AgentRouter 登录签到。</p></div></div>
|
|
37
46
|
<div id="token-field" class="field"><label for="access-token">系统访问令牌</label><input type="password" id="access-token" autocomplete="new-password" placeholder="不是 sk- 开头的模型调用 key"><div class="secret-meta"><span id="token-state" class="hint">留空保留已有值</span><label class="check-label" for="clear-token"><input type="checkbox" id="clear-token">清除已存令牌</label></div></div>
|
|
38
47
|
<div id="cookie-field" class="field"><label for="account-cookie">登录 Cookie</label><input type="password" id="account-cookie" autocomplete="new-password" placeholder="session=…"><div class="secret-meta"><span id="cookie-state" class="hint">留空保留已有值</span><label class="check-label" for="clear-cookie"><input type="checkbox" id="clear-cookie">清除已存 Cookie</label></div><p class="hint">Cookie 过期后需从自己账号的站点重新获取。含登录凭据,仅保存在本机。</p></div>
|
|
48
|
+
<div id="agentrouter-fields" hidden><div class="field-grid"><div class="field"><label for="agent-username">AgentRouter 登录用户名</label><input id="agent-username" autocomplete="username" placeholder="用于站点登录签到"></div><div class="field"><label for="agent-password">AgentRouter 登录密码</label><input type="password" id="agent-password" autocomplete="new-password"><div class="secret-meta"><span id="agent-password-state" class="hint">尚未保存登录密码</span><label class="check-label" for="clear-password"><input type="checkbox" id="clear-password">清除已存密码</label></div></div></div><p class="hint">用户名和密码只用于向站点 POST api/user/login 完成签到,绝不进入模型请求路径;上方 PAT/Cookie 仍只用于只读余额查询。</p></div>
|
|
39
49
|
<div class="field-grid"><div class="field"><label for="quota-per-unit">每单位对应 quota</label><input id="quota-per-unit" type="number" min="0.000001" step="any" value="500000"><p class="hint">New API 常见默认 500000;按站点实际设置修改。</p></div><div class="field"><label for="unit">显示单位</label><input id="unit" value="USD" maxlength="20"><p class="hint">仅显示标签,不代表已验证美元汇率。</p></div></div>
|
|
40
50
|
<div class="account-toggles"><label class="check-label" for="account-enabled"><input id="account-enabled" type="checkbox" checked>启用账号,参与批量刷新</label><label class="check-label" for="checkin-enabled"><input id="checkin-enabled" type="checkbox" checked>允许手动、定时或宿主发起签到</label></div>
|
|
41
51
|
<section class="account-schedule" aria-label="自动签到时间">
|
|
42
|
-
<label class="check-label" for="schedule-enabled"><input id="schedule-enabled" type="checkbox"
|
|
52
|
+
<label class="check-label" for="schedule-enabled"><input id="schedule-enabled" type="checkbox">按此页设置定时签到(可选)</label>
|
|
43
53
|
<div id="schedule-settings" class="field-grid" hidden>
|
|
44
54
|
<div class="field"><label for="schedule-time">每天几点签到</label><input id="schedule-time" type="time" step="60" value="09:00"></div>
|
|
45
55
|
<div class="field"><label for="schedule-timezone">按哪个时区</label><input id="schedule-timezone" list="schedule-timezones" autocomplete="off" spellcheck="false" placeholder="Asia/Shanghai"><datalist id="schedule-timezones"><option value="Asia/Shanghai">北京时间</option><option value="Asia/Hong_Kong">香港</option><option value="Asia/Tokyo">东京</option><option value="UTC">世界标准时间</option><option value="America/New_York">纽约</option><option value="America/Los_Angeles">洛杉矶</option><option value="Europe/London">伦敦</option></datalist><p class="hint">默认使用你浏览器的时区,也可填写其他 IANA 时区名称。</p></div>
|
|
46
56
|
</div>
|
|
47
|
-
<p class="hint"
|
|
57
|
+
<p class="hint">此页定时签到可与站内手动签到、宿主签到分别使用;需要 Magpie 插件进程保持运行,但请勿同时启用宿主另一套自动签到。错过时间不补跑,同一天自动尝试一次,失败不自动重试。夏令时跳过的分钟不执行。</p>
|
|
48
58
|
</section>
|
|
49
59
|
</fieldset></div>
|
|
50
60
|
<footer class="dialog-footer"><div class="dialog-footer-note"><p class="hint">保存不会发送登录或签到请求。密钥不会回传浏览器。</p><p id="editor-dirty-state" class="hint" role="status"></p></div><div class="dialog-footer-actions"><button id="editor-recheck" class="secondary" type="button" hidden>重新读取账号列表</button><button id="editor-ack" class="secondary" type="button" hidden>已核对结果,继续操作</button><button id="account-save" class="primary" type="submit">保存账号</button></div></footer>
|