ai-zero-token 2.0.8 → 2.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.9 - 2026-05-26
4
+
5
+ - Clarified auto-switch settings so manually excluded accounts are separated from accounts that are runtime-ineligible because login is unavailable or quota is exhausted.
6
+ - Updated account filters and stats labels to distinguish configured rotation participation from the actual automatic rotation candidate pool.
7
+ - Preserved account identity metadata when refreshed Codex tokens omit profile claims.
8
+ - Validated `id_token` expiry before using saved profiles for Codex image and web flows, with clearer recovery guidance when a fresh `id_token` is unavailable.
9
+
3
10
  ## 2.0.8 - 2026-05-21
4
11
 
5
12
  - Added Codex prompt-cache key handling for gatewayed Codex requests so upstream cache-hit behavior is more stable.
@@ -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{T as o,_ as s,a as c,b as l,c as u,d,f,g as p,h as m,i as h,m as g,o as _,p as v,r as y,s as b,t as x,v as S,w as C,x as w,y as T}from"./profiles-iNTmJFRe.js";import{_ as E,d as D,p as O,r as k,x as A}from"./index-BM5N4YUY.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=v(n),o=f(n),u=l(n),d=!!e.expandedProfiles[n.profileId],p=!!(t&&n.accountId===t),h=w(n,p),C=b(n),D=_(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-${y(n)} ${C?`is-auth-invalid`:``}`,"data-profile-card":n.profileId,title:C?x(n):void 0,children:[h&&(0,N.jsx)(`span`,{className:`usage-corner ${h.className}`,children:(0,N.jsx)(`span`,{children:h.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:g(n)}),(0,N.jsx)(`strong`,{children:m(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:c(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:s(o)}),(0,N.jsx)(L,{label:S(n,`secondary`),value:u,tone:s(u)})]}),(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 ${p?`is-active`:``}`,children:[(0,N.jsx)(A,{size:14}),(0,N.jsx)(`span`,{children:`Codex`}),(0,N.jsx)(`span`,{className:`usage-dot ${p?`active`:``}`}),(0,N.jsx)(`span`,{className:`usage-state-text`,children:p?`使用中`:`未使用`})]})]}),(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:T(n,`primary`)})]}),(0,N.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,N.jsx)(`label`,{children:S(n,`secondary`)}),(0,N.jsx)(`strong`,{children:T(n,`secondary`)})]})]}),(0,N.jsx)(`div`,{className:`compact-meta-actions`,children:(0,N.jsxs)(`button`,{className:`details-toggle ${d?`is-expanded`:``}`,type:`button`,onClick:()=>e.onToggle(n.profileId),children:[(0,N.jsx)(`span`,{children:d?`收起详情`:`查看详情`}),(0,N.jsx)(I,{})]})})]}),d&&(0,N.jsxs)(`div`,{className:`meta-grid`,children:[(0,N.jsx)(j,{label:`套餐`,value:c(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||C,children:C?`网关不可用`:n.isActive?`网关使用中`:`应用网关`}),(0,N.jsx)(`button`,{className:`btn-secondary ${p?`is-current codex`:``}`,type:`button`,onClick:()=>e.onAction(`apply-codex`,n),disabled:p||M||C,children:C?`Codex 不可用`:p?`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} 次,最近 ${C(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,s]=(0,M.useState)({search:``,status:`all`,sort:`quota-desc`}),c=(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=[m(t,!0).toLowerCase(),t.accountId,t.profileId,t.email||``].join(` `).toLowerCase(),o=v(t),s=!!(e.codexAccountId&&t.accountId===e.codexAccountId),c=y(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=h(n)-h(t);if(i!==0)return i;let o=d(n)-d(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`?m(t,!0).localeCompare(m(n,!0),`zh-CN`):a.sort===`quota-asc`?100-f(n)-(100-f(t)):a.sort===`plan-desc`?h(n)-h(t):a.sort===`email-asc`?m(t,!0).localeCompare(m(n,!0)):f(n)-f(t):o}),i},[a,e.codexAccountId,e.config?.profiles,e.config?.settings.autoSwitch.excludedProfileIds]),l=(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(v(e).key)),tone:`green`},{key:`unavailable`,label:`不可用`,value:r(e=>[`invalid`,`expired`,`exhausted`].includes(v(e).key)),tone:`red`},{key:`unknown`,label:`待请求验证`,value:r(e=>v(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=>v(e).key===`exhausted`),tone:`orange`},{key:`free`,label:`Free`,value:r(e=>y(e)===`free`),tone:`muted`},{key:`plus`,label:`Plus`,value:r(e=>y(e)===`plus`),tone:`brand`},{key:`pro-team`,label:`Pro/Team`,value:r(e=>[`pro`,`team`,`enterprise`,`premium`].includes(y(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]),g=Object.values(t).filter(Boolean).length,_=Object.keys(t).filter(e=>t[e]),x=(0,M.useMemo)(()=>c.map(e=>e.profileId),[c]);async function S(t,n){let r=await O(`/_gateway/admin/profiles/export`,{method:`POST`,headers:{"Content-Type":`application/json`},body:o(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 C(t,n){if(!(t===`remove`&&!window.confirm(`确认删除 ${m(n,e.showEmails)}?`))){if((t===`activate`||t===`apply-codex`)&&b(n)){e.setStatus(`${m(n,e.showEmails)} 登录已失效,不能应用到${t===`activate`?`网关`:`Codex`}。`);return}if((t===`activate`||t===`apply-codex`)&&u(n)){let r=t===`activate`?`网关`:`Codex`;if(!window.confirm(`${m(n,e.showEmails)} 的额度看起来已耗尽,仍要应用到${r}吗?`))return}if(t===`export`){await S(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:o({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 w(){let t=_;if(t.length===0){e.setStatus(`请先勾选要删除的账号。`);return}let r=e.config?.profiles.filter(e=>t.includes(e.profileId)).slice(0,3).map(t=>m(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:o({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:c,accountStats:l,showEmails:e.showEmails,filter:a,selectedProfiles:t,expandedProfiles:r,selectedCount:g,visibleCount:x.length,busy:e.busy,onFilter:s,onSelect:(e,t)=>n(n=>({...n,[e]:t})),onSelectVisible:()=>T(x,`已选择当前筛选结果 ${x.length} 个账号。`),onClearSelected:()=>{n({}),e.setStatus(`已取消选择。`)},onToggle:e=>i(t=>({...t,[e]:!t[e]})),onAction:C,onLocate:()=>e.activeProfile&&document.querySelector(`[data-profile-card="${e.activeProfile.profileId}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`}),onExportSelected:()=>{let t=_;if(t.length===0){e.setStatus(`请先勾选要导出的账号。`);return}S(void 0,t).catch(t=>e.setStatus(t instanceof Error?t.message:String(t)))},onRemoveSelected:()=>void w(),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-_5Ny0cZf.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
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-BM5N4YUY.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-C5SmQvju.js";import{_ as c,b as l,n as u,o as d,p as f,r as p}from"./index-_5Ny0cZf.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,w as s}from"./profiles-iNTmJFRe.js";import{_ as c,b as l,n as u,o as d,p as f,r as p}from"./index-BM5N4YUY.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:[o(e.size),` · `,e.mimeType]}),(0,v.jsx)(`code`,{children:s(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};