ai-zero-token 2.0.6 → 2.0.7
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/CHANGELOG.md +9 -1
- package/README.md +5 -5
- package/README.zh-CN.md +5 -5
- package/admin-ui/dist/assets/accounts-D3tsDc3k.js +4 -0
- package/admin-ui/dist/assets/{docs--eK_2fzC.js → docs-BO-aSEzh.js} +1 -1
- package/admin-ui/dist/assets/{image-bed-7wBZ1GhS.js → image-bed-Dql7Vqd9.js} +1 -1
- package/admin-ui/dist/assets/{index-CdFYy5j6.js → index-CCiBaGwU.js} +3 -3
- package/admin-ui/dist/assets/{launch-BiD1Khtg.js → launch-DXLo-NIM.js} +1 -1
- package/admin-ui/dist/assets/{logs-BdoKDqh2.js → logs-Cwn8-rDu.js} +1 -1
- package/admin-ui/dist/assets/{network-detect-BvKns5nQ.js → network-detect-vzWfL-Tz.js} +1 -1
- package/admin-ui/dist/assets/{overview-wm6M45fu.js → overview-B_yad8ge.js} +1 -1
- package/admin-ui/dist/assets/{profiles-DMOjJORP.js → profiles-C5SmQvju.js} +1 -1
- package/admin-ui/dist/assets/{settings-DOOu7Kd8.js → settings-BdRWcKJb.js} +1 -1
- package/admin-ui/dist/assets/{tester-NrARmlis.js → tester-BKoMSoCz.js} +1 -1
- package/admin-ui/dist/assets/{usage-CdWRVMDV.js → usage-B-qQxXzQ.js} +1 -1
- package/admin-ui/dist/index.html +2 -2
- package/dist/cli/commands/help.js +1 -1
- package/dist/cli/commands/models.js +3 -2
- package/dist/core/context.js +1 -1
- package/dist/core/models/openai-codex-models.js +106 -1
- package/dist/core/providers/http-client.js +142 -12
- package/dist/core/services/auth-service.js +104 -7
- package/dist/core/services/chat-service.js +16 -18
- package/dist/core/services/model-service.js +22 -8
- package/dist/server/app.js +179 -23
- package/dist/server/index.js +1 -1
- package/docs/API_USAGE.md +1 -1
- package/docs/DESKTOP_RELEASE.md +12 -1
- package/package.json +1 -1
- package/admin-ui/dist/assets/accounts-bCDKXGg9.js +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 2.0.
|
|
3
|
+
## 2.0.7 - 2026-05-11
|
|
4
4
|
|
|
5
5
|
- Added persistent local usage statistics with today, current-process, lifetime, daily trend, account, model, endpoint, error, image-route, and source breakdowns.
|
|
6
6
|
- Added safe usage event storage under the local state directory without persisting prompts, messages, access tokens, or base64 payloads.
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
- Restored filtered-result bulk selection controls so filtered accounts can be selected or cleared for batch operations.
|
|
9
9
|
- Clarified usage UI labels so token totals are shown as known upstream-returned usage, while requests without upstream usage are counted separately.
|
|
10
10
|
- Improved the Settings Free-account image warning and removed duplicated Settings page heading copy.
|
|
11
|
+
- Fixed Codex curl streaming status handling so interim `HTTP 100 Continue` responses are ignored before the final upstream status.
|
|
12
|
+
- Changed model refresh to fetch the Codex model catalog from the network and write it back to the local Codex model cache.
|
|
13
|
+
- Improved automatic account switching so unsynced accounts can be used as fallback candidates and invalid active accounts can rotate away.
|
|
14
|
+
- Added request-level account rotation retries for OpenAI-compatible text requests, while binding Codex response ids to their source account and falling back to a new session if a sticky `previous_response_id` continuation cannot be used.
|
|
15
|
+
- Improved Codex curl stream failure handling by tagging pre-header disconnects with gateway request metadata and retrying transient new-session stream failures once.
|
|
16
|
+
- Increased the default request body limit to 128 MiB and allowed `/codex/v1/responses/compact` requests up to at least 256 MiB to avoid local compaction 413 failures.
|
|
17
|
+
- Treated curl TLS, DNS, connect, and timeout failures as transient gateway errors so request-level rotation can retry without relying on quota probing, and labeled accounts without quota snapshots as pending request validation.
|
|
18
|
+
- Captured token usage from Codex SSE `response.completed` events so native `/codex/v1/responses` traffic is no longer counted as unknown whenever upstream returns usage data.
|
|
11
19
|
|
|
12
20
|
## 2.0.5 - 2026-05-09
|
|
13
21
|
|
package/README.md
CHANGED
|
@@ -21,9 +21,9 @@ AI Zero Token provides a local CLI, web console, and HTTP gateway that expose sa
|
|
|
21
21
|
- Account JSON import/export, including ZIP batch import, selected batch export, and export audit indicators.
|
|
22
22
|
- Apply a saved account to local Codex by backing up and updating `~/.codex/auth.json`.
|
|
23
23
|
- `gpt-image-2` image generation and JSON image editing. Paid plans use the Codex Responses image tool; Free plans can optionally use the ChatGPT web image path from Settings.
|
|
24
|
-
- Optional quota-exhaustion auto switch to the next saved API account
|
|
24
|
+
- Optional quota-exhaustion auto switch to the next saved API account, including accounts whose quota has not been synced yet, plus configurable quota refresh concurrency.
|
|
25
25
|
- Optional upstream proxy configuration for OAuth, model refresh, and gateway forwarding.
|
|
26
|
-
- Local model discovery from the Codex model cache with manual
|
|
26
|
+
- Local model discovery from the Codex model cache, with manual network sync from the Codex backend.
|
|
27
27
|
- OpenClaw-oriented chat compatibility for streaming, tool calls, tool result messages, and request-log diagnostics.
|
|
28
28
|
- Persistent local usage statistics for known token usage, images, success/failure counts, latency, and account/model/endpoint breakdowns.
|
|
29
29
|
|
|
@@ -271,10 +271,10 @@ The web console settings are persisted in the same local state directory. The qu
|
|
|
271
271
|
|
|
272
272
|
The global quota refresh concurrency can be configured in the web console settings. The default is `16`; lower it if upstream rate limits increase, or raise it for larger local account pools.
|
|
273
273
|
|
|
274
|
-
The default request body limit is `
|
|
274
|
+
The default request body limit is `128 MiB`, which is intended to make JSON base64 image references and Codex context-compaction requests practical for local workflows. The dedicated `/codex/v1/responses/compact` route is allowed at least `256 MiB`. You can override the default with:
|
|
275
275
|
|
|
276
276
|
```bash
|
|
277
|
-
AZT_BODY_LIMIT_MB=
|
|
277
|
+
AZT_BODY_LIMIT_MB=256 azt start
|
|
278
278
|
```
|
|
279
279
|
|
|
280
280
|
## Image Limits
|
|
@@ -283,7 +283,7 @@ ChatGPT Images availability and limits are controlled by the upstream account. P
|
|
|
283
283
|
|
|
284
284
|
Paid-plan image requests use `gpt-5.4-mini` as the internal orchestration model and pass the requested image model, such as `gpt-image-2`, to the `image_generation` tool. Free-plan web image requests convert the same input to a ChatGPT web image task.
|
|
285
285
|
|
|
286
|
-
For JSON image editing, base64 payloads are about 33% larger than the original image. With the default `
|
|
286
|
+
For JSON image editing, base64 payloads are about 33% larger than the original image. With the default `128 MiB` body limit, a raw image around `96 MiB` is the practical upper bound before JSON overhead. For larger images or batch workflows, prefer a reachable image URL.
|
|
287
287
|
|
|
288
288
|
## Limitations
|
|
289
289
|
|
package/README.zh-CN.md
CHANGED
|
@@ -21,9 +21,9 @@ AI Zero Token 是一个本地优先的 OpenAI 兼容网关,用于把 ChatGPT/C
|
|
|
21
21
|
- 支持账号 JSON 导入/导出、ZIP 批量导入、复选框批量导出和导出记录提示。
|
|
22
22
|
- 支持把已保存账号应用到本机 Codex,并自动备份 `~/.codex/auth.json`。
|
|
23
23
|
- 支持 `gpt-image-2` 文生图和 JSON 图生图;付费账号继续使用 Codex Responses 图片工具,Free 账号可在设置里选择是否启用 ChatGPT 网页图片链路。
|
|
24
|
-
- 支持 API
|
|
24
|
+
- 支持 API 账号额度耗尽后自动切换到下一个账号,包括尚未同步额度的新账号,并可配置全局额度刷新并发数。
|
|
25
25
|
- 支持上游代理配置,覆盖 OAuth、模型刷新和接口转发。
|
|
26
|
-
- 模型列表优先读取本机 Codex
|
|
26
|
+
- 模型列表优先读取本机 Codex 模型缓存,并支持手动从 Codex 后端同步。
|
|
27
27
|
- 面向 OpenClaw 增强 Chat Completions 兼容,支持流式、工具调用、工具结果消息和请求日志诊断。
|
|
28
28
|
- 支持本地持久化用量统计,记录已知 token、图片数、成功/失败、耗时,以及账号/模型/接口维度汇总。
|
|
29
29
|
|
|
@@ -271,10 +271,10 @@ AI_ZERO_TOKEN_HOME=/path/to/home azt start
|
|
|
271
271
|
|
|
272
272
|
全局额度刷新并发数可以在管理页设置里调整,默认 `16`。账号很多时可以调高;遇到上游限流或失败增多时建议调低。
|
|
273
273
|
|
|
274
|
-
默认请求体上限是 `
|
|
274
|
+
默认请求体上限是 `128 MiB`,用于让 JSON base64 图片和 Codex 上下文压缩请求在本地场景里更实用;`/codex/v1/responses/compact` 专用路由会至少放宽到 `256 MiB`。可以用下面的环境变量覆盖:
|
|
275
275
|
|
|
276
276
|
```bash
|
|
277
|
-
AZT_BODY_LIMIT_MB=
|
|
277
|
+
AZT_BODY_LIMIT_MB=256 azt start
|
|
278
278
|
```
|
|
279
279
|
|
|
280
280
|
## 生图额度
|
|
@@ -283,7 +283,7 @@ ChatGPT Images 的可用性和额度由上游账号决定。Plus、Team、Pro
|
|
|
283
283
|
|
|
284
284
|
付费账号图片请求内部使用 `gpt-5.4-mini` 作为编排模型,并把请求里的图片模型(例如 `gpt-image-2`)传给 `image_generation` tool;开启“Free 账号生图”后,Free 账号会把同样的请求转换为 ChatGPT 网页图片任务。
|
|
285
285
|
|
|
286
|
-
对于 JSON 图生图,base64 通常比原始图片大约 33%。在默认 `
|
|
286
|
+
对于 JSON 图生图,base64 通常比原始图片大约 33%。在默认 `128 MiB` 请求体上限下,原始图片约 `96 MiB` 是比较实际的上限,再大就容易被 JSON 开销和本地内存影响。大图或批量场景建议优先使用可访问的图片 URL。
|
|
287
287
|
|
|
288
288
|
## 当前限制
|
|
289
289
|
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{a as e,r as t,t as n}from"./jsx-runtime-DqpGtLhh.js";import{t as r}from"./earth-DFdZaQIi.js";import{t as i}from"./refresh-cw-CAAH2rqe.js";import{t as a}from"./search-B2hz41D3.js";import{C as o,_ as s,a as c,b as l,d as u,f as d,g as f,h as p,i as m,m as h,n as g,o as _,p as v,r as y,s as b,t as x,u as S,v as C,w,y as T}from"./profiles-C5SmQvju.js";import{_ as E,d as D,p as O,r as k,x as A}from"./index-CCiBaGwU.js";import{t as j}from"./InfoRow-0ULI9iI3.js";var M=e(t(),1),N=n();function P(e){let t=e.config?.codex?.accountId,n=e.profiles.length<=0?``:e.profiles.length===1?`profile-count-1`:e.profiles.length===2?`profile-count-2`:e.profiles.length===3?`profile-count-3`:`profile-count-many`;return(0,N.jsxs)(`section`,{className:`card`,id:`accounts`,children:[(0,N.jsxs)(`div`,{className:`section-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`h2`,{children:`账号额度预览`}),(0,N.jsx)(`p`,{children:`账号信息采用卡片式布局展示,支持搜索、状态筛选和额度排序。`})]}),(0,N.jsxs)(`div`,{className:`section-actions`,children:[(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onLocate,children:`定位当前账号`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onExportSelected,children:`导出所选`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onSelectVisible,disabled:e.visibleCount===0,children:`全选筛选结果`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onClearSelected,disabled:e.selectedCount===0,children:`取消选择`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onRemoveSelected,disabled:e.selectedCount===0||e.busy===`bulk-remove`,children:`删除所选`}),(0,N.jsx)(`button`,{className:`btn-primary`,type:`button`,onClick:e.onAddAccount,children:`新增账号`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onRefreshStatus,children:`刷新状态`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onClearAccounts,children:`清空账号`})]})]}),(0,N.jsx)(`div`,{className:`account-stat-strip`,"aria-label":`账号池统计`,children:e.accountStats.map(t=>(0,N.jsxs)(`button`,{className:`account-stat-pill tone-${t.tone} ${e.filter.status===t.key?`is-active`:``}`,type:`button`,onClick:()=>e.onFilter({...e.filter,status:t.key}),children:[(0,N.jsx)(`span`,{children:t.label}),(0,N.jsx)(`strong`,{children:t.value})]},t.key))}),(0,N.jsxs)(`div`,{className:`filter-row`,children:[(0,N.jsxs)(`label`,{className:`search-box`,children:[(0,N.jsx)(a,{size:16}),(0,N.jsx)(`input`,{value:e.filter.search,onChange:t=>e.onFilter({...e.filter,search:t.target.value}),placeholder:`搜索邮箱、账号 ID 或 Profile ID`})]}),(0,N.jsxs)(`select`,{className:`control`,value:e.filter.status,onChange:t=>e.onFilter({...e.filter,status:t.target.value}),children:[(0,N.jsx)(`option`,{value:`all`,children:`全部状态`}),(0,N.jsx)(`option`,{value:`available`,children:`可用`}),(0,N.jsx)(`option`,{value:`unavailable`,children:`不可用`}),(0,N.jsx)(`option`,{value:`active`,children:`使用中`}),(0,N.jsx)(`option`,{value:`api-active`,children:`API 使用中`}),(0,N.jsx)(`option`,{value:`codex-active`,children:`Codex 使用中`}),(0,N.jsx)(`option`,{value:`healthy`,children:`健康`}),(0,N.jsx)(`option`,{value:`warning`,children:`即将耗尽`}),(0,N.jsx)(`option`,{value:`unknown`,children:`待请求验证`}),(0,N.jsx)(`option`,{value:`exhausted`,children:`额度耗尽`}),(0,N.jsx)(`option`,{value:`invalid`,children:`登录/认证异常`}),(0,N.jsx)(`option`,{value:`login-invalid`,children:`登录失效`}),(0,N.jsx)(`option`,{value:`auth-error`,children:`认证异常`}),(0,N.jsx)(`option`,{value:`expired`,children:`已过期`}),(0,N.jsx)(`option`,{value:`free`,children:`Free`}),(0,N.jsx)(`option`,{value:`plus`,children:`Plus`}),(0,N.jsx)(`option`,{value:`pro-team`,children:`Pro/Team`}),(0,N.jsx)(`option`,{value:`auto-included`,children:`参与轮换`}),(0,N.jsx)(`option`,{value:`auto-excluded`,children:`排除轮换`})]}),(0,N.jsxs)(`select`,{className:`control`,value:e.filter.sort,onChange:t=>e.onFilter({...e.filter,sort:t.target.value}),children:[(0,N.jsx)(`option`,{value:`quota-desc`,children:`默认排序`}),(0,N.jsx)(`option`,{value:`latency-asc`,children:`按额度更新时间`}),(0,N.jsx)(`option`,{value:`expiry-asc`,children:`按过期时间`}),(0,N.jsx)(`option`,{value:`name-asc`,children:`按名称排序`}),(0,N.jsx)(`option`,{value:`quota-asc`,children:`按剩余额度升序`}),(0,N.jsx)(`option`,{value:`plan-desc`,children:`按套餐排序`}),(0,N.jsx)(`option`,{value:`email-asc`,children:`按邮箱排序`})]}),(0,N.jsxs)(`span`,{className:`account-selected-count`,children:[`已选择 `,e.selectedCount,` 个`]})]}),(0,N.jsx)(`div`,{className:`account-grid ${n}`,children:e.profiles.length===0?(0,N.jsx)(`div`,{className:`empty-state`,children:`还没有匹配的账号。可以新增账号或调整筛选条件。`}):e.profiles.map(n=>{let a=d(n),o=u(n),p=T(n),y=!!e.expandedProfiles[n.profileId],b=!!(t&&n.accountId===t),S=l(n,b),w=_(n),D=c(n),O=n.exportAudit,k=O?.exported?`已导出 ${O.count} 次`:`未导出`,M=typeof e.busy==`string`&&e.busy.startsWith(`profile:`)&&e.busy.endsWith(n.profileId),P=e.busy===`profile:sync-quota:${n.profileId}`;return(0,N.jsxs)(`article`,{className:`account-card plan-${g(n)} ${w?`is-auth-invalid`:``}`,"data-profile-card":n.profileId,title:w?x(n):void 0,children:[S&&(0,N.jsx)(`span`,{className:`usage-corner ${S.className}`,children:(0,N.jsx)(`span`,{children:S.label})}),(0,N.jsxs)(`div`,{className:`account-head`,children:[(0,N.jsxs)(`div`,{className:`account-title`,children:[(0,N.jsxs)(`div`,{className:`account-name`,children:[(0,N.jsx)(`span`,{className:`avatar`,children:v(n)}),(0,N.jsx)(`strong`,{children:h(n,e.showEmails)}),(0,N.jsx)(`button`,{"aria-label":`刷新额度`,className:`account-icon-btn`,disabled:M,onClick:()=>e.onAction(`sync-quota`,n),title:`刷新额度`,type:`button`,children:P?(0,N.jsx)(E,{className:`spin`,size:14}):(0,N.jsx)(i,{size:14})})]}),(0,N.jsxs)(`div`,{className:`badge-row`,children:[(0,N.jsx)(`span`,{className:`badge brand`,children:m(n)}),(0,N.jsx)(`span`,{className:`badge ${a.tone}`,children:a.label}),(0,N.jsx)(`span`,{className:`badge ${D.ok?`green`:`orange`}`,children:`gpt-image-2`}),(0,N.jsx)(`span`,{className:`badge ${O?.exported?`orange`:`muted`}`,children:k})]})]}),(0,N.jsxs)(`label`,{className:`account-select`,children:[(0,N.jsx)(`input`,{type:`checkbox`,checked:!!e.selectedProfiles[n.profileId],onChange:t=>e.onSelect(n.profileId,t.target.checked)}),(0,N.jsx)(`span`,{children:`选择`})]})]}),(0,N.jsxs)(`div`,{className:`account-metrics`,children:[(0,N.jsx)(L,{label:s(n,`primary`),value:o,tone:f(o)}),(0,N.jsx)(L,{label:s(n,`secondary`),value:p,tone:f(p)})]}),(0,N.jsxs)(`div`,{className:`usage-status-row`,children:[(0,N.jsxs)(`span`,{className:`usage-status ${n.isActive?`is-active`:``}`,children:[(0,N.jsx)(r,{size:14}),(0,N.jsx)(`span`,{children:`API`}),(0,N.jsx)(`span`,{className:`usage-dot ${n.isActive?`active`:``}`}),(0,N.jsx)(`span`,{className:`usage-state-text`,children:n.isActive?`使用中`:`未使用`})]}),(0,N.jsxs)(`span`,{className:`usage-status ${b?`is-active`:``}`,children:[(0,N.jsx)(A,{size:14}),(0,N.jsx)(`span`,{children:`Codex`}),(0,N.jsx)(`span`,{className:`usage-dot ${b?`active`:``}`}),(0,N.jsx)(`span`,{className:`usage-state-text`,children:b?`使用中`:`未使用`})]})]}),(0,N.jsxs)(`div`,{className:`compact-meta-row`,children:[(0,N.jsxs)(`div`,{className:`compact-reset-list`,children:[(0,N.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,N.jsx)(`label`,{children:s(n,`primary`)}),(0,N.jsx)(`strong`,{children:C(n,`primary`)})]}),(0,N.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,N.jsx)(`label`,{children:s(n,`secondary`)}),(0,N.jsx)(`strong`,{children:C(n,`secondary`)})]})]}),(0,N.jsx)(`div`,{className:`compact-meta-actions`,children:(0,N.jsxs)(`button`,{className:`details-toggle ${y?`is-expanded`:``}`,type:`button`,onClick:()=>e.onToggle(n.profileId),children:[(0,N.jsx)(`span`,{children:y?`收起详情`:`查看详情`}),(0,N.jsx)(I,{})]})})]}),y&&(0,N.jsxs)(`div`,{className:`meta-grid`,children:[(0,N.jsx)(j,{label:`套餐`,value:m(n)}),(0,N.jsx)(j,{label:`Account ID`,value:(e.showEmails,n.accountId),code:!0}),(0,N.jsx)(j,{label:`Profile ID`,value:(e.showEmails,n.profileId),code:!0}),(0,N.jsx)(j,{label:`认证状态`,value:x(n)}),(0,N.jsx)(j,{label:`生图能力`,value:D.ok?`gpt-image-2 可用`:D.detail}),(0,N.jsx)(j,{label:`导出记录`,value:F(O)}),(0,N.jsx)(j,{label:`过期时间`,value:n.expiresAt?new Date(n.expiresAt).toLocaleString(`zh-CN`):`-`}),(0,N.jsx)(j,{label:`额度快照`,value:n.quota?.capturedAt?new Date(n.quota.capturedAt).toLocaleString(`zh-CN`):`-`})]}),(0,N.jsxs)(`div`,{className:`account-actions`,children:[(0,N.jsx)(`button`,{className:`btn-secondary ${n.isActive?`is-current`:``}`,type:`button`,onClick:()=>e.onAction(`activate`,n),disabled:n.isActive||M||w,children:w?`网关不可用`:n.isActive?`网关使用中`:`应用网关`}),(0,N.jsx)(`button`,{className:`btn-secondary ${b?`is-current codex`:``}`,type:`button`,onClick:()=>e.onAction(`apply-codex`,n),disabled:b||M||w,children:w?`Codex 不可用`:b?`Codex 使用中`:`应用 Codex`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>e.onAction(`export`,n),disabled:M,children:`导出`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:()=>e.onAction(`remove`,n),disabled:M,children:`删除`})]})]},n.profileId)})})]})}function F(e){if(!e?.exported)return`未导出`;let t=e.lastExportKind===`single`?`单账号导出`:e.lastExportKind===`batch`?`批量导出`:`全部导出`;return`${e.count} 次,最近 ${o(e.lastExportedAt)},方式 ${t}`}function I(){return(0,N.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,"aria-hidden":`true`,children:(0,N.jsx)(`path`,{d:`m6 9 6 6 6-6`})})}function L(e){return(0,N.jsxs)(`div`,{className:`quota-row`,children:[(0,N.jsxs)(`div`,{className:`quota-line`,children:[(0,N.jsxs)(`span`,{children:[e.label,` · 已用 `,e.value,`% / 剩余 `,100-e.value,`%`]}),(0,N.jsxs)(`strong`,{children:[`剩余 `,100-e.value,`%`]})]}),(0,N.jsx)(`div`,{className:`progress-track`,children:(0,N.jsx)(`div`,{className:`progress-bar ${e.tone}`,style:{width:`${e.value}%`}})})]})}function R(e){let[t,n]=(0,M.useState)({}),[r,i]=(0,M.useState)({}),[a,o]=(0,M.useState)({search:``,status:`all`,sort:`quota-desc`}),s=(0,M.useMemo)(()=>{let t=e.config?.profiles?[...e.config.profiles]:[],n=new Set(e.config?.settings.autoSwitch.excludedProfileIds||[]),r=a.search.trim().toLowerCase(),i=t.filter(t=>{let i=[h(t,!0).toLowerCase(),t.accountId,t.profileId,t.email||``].join(` `).toLowerCase(),o=d(t),s=!!(e.codexAccountId&&t.accountId===e.codexAccountId),c=g(t);return r&&!i.includes(r)?!1:a.status===`active`?t.isActive||s:a.status===`healthy`?o.key===`healthy`:a.status===`warning`?o.key===`warning`:a.status===`unknown`?o.key===`unknown`:a.status===`exhausted`?o.key===`exhausted`:a.status===`expired`?o.key===`expired`:a.status===`invalid`?o.key===`invalid`:a.status===`login-invalid`?t.authStatus?.state===`token_invalidated`:a.status===`auth-error`?t.authStatus?.state===`auth_error`:a.status===`available`?o.key===`healthy`||o.key===`warning`||o.key===`unknown`:a.status===`unavailable`?o.key===`invalid`||o.key===`expired`||o.key===`exhausted`:a.status===`free`?c===`free`:a.status===`plus`?c===`plus`:a.status===`pro-team`?c===`pro`||c===`team`||c===`enterprise`||c===`premium`:a.status===`api-active`?t.isActive:a.status===`codex-active`?s:a.status===`auto-included`?!n.has(t.profileId):a.status===`auto-excluded`?n.has(t.profileId):!0});return i.sort((t,n)=>{let r=p(t,e.codexAccountId)-p(n,e.codexAccountId);if(r!==0)return r;let i=y(n)-y(t);if(i!==0)return i;let o=S(n)-S(t);return o===0?a.sort===`latency-asc`?(n.quota?.capturedAt||0)-(t.quota?.capturedAt||0):a.sort===`expiry-asc`?(t.expiresAt||2**53-1)-(n.expiresAt||2**53-1):a.sort===`name-asc`?h(t,!0).localeCompare(h(n,!0),`zh-CN`):a.sort===`quota-asc`?100-u(n)-(100-u(t)):a.sort===`plan-desc`?y(n)-y(t):a.sort===`email-asc`?h(t,!0).localeCompare(h(n,!0)):u(n)-u(t):o}),i},[a,e.codexAccountId,e.config?.profiles,e.config?.settings.autoSwitch.excludedProfileIds]),c=(0,M.useMemo)(()=>{let t=e.config?.profiles||[],n=new Set(e.config?.settings.autoSwitch.excludedProfileIds||[]),r=e=>t.filter(e).length,i=r(t=>!!(e.codexAccountId&&t.accountId===e.codexAccountId));return[{key:`all`,label:`总账号`,value:t.length,tone:`blue`},{key:`available`,label:`可用`,value:r(e=>[`healthy`,`warning`,`unknown`].includes(d(e).key)),tone:`green`},{key:`unavailable`,label:`不可用`,value:r(e=>[`invalid`,`expired`,`exhausted`].includes(d(e).key)),tone:`red`},{key:`unknown`,label:`待请求验证`,value:r(e=>d(e).key===`unknown`),tone:`blue`},{key:`login-invalid`,label:`登录失效`,value:r(e=>e.authStatus?.state===`token_invalidated`),tone:`red`},{key:`auth-error`,label:`认证异常`,value:r(e=>e.authStatus?.state===`auth_error`),tone:`red`},{key:`exhausted`,label:`额度耗尽`,value:r(e=>d(e).key===`exhausted`),tone:`orange`},{key:`free`,label:`Free`,value:r(e=>g(e)===`free`),tone:`muted`},{key:`plus`,label:`Plus`,value:r(e=>g(e)===`plus`),tone:`brand`},{key:`pro-team`,label:`Pro/Team`,value:r(e=>[`pro`,`team`,`enterprise`,`premium`].includes(g(e))),tone:`blue`},{key:`api-active`,label:`API 使用中`,value:r(e=>e.isActive),tone:`green`},{key:`codex-active`,label:`Codex 使用中`,value:i,tone:`green`},{key:`auto-included`,label:`参与轮换`,value:r(e=>!n.has(e.profileId)),tone:`blue`},{key:`auto-excluded`,label:`排除轮换`,value:r(e=>n.has(e.profileId)),tone:`orange`}]},[e.codexAccountId,e.config?.profiles,e.config?.settings.autoSwitch.excludedProfileIds]),l=Object.values(t).filter(Boolean).length,f=Object.keys(t).filter(e=>t[e]),m=(0,M.useMemo)(()=>s.map(e=>e.profileId),[s]);async function v(t,n){let r=await O(`/_gateway/admin/profiles/export`,{method:`POST`,headers:{"Content-Type":`application/json`},body:w(n?{profileIds:n}:{profileId:t})});D(`ai-zero-token-${n?`profiles-${n.length}`:t||`active`}.json`,r.profile),r.config?e.setConfig(r.config):await e.refreshConfig({silent:!0}),e.setStatus(n?`已导出 ${n.length} 个账号。`:`账号配置已导出。`)}async function x(t,n){if(!(t===`remove`&&!window.confirm(`确认删除 ${h(n,e.showEmails)}?`))){if((t===`activate`||t===`apply-codex`)&&_(n)){e.setStatus(`${h(n,e.showEmails)} 登录已失效,不能应用到${t===`activate`?`网关`:`Codex`}。`);return}if((t===`activate`||t===`apply-codex`)&&b(n)){let r=t===`activate`?`网关`:`Codex`;if(!window.confirm(`${h(n,e.showEmails)} 的额度看起来已耗尽,仍要应用到${r}吗?`))return}if(t===`export`){await v(n.profileId);return}e.setBusy(`profile:${t}:${n.profileId}`);try{let r=await O({activate:`/_gateway/admin/profiles/activate`,"apply-codex":`/_gateway/admin/codex/apply`,"sync-quota":`/_gateway/admin/profiles/sync-quota`,remove:`/_gateway/admin/profiles/remove`}[t],{method:`POST`,headers:{"Content-Type":`application/json`},body:w({profileId:n.profileId})}),i=`config`in r?r.config:r;if(e.setConfig(i),e.setStatus(t===`activate`?`已应用到网关。`:t===`apply-codex`?`已应用到本机 Codex。`:t===`sync-quota`?`额度信息已同步。`:`账号已删除。`),t===`apply-codex`)if(i.codexRestartSupported&&window.confirm(`Codex 账号已切换,是否现在重启 Codex 客户端?
|
|
2
|
+
|
|
3
|
+
Codex 通常在启动时读取本机 auth.json,重启后新账号会立即生效。`))try{await O(`/_gateway/admin/desktop/restart-codex`,{method:`POST`}),e.setStatus(`已应用到本机 Codex,并已重启 Codex 客户端。`)}catch(t){e.setStatus(`已应用到本机 Codex,但重启 Codex 失败: ${k(t)}`)}else e.setStatus(`已应用到本机 Codex,重启 Codex 客户端后生效。`)}catch(t){e.setStatus(k(t))}finally{e.setBusy(null)}}}async function C(){let t=f;if(t.length===0){e.setStatus(`请先勾选要删除的账号。`);return}let r=e.config?.profiles.filter(e=>t.includes(e.profileId)).slice(0,3).map(t=>h(t,e.showEmails)),i=r?.length?`\n\n${r.join(`
|
|
4
|
+
`)}${t.length>r.length?`\n等 ${t.length} 个账号`:``}`:``;if(window.confirm(`确认删除所选 ${t.length} 个账号?此操作不可撤销。${i}`)){e.setBusy(`bulk-remove`),e.setStatus(`正在删除 ${t.length} 个账号...`);try{let r=await O(`/_gateway/admin/profiles/remove-batch`,{method:`POST`,headers:{"Content-Type":`application/json`},body:w({profileIds:t})});e.setConfig(r),n({}),e.setStatus(`已删除 ${r.removedProfileCount??t.length} 个账号。`)}catch(t){e.setStatus(`删除所选失败: ${k(t)}`)}finally{e.setBusy(null)}}}function T(t,r){if(t.length===0){e.setStatus(`没有可选择的账号。`);return}n(e=>{let n={...e};for(let e of t)n[e]=!0;return n}),e.setStatus(r)}return(0,N.jsx)(P,{config:e.config,profiles:s,accountStats:c,showEmails:e.showEmails,filter:a,selectedProfiles:t,expandedProfiles:r,selectedCount:l,visibleCount:m.length,busy:e.busy,onFilter:o,onSelect:(e,t)=>n(n=>({...n,[e]:t})),onSelectVisible:()=>T(m,`已选择当前筛选结果 ${m.length} 个账号。`),onClearSelected:()=>{n({}),e.setStatus(`已取消选择。`)},onToggle:e=>i(t=>({...t,[e]:!t[e]})),onAction:x,onLocate:()=>e.activeProfile&&document.querySelector(`[data-profile-card="${e.activeProfile.profileId}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`}),onExportSelected:()=>{let t=f;if(t.length===0){e.setStatus(`请先勾选要导出的账号。`);return}v(void 0,t).catch(t=>e.setStatus(t instanceof Error?t.message:String(t)))},onRemoveSelected:()=>void C(),onAddAccount:()=>e.setAccountModalOpen(!0),onRefreshStatus:()=>e.refreshConfig({runtime:!0}),onClearAccounts:()=>e.logout()})}export{R as AccountsPage};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./server-BrjJPb9D.js";import{b as a,f as o,g as s,n as c,v as l,y as u}from"./index-
|
|
1
|
+
import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./server-BrjJPb9D.js";import{b as a,f as o,g as s,n as c,v as l,y as u}from"./index-CCiBaGwU.js";var d=t(`arrow-right`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`m12 5 7 7-7 7`,key:`xquz4c`}]]),f=`# AI-Zero-Token Local Gateway Skill
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./circle-check-ZYtn9GqY.js";import{t as a}from"./upload-CwXb7Q1b.js";import{C as o,S as s}from"./profiles-DMOjJORP.js";import{_ as c,b as l,n as u,o as d,p as f,r as p}from"./index-CdFYy5j6.js";var m=t(`chevron-down`,[[`path`,{d:`m6 9 6 6 6-6`,key:`qrunsl`}]]),h=t(`link-2`,[[`path`,{d:`M9 17H7A5 5 0 0 1 7 7h2`,key:`8i5ue5`}],[`path`,{d:`M15 7h2a5 5 0 1 1 0 10h-2`,key:`1b9ql8`}],[`line`,{x1:`8`,x2:`16`,y1:`12`,y2:`12`,key:`1jonct`}]]),g=t(`trash-2`,[[`path`,{d:`M10 11v6`,key:`nco0om`}],[`path`,{d:`M14 11v6`,key:`outv1u`}],[`path`,{d:`M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6`,key:`miytrc`}],[`path`,{d:`M3 6h18`,key:`d0wm0j`}],[`path`,{d:`M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2`,key:`e791ji`}]]),_=e(n(),1),v=r();function y(e,t){return e?`${e.owner}/${e.repository} · ${e.branch}`:t?.hasToken?`已保存,待验证`:`尚未保存 token`}function b(e,t){if(!e)return t;try{let t=JSON.parse(e);if(typeof t.error?.message==`string`&&t.error.message.trim())return t.error.message}catch{}return e||t}async function x(e,t){let n=await d(e);return new Promise((r,i)=>{let a=new XMLHttpRequest;a.open(`POST`,`/_gateway/image-bed/upload`),a.setRequestHeader(`Content-Type`,`application/json`),a.responseType=`text`,a.upload.onprogress=e=>{e.lengthComputable&&e.total>0&&t(e.loaded/e.total*100)},a.onerror=()=>i(Error(`上传请求失败。`)),a.onload=()=>{let e=a.responseText||``;if(a.status<200||a.status>=300){i(Error(b(e,`HTTP ${a.status}`)));return}try{r(JSON.parse(e))}catch{i(Error(`上传响应解析失败。`))}},a.send(JSON.stringify({filename:e.name,dataUrl:n}))})}function S(e){let[t,n]=(0,_.useState)(null),[r,d]=(0,_.useState)(``),[b,S]=(0,_.useState)(!0),[C,w]=(0,_.useState)(null),[T,E]=(0,_.useState)([]),[D,O]=(0,_.useState)(12),[k,A]=(0,_.useState)(`正在读取图床配置...`),[j,M]=(0,_.useState)(!1),[N,P]=(0,_.useState)(null),[F,I]=(0,_.useState)(null),L=(0,_.useRef)(null),R=(0,_.useMemo)(()=>y(C,t),[C,t]),z=(0,_.useMemo)(()=>T.slice(0,D),[T,D]);(0,_.useEffect)(()=>{let e=!0;async function t(){try{let t=await f(`/_gateway/image-bed/config`);if(!e)return;n(t),S(!t.hasToken),A(t.hasToken?`GitHub token 已保存,正在验证连接...`:`请先保存一个 GitHub token。`),t.hasToken&&await V(!1)}catch(t){if(!e)return;A(p(t))}try{let t=await f(`/_gateway/image-bed/history?limit=100`);if(!e)return;E(t.items)}catch(t){if(!e)return;A(e=>`${e} ${p(t)}`)}}return t(),()=>{e=!1}},[]);async function B(){E((await f(`/_gateway/image-bed/history?limit=100`)).items)}async function V(t=!0){t&&e.setBusy(`image-bed-save`);try{let t=await f(`/_gateway/image-bed/validate`,{method:`POST`});return w(t),A(`已连接到 ${t.owner}/${t.repository},默认分支 ${t.branch}。`),e.setStatus(`图床连接正常:${t.owner}/${t.repository}`),t}catch(t){let n=p(t);throw w(null),A(`连接失败: ${n}`),e.setStatus(n),t}finally{t&&e.setBusy(null)}}async function H(){let t=r.trim();if(!t){A(`请先填写 GitHub token。`);return}e.setBusy(`image-bed-save`);try{n(await f(`/_gateway/image-bed/config`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({token:t})})),d(``),S(!1),A(`GitHub token 已保存,正在验证...`),e.setStatus(`GitHub token 已保存。`);try{await V(!1),A(`GitHub token 已保存并验证。`)}catch(e){S(!0),A(`已保存,但验证失败: ${p(e)}`)}}catch(t){let n=p(t);A(`保存失败: ${n}`),e.setStatus(n)}finally{e.setBusy(null)}}async function U(){e.setBusy(`image-bed-save`);try{n(await f(`/_gateway/image-bed/config`,{method:`DELETE`})),w(null),d(``),S(!0),A(`GitHub token 已清除。`),e.setStatus(`GitHub token 已清除。`)}catch(t){let n=p(t);A(`清除失败: ${n}`),e.setStatus(n)}finally{e.setBusy(null)}}async function W(){S(!0),d(``)}async function G(e,t){if(!e.type.startsWith(`image/`))throw Error(`文件 ${e.name} 不是图片。`);return x(e,t)}async function K(n){let r=Array.from(n).filter(e=>e.type.startsWith(`image/`));if(r.length===0){A(`请选择图片文件。`);return}if(!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}e.setBusy(`image-bed-upload`);try{let t=[];for(let n=0;n<r.length;n+=1){let i=r[n];P({phase:`reading`,fileName:i.name,fileIndex:n+1,totalFiles:r.length,percent:n/r.length*100}),A(`正在读取 ${n+1}/${r.length}: ${i.name}`),e.setStatus(`正在读取 ${n+1}/${r.length}: ${i.name}`);let a=await G(i,e=>{P({phase:`uploading`,fileName:i.name,fileIndex:n+1,totalFiles:r.length,percent:(n+e/100)/r.length*100})}),o={id:a.path,createdAt:Date.now(),filename:a.filename,path:a.path,url:a.url,htmlUrl:a.htmlUrl,downloadUrl:a.downloadUrl,owner:a.owner,repository:a.repository,branch:a.branch,size:a.size,mimeType:a.mimeType,previewUrl:a.url,sha:a.sha};t.unshift(o),E(e=>[o,...e.filter(e=>e.id!==o.id)].slice(0,100))}O(e=>Math.max(e,Math.min(12,T.length+t.length))),A(`已上传 ${r.length} 张图片。`),e.setStatus(`已上传 ${r.length} 张图片。`),await B()}catch(t){let n=p(t);A(`上传失败: ${n}`),e.setStatus(n)}finally{P(null),e.setBusy(null)}}async function q(t){let n=await u(t)?`链接已复制。`:`链接复制失败。`;A(n),e.setStatus(n)}function J(e){let t=Array.from(e.currentTarget.files||[]);e.currentTarget.value=``,t.length!==0&&K(t)}async function Y(){await f(`/_gateway/image-bed/history`,{method:`DELETE`}),E([]),O(12),A(`历史记录已清空。`),e.setStatus(`历史记录已清空。`)}async function X(t){if(window.confirm(`确认从 GitHub 仓库删除 ${t.filename} 吗?删除后原链接会失效。`)){I(t.id),e.setBusy(`image-bed-delete`);try{E((await f(`/_gateway/image-bed/history/${encodeURIComponent(t.id)}`,{method:`DELETE`})).items),A(`已删除 ${t.filename}。`),e.setStatus(`已从图床删除 ${t.filename}。`)}catch(t){let n=p(t);A(`删除失败: ${n}`),e.setStatus(n)}finally{I(null),e.setBusy(null)}}}let Z=e.busy===`image-bed-save`||e.busy===`image-bed-upload`||e.busy===`image-bed-delete`,Q=T[0];return(0,v.jsxs)(`section`,{className:`image-bed-page`,children:[(0,v.jsxs)(`div`,{className:`image-bed-workbench`,children:[(0,v.jsxs)(`section`,{className:`image-bed-upload-panel ${j?`is-dragging`:``}`,children:[(0,v.jsxs)(`div`,{className:`image-bed-upload-copy`,children:[(0,v.jsx)(`span`,{children:t?.hasToken?`GitHub 图床已准备`:`先配置 GitHub token`}),(0,v.jsx)(`h2`,{children:`拖入图片,直接拿公网链接`}),(0,v.jsxs)(`p`,{children:[`文件会写入公开仓库 `,(0,v.jsx)(`strong`,{children:t?.repository||`azt-img-bed`}),` 的 `,(0,v.jsx)(`strong`,{children:t?.pathPrefix||`images`}),` 目录,上传结果会保存在本机历史里。`]})]}),(0,v.jsxs)(`div`,{className:`upload-dropzone ${j?`is-dragging`:``} ${t?.hasToken?``:`is-disabled`}`,role:`button`,tabIndex:0,onClick:()=>{if(!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}L.current&&(L.current.value=``,L.current.click())},onKeyDown:e=>{if(e.key===`Enter`||e.key===` `){if(e.preventDefault(),!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}L.current&&(L.current.value=``,L.current.click())}},onDragOver:e=>{e.preventDefault(),M(!0)},onDragLeave:()=>M(!1),onDrop:e=>{e.preventDefault(),M(!1),K(e.dataTransfer.files)},children:[(0,v.jsx)(`input`,{ref:L,className:`upload-dropzone-input`,type:`file`,accept:`image/*`,multiple:!0,onChange:J}),(0,v.jsx)(`div`,{className:`upload-dropzone-icon`,children:(0,v.jsx)(a,{size:22})}),(0,v.jsx)(`strong`,{children:`选择图片或拖拽到这里`}),(0,v.jsx)(`span`,{children:t?.hasToken?`支持批量上传,完成后自动生成可访问链接。`:`保存并验证 token 后即可上传。`})]}),N?(0,v.jsxs)(`div`,{className:`upload-progress-block`,"aria-live":`polite`,children:[(0,v.jsxs)(`div`,{className:`upload-progress-head`,children:[(0,v.jsx)(`strong`,{children:N.phase===`reading`?`正在读取`:`正在上传`}),(0,v.jsxs)(`span`,{children:[N.fileIndex,`/`,N.totalFiles,` · `,N.fileName]})]}),(0,v.jsx)(`div`,{className:`upload-progress-track`,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(N.percent),children:(0,v.jsx)(`div`,{className:`upload-progress-fill`,style:{width:`${Math.max(4,Math.min(100,N.percent))}%`}})})]}):Q?(0,v.jsxs)(`div`,{className:`image-bed-latest`,children:[(0,v.jsx)(`button`,{type:`button`,className:`image-bed-latest-preview`,onClick:()=>void q(Q.url),title:`点击复制链接`,children:(0,v.jsx)(`img`,{loading:`lazy`,decoding:`async`,src:Q.previewUrl,alt:Q.filename})}),(0,v.jsxs)(`div`,{className:`image-bed-latest-info`,children:[(0,v.jsx)(`span`,{children:`最近上传`}),(0,v.jsx)(`strong`,{children:Q.filename}),(0,v.jsx)(`code`,{children:Q.url})]}),(0,v.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void q(Q.url),children:[(0,v.jsx)(l,{size:16}),`复制链接`]})]}):(0,v.jsx)(`div`,{className:`image-bed-upload-note`,children:k})]}),(0,v.jsxs)(`aside`,{className:`image-bed-side-stack`,children:[(0,v.jsxs)(`section`,{className:`image-bed-side-card`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsx)(`h4`,{children:`连接`}),(0,v.jsx)(`span`,{className:`image-bed-status-dot ${C?`is-ok`:t?.hasToken?`is-warn`:``}`})]}),(0,v.jsx)(`strong`,{className:`image-bed-connection-label`,children:R}),(0,v.jsx)(`p`,{children:k})]}),(0,v.jsxs)(`section`,{className:`image-bed-side-card`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsx)(`h4`,{children:`Token`}),!b&&t?.hasToken&&(0,v.jsx)(`button`,{className:`image-bed-link-button`,type:`button`,onClick:()=>void W(),children:`修改`})]}),b||!t?.hasToken?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`GitHub token`}),(0,v.jsx)(`input`,{className:`input`,value:r,onChange:e=>d(e.target.value),placeholder:`github_pat_...`,spellCheck:!1,autoComplete:`off`}),(0,v.jsxs)(`p`,{className:`image-bed-token-hint`,children:[`推荐使用 fine-grained token,只给公开仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),` 的 Contents 读写权限。`]})]}),(0,v.jsxs)(`div`,{className:`image-bed-token-actions`,children:[(0,v.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void H(),disabled:Z||!r.trim(),children:[e.busy===`image-bed-save`?(0,v.jsx)(c,{className:`spin`,size:16}):(0,v.jsx)(i,{size:16}),`保存并验证`]}),(0,v.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:U,disabled:!t?.hasToken,children:[(0,v.jsx)(g,{size:16}),`清除`]})]})]}):(0,v.jsxs)(`div`,{className:`image-bed-token-summary`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`已保存到本机`}),(0,v.jsx)(`strong`,{children:`GitHub token`})]}),(0,v.jsx)(`button`,{className:`btn-secondary icon-only`,type:`button`,onClick:U,title:`清除 Token`,children:(0,v.jsx)(g,{size:16})})]})]}),(0,v.jsxs)(`section`,{className:`image-bed-side-card image-bed-target-card`,children:[(0,v.jsx)(`h4`,{children:`目标`}),(0,v.jsxs)(`div`,{className:`image-bed-target-list`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`仓库`}),(0,v.jsx)(`strong`,{children:t?.repository||`azt-img-bed`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`分支`}),(0,v.jsx)(`strong`,{children:t?.defaultBranch||`auto`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`目录`}),(0,v.jsx)(`strong`,{children:t?.pathPrefix||`images`})]})]})]})]})]}),(0,v.jsxs)(`section`,{className:`image-bed-gallery-section`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`h4`,{children:`上传历史`}),(0,v.jsx)(`p`,{children:`本机保存最近 100 条,只加载当前可见缩略图。`})]}),(0,v.jsx)(`button`,{className:`btn-secondary icon-only`,type:`button`,onClick:()=>void Y(),disabled:T.length===0,title:`清空历史`,children:(0,v.jsx)(g,{size:16})})]}),T.length===0?(0,v.jsx)(`div`,{className:`image-bed-empty`,children:`还没有上传记录。上传完成后,这里会以图库形式展示预览和链接。`}):(0,v.jsx)(`div`,{className:`image-bed-results-grid`,children:z.map(e=>(0,v.jsxs)(`figure`,{className:`image-bed-result-card`,children:[(0,v.jsx)(`button`,{type:`button`,className:`image-bed-preview-button`,onClick:()=>void q(e.url),title:`点击复制链接`,children:(0,v.jsx)(`img`,{loading:`lazy`,decoding:`async`,src:e.previewUrl,alt:e.filename})}),(0,v.jsxs)(`figcaption`,{children:[(0,v.jsx)(`strong`,{children:e.filename}),(0,v.jsxs)(`span`,{children:[s(e.size),` · `,e.mimeType]}),(0,v.jsx)(`code`,{children:o(e.createdAt)})]}),(0,v.jsxs)(`div`,{className:`image-bed-result-actions`,children:[(0,v.jsx)(`button`,{className:`image-bed-card-action`,type:`button`,onClick:()=>void q(e.url),title:`复制链接`,"aria-label":`复制链接`,children:(0,v.jsx)(l,{size:15})}),(0,v.jsx)(`a`,{className:`image-bed-card-action`,href:e.url,target:`_blank`,rel:`noreferrer`,title:`打开原图`,"aria-label":`打开原图`,children:(0,v.jsx)(h,{size:15})}),(0,v.jsx)(`button`,{className:`image-bed-card-action is-danger`,type:`button`,onClick:()=>void X(e),title:`删除图床文件`,"aria-label":`删除图床文件`,disabled:F===e.id,children:F===e.id?(0,v.jsx)(c,{className:`spin`,size:15}):(0,v.jsx)(g,{size:15})})]})]},e.path))}),T.length>D&&(0,v.jsxs)(`button`,{className:`btn-secondary image-bed-load-more`,type:`button`,onClick:()=>O(e=>Math.min(e+12,T.length)),children:[(0,v.jsx)(m,{size:16}),`加载更多`]})]}),(0,v.jsxs)(`details`,{className:`image-bed-help-section`,children:[(0,v.jsx)(`summary`,{children:`GitHub token 创建说明`}),(0,v.jsxs)(`p`,{className:`image-bed-help-intro`,children:[`这个图床固定使用当前 GitHub 账号下的公开仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`,图片会写入 `,(0,v.jsx)(`code`,{children:`images`}),` 目录并返回 raw.githubusercontent.com 原图链接。`]}),(0,v.jsxs)(`ol`,{className:`image-bed-steps`,children:[(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`先建公开仓库。`}),` 在 GitHub 新建仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`,Visibility 选 `,(0,v.jsx)(`strong`,{children:`Public`}),`。如果仓库已经存在,确认它属于这个 token 对应的个人账号,并且不是 Private。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`创建 fine-grained token。`}),` 打开 GitHub 的 `,(0,v.jsx)(`a`,{href:`https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens`,target:`_blank`,rel:`noreferrer`,children:`Personal access tokens`}),` 页面,选择 `,(0,v.jsx)(`strong`,{children:`Fine-grained tokens`}),`,点 `,(0,v.jsx)(`strong`,{children:`Generate new token`}),`。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`限制仓库范围。`}),` Token name 可填 `,(0,v.jsx)(`code`,{children:`AI Zero Token image bed`}),`,Expiration 按需设置,Resource owner 选自己的账号;Repository access 选择 `,(0,v.jsx)(`strong`,{children:`Only select repositories`}),`,只勾选 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`给最小权限。`}),` 在 Repository permissions 里把 `,(0,v.jsx)(`strong`,{children:`Contents`}),` 设置为 `,(0,v.jsx)(`strong`,{children:`Read and write`}),`;`,(0,v.jsx)(`strong`,{children:`Metadata`}),` 保持默认 Read-only 即可,其它权限不需要打开。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`复制并验证。`}),` GitHub 只会展示一次生成后的 token,复制后回到这里粘贴,点击 `,(0,v.jsx)(`strong`,{children:`保存并验证`}),`。验证通过后就可以拖图上传。`]})]}),(0,v.jsxs)(`dl`,{className:`image-bed-help-facts`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`dt`,{children:`Token 格式`}),(0,v.jsxs)(`dd`,{children:[`fine-grained token 通常以 `,(0,v.jsx)(`code`,{children:`github_pat_`}),` 开头。`]})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`dt`,{children:`验证失败`}),(0,v.jsxs)(`dd`,{children:[`如果提示未找到仓库,优先检查仓库名是否为 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`、仓库是否 Public、Repository access 是否选中了这个仓库。`]})]})]}),(0,v.jsx)(`p`,{className:`hint`,children:`Token 要像密码一样保管,不要发给别人。这个页面只会把它保存到你本机状态目录。`})]})]})}export{S as ImageBedPage};
|
|
1
|
+
import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./circle-check-ZYtn9GqY.js";import{t as a}from"./upload-CwXb7Q1b.js";import{C as o,S as s}from"./profiles-C5SmQvju.js";import{_ as c,b as l,n as u,o as d,p as f,r as p}from"./index-CCiBaGwU.js";var m=t(`chevron-down`,[[`path`,{d:`m6 9 6 6 6-6`,key:`qrunsl`}]]),h=t(`link-2`,[[`path`,{d:`M9 17H7A5 5 0 0 1 7 7h2`,key:`8i5ue5`}],[`path`,{d:`M15 7h2a5 5 0 1 1 0 10h-2`,key:`1b9ql8`}],[`line`,{x1:`8`,x2:`16`,y1:`12`,y2:`12`,key:`1jonct`}]]),g=t(`trash-2`,[[`path`,{d:`M10 11v6`,key:`nco0om`}],[`path`,{d:`M14 11v6`,key:`outv1u`}],[`path`,{d:`M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6`,key:`miytrc`}],[`path`,{d:`M3 6h18`,key:`d0wm0j`}],[`path`,{d:`M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2`,key:`e791ji`}]]),_=e(n(),1),v=r();function y(e,t){return e?`${e.owner}/${e.repository} · ${e.branch}`:t?.hasToken?`已保存,待验证`:`尚未保存 token`}function b(e,t){if(!e)return t;try{let t=JSON.parse(e);if(typeof t.error?.message==`string`&&t.error.message.trim())return t.error.message}catch{}return e||t}async function x(e,t){let n=await d(e);return new Promise((r,i)=>{let a=new XMLHttpRequest;a.open(`POST`,`/_gateway/image-bed/upload`),a.setRequestHeader(`Content-Type`,`application/json`),a.responseType=`text`,a.upload.onprogress=e=>{e.lengthComputable&&e.total>0&&t(e.loaded/e.total*100)},a.onerror=()=>i(Error(`上传请求失败。`)),a.onload=()=>{let e=a.responseText||``;if(a.status<200||a.status>=300){i(Error(b(e,`HTTP ${a.status}`)));return}try{r(JSON.parse(e))}catch{i(Error(`上传响应解析失败。`))}},a.send(JSON.stringify({filename:e.name,dataUrl:n}))})}function S(e){let[t,n]=(0,_.useState)(null),[r,d]=(0,_.useState)(``),[b,S]=(0,_.useState)(!0),[C,w]=(0,_.useState)(null),[T,E]=(0,_.useState)([]),[D,O]=(0,_.useState)(12),[k,A]=(0,_.useState)(`正在读取图床配置...`),[j,M]=(0,_.useState)(!1),[N,P]=(0,_.useState)(null),[F,I]=(0,_.useState)(null),L=(0,_.useRef)(null),R=(0,_.useMemo)(()=>y(C,t),[C,t]),z=(0,_.useMemo)(()=>T.slice(0,D),[T,D]);(0,_.useEffect)(()=>{let e=!0;async function t(){try{let t=await f(`/_gateway/image-bed/config`);if(!e)return;n(t),S(!t.hasToken),A(t.hasToken?`GitHub token 已保存,正在验证连接...`:`请先保存一个 GitHub token。`),t.hasToken&&await V(!1)}catch(t){if(!e)return;A(p(t))}try{let t=await f(`/_gateway/image-bed/history?limit=100`);if(!e)return;E(t.items)}catch(t){if(!e)return;A(e=>`${e} ${p(t)}`)}}return t(),()=>{e=!1}},[]);async function B(){E((await f(`/_gateway/image-bed/history?limit=100`)).items)}async function V(t=!0){t&&e.setBusy(`image-bed-save`);try{let t=await f(`/_gateway/image-bed/validate`,{method:`POST`});return w(t),A(`已连接到 ${t.owner}/${t.repository},默认分支 ${t.branch}。`),e.setStatus(`图床连接正常:${t.owner}/${t.repository}`),t}catch(t){let n=p(t);throw w(null),A(`连接失败: ${n}`),e.setStatus(n),t}finally{t&&e.setBusy(null)}}async function H(){let t=r.trim();if(!t){A(`请先填写 GitHub token。`);return}e.setBusy(`image-bed-save`);try{n(await f(`/_gateway/image-bed/config`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({token:t})})),d(``),S(!1),A(`GitHub token 已保存,正在验证...`),e.setStatus(`GitHub token 已保存。`);try{await V(!1),A(`GitHub token 已保存并验证。`)}catch(e){S(!0),A(`已保存,但验证失败: ${p(e)}`)}}catch(t){let n=p(t);A(`保存失败: ${n}`),e.setStatus(n)}finally{e.setBusy(null)}}async function U(){e.setBusy(`image-bed-save`);try{n(await f(`/_gateway/image-bed/config`,{method:`DELETE`})),w(null),d(``),S(!0),A(`GitHub token 已清除。`),e.setStatus(`GitHub token 已清除。`)}catch(t){let n=p(t);A(`清除失败: ${n}`),e.setStatus(n)}finally{e.setBusy(null)}}async function W(){S(!0),d(``)}async function G(e,t){if(!e.type.startsWith(`image/`))throw Error(`文件 ${e.name} 不是图片。`);return x(e,t)}async function K(n){let r=Array.from(n).filter(e=>e.type.startsWith(`image/`));if(r.length===0){A(`请选择图片文件。`);return}if(!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}e.setBusy(`image-bed-upload`);try{let t=[];for(let n=0;n<r.length;n+=1){let i=r[n];P({phase:`reading`,fileName:i.name,fileIndex:n+1,totalFiles:r.length,percent:n/r.length*100}),A(`正在读取 ${n+1}/${r.length}: ${i.name}`),e.setStatus(`正在读取 ${n+1}/${r.length}: ${i.name}`);let a=await G(i,e=>{P({phase:`uploading`,fileName:i.name,fileIndex:n+1,totalFiles:r.length,percent:(n+e/100)/r.length*100})}),o={id:a.path,createdAt:Date.now(),filename:a.filename,path:a.path,url:a.url,htmlUrl:a.htmlUrl,downloadUrl:a.downloadUrl,owner:a.owner,repository:a.repository,branch:a.branch,size:a.size,mimeType:a.mimeType,previewUrl:a.url,sha:a.sha};t.unshift(o),E(e=>[o,...e.filter(e=>e.id!==o.id)].slice(0,100))}O(e=>Math.max(e,Math.min(12,T.length+t.length))),A(`已上传 ${r.length} 张图片。`),e.setStatus(`已上传 ${r.length} 张图片。`),await B()}catch(t){let n=p(t);A(`上传失败: ${n}`),e.setStatus(n)}finally{P(null),e.setBusy(null)}}async function q(t){let n=await u(t)?`链接已复制。`:`链接复制失败。`;A(n),e.setStatus(n)}function J(e){let t=Array.from(e.currentTarget.files||[]);e.currentTarget.value=``,t.length!==0&&K(t)}async function Y(){await f(`/_gateway/image-bed/history`,{method:`DELETE`}),E([]),O(12),A(`历史记录已清空。`),e.setStatus(`历史记录已清空。`)}async function X(t){if(window.confirm(`确认从 GitHub 仓库删除 ${t.filename} 吗?删除后原链接会失效。`)){I(t.id),e.setBusy(`image-bed-delete`);try{E((await f(`/_gateway/image-bed/history/${encodeURIComponent(t.id)}`,{method:`DELETE`})).items),A(`已删除 ${t.filename}。`),e.setStatus(`已从图床删除 ${t.filename}。`)}catch(t){let n=p(t);A(`删除失败: ${n}`),e.setStatus(n)}finally{I(null),e.setBusy(null)}}}let Z=e.busy===`image-bed-save`||e.busy===`image-bed-upload`||e.busy===`image-bed-delete`,Q=T[0];return(0,v.jsxs)(`section`,{className:`image-bed-page`,children:[(0,v.jsxs)(`div`,{className:`image-bed-workbench`,children:[(0,v.jsxs)(`section`,{className:`image-bed-upload-panel ${j?`is-dragging`:``}`,children:[(0,v.jsxs)(`div`,{className:`image-bed-upload-copy`,children:[(0,v.jsx)(`span`,{children:t?.hasToken?`GitHub 图床已准备`:`先配置 GitHub token`}),(0,v.jsx)(`h2`,{children:`拖入图片,直接拿公网链接`}),(0,v.jsxs)(`p`,{children:[`文件会写入公开仓库 `,(0,v.jsx)(`strong`,{children:t?.repository||`azt-img-bed`}),` 的 `,(0,v.jsx)(`strong`,{children:t?.pathPrefix||`images`}),` 目录,上传结果会保存在本机历史里。`]})]}),(0,v.jsxs)(`div`,{className:`upload-dropzone ${j?`is-dragging`:``} ${t?.hasToken?``:`is-disabled`}`,role:`button`,tabIndex:0,onClick:()=>{if(!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}L.current&&(L.current.value=``,L.current.click())},onKeyDown:e=>{if(e.key===`Enter`||e.key===` `){if(e.preventDefault(),!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}L.current&&(L.current.value=``,L.current.click())}},onDragOver:e=>{e.preventDefault(),M(!0)},onDragLeave:()=>M(!1),onDrop:e=>{e.preventDefault(),M(!1),K(e.dataTransfer.files)},children:[(0,v.jsx)(`input`,{ref:L,className:`upload-dropzone-input`,type:`file`,accept:`image/*`,multiple:!0,onChange:J}),(0,v.jsx)(`div`,{className:`upload-dropzone-icon`,children:(0,v.jsx)(a,{size:22})}),(0,v.jsx)(`strong`,{children:`选择图片或拖拽到这里`}),(0,v.jsx)(`span`,{children:t?.hasToken?`支持批量上传,完成后自动生成可访问链接。`:`保存并验证 token 后即可上传。`})]}),N?(0,v.jsxs)(`div`,{className:`upload-progress-block`,"aria-live":`polite`,children:[(0,v.jsxs)(`div`,{className:`upload-progress-head`,children:[(0,v.jsx)(`strong`,{children:N.phase===`reading`?`正在读取`:`正在上传`}),(0,v.jsxs)(`span`,{children:[N.fileIndex,`/`,N.totalFiles,` · `,N.fileName]})]}),(0,v.jsx)(`div`,{className:`upload-progress-track`,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(N.percent),children:(0,v.jsx)(`div`,{className:`upload-progress-fill`,style:{width:`${Math.max(4,Math.min(100,N.percent))}%`}})})]}):Q?(0,v.jsxs)(`div`,{className:`image-bed-latest`,children:[(0,v.jsx)(`button`,{type:`button`,className:`image-bed-latest-preview`,onClick:()=>void q(Q.url),title:`点击复制链接`,children:(0,v.jsx)(`img`,{loading:`lazy`,decoding:`async`,src:Q.previewUrl,alt:Q.filename})}),(0,v.jsxs)(`div`,{className:`image-bed-latest-info`,children:[(0,v.jsx)(`span`,{children:`最近上传`}),(0,v.jsx)(`strong`,{children:Q.filename}),(0,v.jsx)(`code`,{children:Q.url})]}),(0,v.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void q(Q.url),children:[(0,v.jsx)(l,{size:16}),`复制链接`]})]}):(0,v.jsx)(`div`,{className:`image-bed-upload-note`,children:k})]}),(0,v.jsxs)(`aside`,{className:`image-bed-side-stack`,children:[(0,v.jsxs)(`section`,{className:`image-bed-side-card`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsx)(`h4`,{children:`连接`}),(0,v.jsx)(`span`,{className:`image-bed-status-dot ${C?`is-ok`:t?.hasToken?`is-warn`:``}`})]}),(0,v.jsx)(`strong`,{className:`image-bed-connection-label`,children:R}),(0,v.jsx)(`p`,{children:k})]}),(0,v.jsxs)(`section`,{className:`image-bed-side-card`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsx)(`h4`,{children:`Token`}),!b&&t?.hasToken&&(0,v.jsx)(`button`,{className:`image-bed-link-button`,type:`button`,onClick:()=>void W(),children:`修改`})]}),b||!t?.hasToken?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`GitHub token`}),(0,v.jsx)(`input`,{className:`input`,value:r,onChange:e=>d(e.target.value),placeholder:`github_pat_...`,spellCheck:!1,autoComplete:`off`}),(0,v.jsxs)(`p`,{className:`image-bed-token-hint`,children:[`推荐使用 fine-grained token,只给公开仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),` 的 Contents 读写权限。`]})]}),(0,v.jsxs)(`div`,{className:`image-bed-token-actions`,children:[(0,v.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void H(),disabled:Z||!r.trim(),children:[e.busy===`image-bed-save`?(0,v.jsx)(c,{className:`spin`,size:16}):(0,v.jsx)(i,{size:16}),`保存并验证`]}),(0,v.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:U,disabled:!t?.hasToken,children:[(0,v.jsx)(g,{size:16}),`清除`]})]})]}):(0,v.jsxs)(`div`,{className:`image-bed-token-summary`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`已保存到本机`}),(0,v.jsx)(`strong`,{children:`GitHub token`})]}),(0,v.jsx)(`button`,{className:`btn-secondary icon-only`,type:`button`,onClick:U,title:`清除 Token`,children:(0,v.jsx)(g,{size:16})})]})]}),(0,v.jsxs)(`section`,{className:`image-bed-side-card image-bed-target-card`,children:[(0,v.jsx)(`h4`,{children:`目标`}),(0,v.jsxs)(`div`,{className:`image-bed-target-list`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`仓库`}),(0,v.jsx)(`strong`,{children:t?.repository||`azt-img-bed`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`分支`}),(0,v.jsx)(`strong`,{children:t?.defaultBranch||`auto`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`目录`}),(0,v.jsx)(`strong`,{children:t?.pathPrefix||`images`})]})]})]})]})]}),(0,v.jsxs)(`section`,{className:`image-bed-gallery-section`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`h4`,{children:`上传历史`}),(0,v.jsx)(`p`,{children:`本机保存最近 100 条,只加载当前可见缩略图。`})]}),(0,v.jsx)(`button`,{className:`btn-secondary icon-only`,type:`button`,onClick:()=>void Y(),disabled:T.length===0,title:`清空历史`,children:(0,v.jsx)(g,{size:16})})]}),T.length===0?(0,v.jsx)(`div`,{className:`image-bed-empty`,children:`还没有上传记录。上传完成后,这里会以图库形式展示预览和链接。`}):(0,v.jsx)(`div`,{className:`image-bed-results-grid`,children:z.map(e=>(0,v.jsxs)(`figure`,{className:`image-bed-result-card`,children:[(0,v.jsx)(`button`,{type:`button`,className:`image-bed-preview-button`,onClick:()=>void q(e.url),title:`点击复制链接`,children:(0,v.jsx)(`img`,{loading:`lazy`,decoding:`async`,src:e.previewUrl,alt:e.filename})}),(0,v.jsxs)(`figcaption`,{children:[(0,v.jsx)(`strong`,{children:e.filename}),(0,v.jsxs)(`span`,{children:[s(e.size),` · `,e.mimeType]}),(0,v.jsx)(`code`,{children:o(e.createdAt)})]}),(0,v.jsxs)(`div`,{className:`image-bed-result-actions`,children:[(0,v.jsx)(`button`,{className:`image-bed-card-action`,type:`button`,onClick:()=>void q(e.url),title:`复制链接`,"aria-label":`复制链接`,children:(0,v.jsx)(l,{size:15})}),(0,v.jsx)(`a`,{className:`image-bed-card-action`,href:e.url,target:`_blank`,rel:`noreferrer`,title:`打开原图`,"aria-label":`打开原图`,children:(0,v.jsx)(h,{size:15})}),(0,v.jsx)(`button`,{className:`image-bed-card-action is-danger`,type:`button`,onClick:()=>void X(e),title:`删除图床文件`,"aria-label":`删除图床文件`,disabled:F===e.id,children:F===e.id?(0,v.jsx)(c,{className:`spin`,size:15}):(0,v.jsx)(g,{size:15})})]})]},e.path))}),T.length>D&&(0,v.jsxs)(`button`,{className:`btn-secondary image-bed-load-more`,type:`button`,onClick:()=>O(e=>Math.min(e+12,T.length)),children:[(0,v.jsx)(m,{size:16}),`加载更多`]})]}),(0,v.jsxs)(`details`,{className:`image-bed-help-section`,children:[(0,v.jsx)(`summary`,{children:`GitHub token 创建说明`}),(0,v.jsxs)(`p`,{className:`image-bed-help-intro`,children:[`这个图床固定使用当前 GitHub 账号下的公开仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`,图片会写入 `,(0,v.jsx)(`code`,{children:`images`}),` 目录并返回 raw.githubusercontent.com 原图链接。`]}),(0,v.jsxs)(`ol`,{className:`image-bed-steps`,children:[(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`先建公开仓库。`}),` 在 GitHub 新建仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`,Visibility 选 `,(0,v.jsx)(`strong`,{children:`Public`}),`。如果仓库已经存在,确认它属于这个 token 对应的个人账号,并且不是 Private。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`创建 fine-grained token。`}),` 打开 GitHub 的 `,(0,v.jsx)(`a`,{href:`https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens`,target:`_blank`,rel:`noreferrer`,children:`Personal access tokens`}),` 页面,选择 `,(0,v.jsx)(`strong`,{children:`Fine-grained tokens`}),`,点 `,(0,v.jsx)(`strong`,{children:`Generate new token`}),`。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`限制仓库范围。`}),` Token name 可填 `,(0,v.jsx)(`code`,{children:`AI Zero Token image bed`}),`,Expiration 按需设置,Resource owner 选自己的账号;Repository access 选择 `,(0,v.jsx)(`strong`,{children:`Only select repositories`}),`,只勾选 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`给最小权限。`}),` 在 Repository permissions 里把 `,(0,v.jsx)(`strong`,{children:`Contents`}),` 设置为 `,(0,v.jsx)(`strong`,{children:`Read and write`}),`;`,(0,v.jsx)(`strong`,{children:`Metadata`}),` 保持默认 Read-only 即可,其它权限不需要打开。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`复制并验证。`}),` GitHub 只会展示一次生成后的 token,复制后回到这里粘贴,点击 `,(0,v.jsx)(`strong`,{children:`保存并验证`}),`。验证通过后就可以拖图上传。`]})]}),(0,v.jsxs)(`dl`,{className:`image-bed-help-facts`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`dt`,{children:`Token 格式`}),(0,v.jsxs)(`dd`,{children:[`fine-grained token 通常以 `,(0,v.jsx)(`code`,{children:`github_pat_`}),` 开头。`]})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`dt`,{children:`验证失败`}),(0,v.jsxs)(`dd`,{children:[`如果提示未找到仓库,优先检查仓库名是否为 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`、仓库是否 Public、Repository access 是否选中了这个仓库。`]})]})]}),(0,v.jsx)(`p`,{className:`hint`,children:`Token 要像密码一样保管,不要发给别人。这个页面只会把它保存到你本机状态目录。`})]})]})}export{S as ImageBedPage};
|