3297 Commits

Author SHA1 Message Date
mini
284b5129ac feat(router): mount Landing at / with auth-aware redirect
- / (anonymous) → LandingView
- / (authenticated) → redirects to /dashboard via new meta.redirectIfAuth
- Remove temporary /landing-preview route (Task 2 helper)
- RouteMeta TS augmentation for redirectIfAuth
- LandingView brand link uses router-link (was <a href>, causing SPA reload)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:14:12 +08:00
Wesley Liddick
e8be434498 Merge pull request #1752 from touwaeriol/fix/quota-exceeded-scheduling
fix(account): prevent quota-exceeded API key/Bedrock accounts from being scheduled
2026-04-19 21:14:06 +08:00
Wesley Liddick
061fd48df7 Merge pull request #1749 from touwaeriol/fix/xhigh-reasoning-effort
fix: support xhigh reasoning effort in usage records
2026-04-19 21:13:45 +08:00
mini
4832534232 fix(landing): Polish items from code review
- Add noreferrer to all external rel attributes (4 anchors)
- Change 更新日志 link from /releases (may 404) to /commits/branch/main
- Remove dead CSS overridden by puro.css: .nav z-index:10, .nav-links gap:20px
- Document puro.css global dependency at top of scoped style block

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:11:13 +08:00
mini
4e675d70c1 feat(landing): Footer + sticky Nav styles
- 4-column footer (brand/产品/资源/联系), responsive 2-col on mobile
- Nav sticky with blur background, border-bottom
- nav-links hidden on mobile (<640px)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:01:10 +08:00
erio
6579f28b64 fix: delete scheduled test plans when account is deleted
Accounts use soft-delete (setting deleted_at), so PostgreSQL's
ON DELETE CASCADE on scheduled_test_plans.account_id never fires.
Add plan deletion to the existing account deletion transaction
to ensure atomicity.

Closes Wei-Shaw/sub2api#1728
2026-04-19 20:38:57 +08:00
mini
c099cd5d97 feat(landing): add Code Demo + Dashboard mockup sections
Dashboard mockup is pure static (SVG chart + stats grid + log table).
No backend dependency. Reuses puro.css .log-table / .provider / .status-*
(scoped to .puro-page). Only adds component-local styles for .code-demo,
.dash-mock, .stat-row, .chart-card and friends.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 20:29:14 +08:00
mini
158f2a8d53 fix(landing): resolve Task 3 CSS specificity issues
Code review flagged:
- .feature.card caused padding/background specificity race;
  .feature already defines all card properties, so drop .card
- model-card flex row misaligned name/meta (should stack in column);
  wrap in div to get dot | [name/meta] layout
- .container and .section-* scoped rules silently shadow puro.css defaults;
  add comment explaining intentional override

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 20:26:50 +08:00
mini
cfcdd988db feat(landing): add Models wall + Features sections
- Added Models section (② 模型墙) with 5 platform cards (Claude, GPT, Codex, Gemini, More)
- Added Features section (③ 三特性) with 3 feature cards (unified key, account pool failover, analytics dashboard)
- Implemented responsive grid layouts with scoped CSS rules
- Used existing design tokens and brand colors from puro.css

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 20:18:56 +08:00
mini
9dae8724e3 feat(landing): LandingView scaffold with Nav + Hero
Temporary route /landing-preview added for dev iteration. Will flip /
to this view once all 6 sections are in place (Task 6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 20:11:06 +08:00
mini
064a4b7614 fix(design): scope .bg-glow::before selector to .puro-page
One missed prefix from the automated transform. Aligns with the
scoping contract established in 41664efe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 20:06:21 +08:00
mini
41664efede fix(design): scope puro.css to .puro-page container
Code review flagged two critical issues with the initial scaffold:
- html/body bg + universal reset applied globally, overriding admin Tailwind styles
- Shared class names (.btn, .card, .input, .badge, .divider) collided with style.css,
  breaking 25+ admin views

Fix: all non-:root rules now scoped under .puro-page descendant selector.
@keyframes and global box-sizing preserved. html/body props merged into .puro-page root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 20:03:22 +08:00
mini
1d7e75b82e feat(design): scaffold PURO AI design system
- Add puro.css (tokens + primitives) as global stylesheet
- Load Inter + JetBrains Mono via Google Fonts
- Extend tailwind.config with puro.* color namespace (no conflict with legacy primary/accent palettes)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 19:11:24 +08:00
puro design
d941550bf6 docs: implementation plan for PURO AI landing+auth [CI SKIP]
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
13 tasks, ~4-6 hours total work. Each task has concrete file paths,
full code blocks, verification steps. Scope: Landing + AuthLayout split
+ LoginView/RegisterView restyle + public Docs + puro.css design system.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 19:04:23 +08:00
erio
258fd145ff fix(account): prevent quota-exceeded API key/Bedrock accounts from being scheduled
Add quota exceeded check to IsSchedulable() and refactor
shouldClearStickySession to delegate to IsSchedulable(), eliminating
duplicated logic and fixing missed overload/rate-limit/expired checks.
Frontend displays quota exceeded status independently via quota fields.
2026-04-19 18:45:04 +08:00
puro design
3a16b3ecde docs: archive Claude Design v2 output [CI SKIP]
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
10 HTML pages + puro.css + HANDOFF.md + 2 images (~810KB total).
Reference artifacts for Stage 3 Vue 3 translation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:05:27 +08:00
erio
6530776a62 fix: support xhigh reasoning effort in usage records for Claude Messages API
Closes #1732
2026-04-19 18:05:25 +08:00
puro design
02173c8d7e docs: spec v2 + design drafts archive [CI SKIP]
Stage 2 完成(Claude Design 产出 10 页)后的 spec 更新:
- 范围决策:路径 B 分层交付(本期做 Landing/Login/Register/Docs 4 页 + puro.css 落地;二期换皮 Dashboard/API Keys;不做 Binding/Pricing)
- Landing 文案精修:剔除 Pricing/FAQ/CTA banner;Hero CTA 改「登录」+「联系咨询」;微文案对齐真实能力
- Footer 删掉「套餐(暂隐)」
- 新增 §4.5 Docs 页(公开 /docs,精简版)
- §5 brief 标记为历史存档(Stage 2 已完成)
- §5.5 Stage 2 产出清单(10 个文件 + 本期用/二期/不做)
- 归档 docs/design-drafts/v2/ 全部产出(含 puro.css、HANDOFF.md、10 个 HTML)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:05:14 +08:00
puro design
332d46cde7 docs: PURO AI landing+auth redesign spec [CI SKIP]
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
Stage 1 brainstorm 产出:
- 风格方向:暗黑科技(cyan/purple on slate-950)
- Landing 6 sections + 完整中文文案
- Auth 左右分栏 + "5→1" 数字对比叙事
- 给 claude.ai/design 的 brief(可直接复制)
- Stage 3 实施约束(Vue 3 + Tailwind + i18n 对齐)

下一步:user 拿 brief 到 claude.ai/design 出视觉稿(Stage 2)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:23:20 +08:00
puro ci
ac3417a964 docs: add VPS deployment + CI notes [CI SKIP]
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
综合归档 ai.puro.im 部署记录:
- VPS 架构 + CI 流程图
- 初次手工部署流程(对应 LOCAL_SETUP_NOTES 的线上版)
- Drone pipeline 细节 + skip-CI 约定
- 7 个踩过的坑 + 解法(Setup wizard port/权限、账号池 3 件套、run_mode、balance 缓存、/setup 路由)
- 当前 snapshot、常用运维命令、从头重来流程

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:05:30 +08:00
puro ci
9c34e9619c ci: trigger first Drone build
Some checks failed
continuous-integration/drone/push Build is passing
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
2026-04-19 12:29:14 +08:00
puro ci
f431b2e2ff ci: add Drone pipeline for ai.puro.im deployment
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
- .drone.yml: pnpm build frontend → go build backend → docker compose up
- .ci/Dockerfile: distroless:nonroot runtime image
- host state (/opt/sub2api/{config.yaml,compose,volumes}) stays untouched

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:26:37 +08:00
Wesley Liddick
51af8df31d Merge pull request #1731 from touwaeriol/fix/rate-billing-autofill-response-limit
fix: subscription billing, alipay redirect + H5, payment secrets, 128MB response limit
2026-04-19 09:43:24 +08:00
erio
235f710853 feat(payment): redact provider secrets in admin config API
Admin GET /api/v1/admin/payment/providers previously returned every
config value — including privateKey / apiV3Key / secretKey etc. —
verbatim. Any future XSS on the admin UI would hand attackers the
full set of production payment credentials, and the plaintext values
sat unnecessarily in browser memory for every operator.

Treat those fields as write-only from the admin surface:

- decryptAndMaskConfig() strips sensitive keys from the GET response.
  The authoritative list is an explicit per-provider registry that
  mirrors the frontend's PROVIDER_CONFIG_FIELDS sensitive flag:
    alipay   → privateKey, publicKey, alipayPublicKey
    wxpay    → privateKey, apiV3Key, publicKey
    stripe   → secretKey, webhookSecret (publishableKey stays plain)
    easypay  → pkey
  Payment runtime still reads the full config via decryptConfig, so
  nothing at the gateway changes.

- mergeConfig() treats an empty value for a sensitive key as "leave
  unchanged" — the admin UI omits unchanged secrets so operators can
  tweak non-sensitive settings without re-entering credentials.

- Admin dialog (PaymentProviderDialog.vue):
  * secret inputs get autocomplete="new-password", data-1p-ignore,
    data-lpignore and data-bwignore so password managers do not
    offer to save provider credentials
  * in edit mode the required-field check skips sensitive fields
    (empty is the "keep existing" signal) and the placeholder shows
    "leave empty to keep" instead of the default example value
  * create mode still requires every non-optional field, including
    secrets, since there is nothing to preserve

- Unit test renamed to TestIsSensitiveProviderConfigField, covers
  the per-provider registry and specifically asserts that Stripe's
  publishableKey is NOT treated as a secret.
2026-04-19 02:22:53 +08:00
erio
c3cb0280ef fix(payment): alipay redirect-only flow, H5 detection and popup sizing
The native Alipay provider previously tried to embed the payment page
URL into a QR code on the client — the URL is not a scannable payload
so the QR never worked. Merchants also hit a H5 detection mismatch
whenever the backend UA sniffer missed iPadOS 13+ or embedded browsers,
and the popup window was too small for Alipay's standard checkout
layout (QR + account-login panel on the right), forcing the user to
scroll horizontally and vertically.

Changes:

Backend
- alipay.go: drop QR-on-URL path. Use redirect-only flow —
  alipay.trade.page.pay for PC (returns a gateway URL the browser
  opens in a new window) and alipay.trade.wap.pay for H5 (returns a
  URL the browser jumps to). Both flows produce pages on
  openapi.alipaydev.com / excashier.alipay.com; the client never
  renders a QR itself.
- payment_handler.go: add optional is_mobile bool to
  CreateOrderRequest so the frontend can declare the device
  explicitly. Server still falls back to UA sniffing when absent.

Frontend
- types/payment.ts, PaymentView.vue: declare is_mobile in
  CreateOrderRequest and pass the computed isMobileDevice() value.
- providerConfig.ts: replace the two fixed POPUP_WINDOW_FEATURES
  constants with getPaymentPopupFeatures(), which prefers 1250×900
  (Alipay's checkout footprint), clamps to window.screen.avail* and
  centers the popup so it never overflows on smaller laptops.
- PaymentQRDialog.vue, PaymentStatusPanel.vue, StripePaymentInline.vue,
  PaymentView.vue: use the new helper at all popup call sites.
2026-04-19 02:22:41 +08:00
Wesley Liddick
6c73b6212c Merge pull request #1734 from touwaeriol/docs/payment-recommend-kyren-topup
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
docs(payment): add Kyren Topup as international EasyPay provider option
2026-04-18 18:33:14 +08:00
erio
0c538a584f docs: note Kyren Topup $200 account fee waived via referral link 2026-04-18 14:48:47 +08:00
erio
6ae1cc8f3f docs: use 易支付 in Chinese coexistence note 2026-04-18 14:45:25 +08:00
erio
37123cef8f docs(payment): add Kyren Topup as international EasyPay provider option
Restructure the EasyPay recommendation block to present two options side
by side so users can pick by funding channel and settlement currency:

- Domestic / CNY — ZPay: official Alipay/WeChat API with 1.6% fee and
  T+1 automatic settlement (existing recommendation, expanded with fee
  and settlement details).
- International / USDT or USD — Kyren Topup (https://kyren.top): global
  payment stack supporting WeChat Pay and Alipay with local-currency
  checkout, USD settlement, and USDT/USD withdrawal. Fees: WeChat 2%,
  Alipay 2.5%, withdrawal 0.1% ($40 min / $150 max). Fills the gap for
  users who cannot use domestic Chinese channels or tolerate Stripe's
  6%+ fees.

Both recommendations share a single disclaimer at the end. The Chinese
heading uses "易支付" while the English one keeps "EasyPay".
2026-04-18 14:42:55 +08:00
erio
61a008f7e4 chore(payment): mark legacy AES ciphertext fallback as deprecated
明文 JSON 已经是新写入的默认格式;保留 AES 密文读取仅为兼容迁移期间的旧
记录,一旦所有部署通过管理后台重存过一次即可删除。标记为 deprecated 并加
TODO,几个版本后统一清理掉:payment.Encrypt / payment.Decrypt、两处
decryptConfig 的 AES 分支、PaymentConfigService.encryptionKey 和
DefaultLoadBalancer.encryptionKey 字段。
2026-04-17 23:24:27 +08:00
erio
bf0bbe0be7 feat(gateway): raise upstream response read limit 8MB -> 128MB (configurable)
图片生成 API 返回的 base64 内联图响应经常超过 8MB 单次读取上限,被
ReadUpstreamResponseBody 拦截成 502 upstream_error。

单张 4K PNG base64 最坏约 67MB,多张候选图或 imageSize=4K 的 image_generation
一次请求能轻松到 30MB+。把默认上限提到 128MB 能覆盖 2-3 张 4K 图,相对
请求体上限 256MB 仍有缓冲;同时抽出 config.DefaultUpstreamResponseReadMaxBytes
共享常量,viper 默认值和 service 层兜底共用,消除两处同步魔法数字。

仍可通过 gateway.upstream_response_read_max_bytes 配置项覆盖。
2026-04-17 22:07:15 +08:00
erio
df57d2776b fix(billing): reject rate_multiplier <= 0 on save; clamp negatives to 0 in compute
分组倍率和用户专属倍率在保存时没有校验,0 会触发计费层的 `<=0 → 1.0`
防御条款,结果订阅/余额分组按标准价扣费;完全是沉默地绕过了业务规则。

- 保存校验(admin_service):CreateGroup / UpdateGroup / BatchSetGroupRateMultipliers /
  UpdateUser.SyncUserGroupRates 全部要求 > 0
- 计算层(billing_service):三处 `<=0 → 1.0` 改为 `<0 → 0`;负数按 0 结算,
  避免配置异常被静默按 1x 收费
- 前端:分组倍率 / 用户专属倍率输入 min 统一到 0.001
- 删除未使用的 IsFreeSubscription 方法

测试:新增 billing_service_rate_multiplier_test.go 端到端验证;更新原有锁定
旧 `<=0 → 1.0` 行为的测试。
2026-04-17 22:06:32 +08:00
erio
948d8e6d02 fix(admin): prevent browser password manager from autofilling account API key
Chrome's password manager matched the apikey-type account's Base URL + API Key
inputs as a login form and autofilled the last saved password by domain, so
editing a Gemini account could overwrite its apikey with a Claude key that
shared the same Base URL. Add autocomplete="new-password" plus data-*-ignore
attributes for 1Password / LastPass / Bitwarden to opt the field out of every
major password manager's autofill.
2026-04-17 22:06:32 +08:00
erio
44cdef7934 fix(usage): subscription billing honours group rate multiplier
Subscription-mode billing was consuming quota at TotalCost (raw) instead of
ActualCost (TotalCost * RateMultiplier), so per-group rate multipliers —
including free subscriptions (multiplier = 0) — were silently ignored.
Switch the three subscription cost writes in buildUsageBillingCommand,
finalizePostUsageBilling, and the legacy postUsageBilling fallback to
ActualCost, and add a table-driven test covering 2x / 0.5x / free multipliers
plus a balance-mode regression check.
2026-04-17 22:06:32 +08:00
erio
fd0c9a1305 fix(payment): store provider config as plaintext JSON with legacy ciphertext fallback
Without TOTP_ENCRYPTION_KEY, saved payment configs were lost on restart because
the AES round-trip failed silently. Write new records as plaintext JSON; read
path tries JSON first, falls back to legacy AES decrypt when a key is present,
and treats unreadable values as empty so admins can re-enter them via the UI.
2026-04-17 22:06:32 +08:00
github-actions[bot]
6cfdf4ec05 chore: sync VERSION to 0.1.114 [skip ci] 2026-04-17 02:51:18 +00:00
Wesley Liddick
358ff6a608 Merge pull request #1683 from FjlI5/dev-main
fix:修复上游账号为OpenAI API key时Claude Code调用缓存率低的问题
2026-04-17 10:28:12 +08:00
Wesley Liddick
41fbdba104 Merge pull request #1687 from touwaeriol/refactor/upstream-response-limit-dedup
refactor: extract ReadUpstreamResponseBody to deduplicate response read + too-large handling
2026-04-17 10:19:14 +08:00
Wesley Liddick
c22d11cedd Merge pull request #1702 from StarryKira/fix/outbox-watermark-context-dedup-1691
fix: fix outbox watermark context expiry and add in-batch group rebuild dedup
2026-04-17 10:18:56 +08:00
shaw
5d586a9f3a fix: 上游返回 KYC 身份验证要求时停止账号调度 2026-04-17 10:17:50 +08:00
shaw
a789c8c4c7 feat: 支持opus-4.7 2026-04-17 09:37:25 +08:00
Elysia
697c41a3f6 fix: create fresh context per watermark write retry attempt
Each retry in the SetOutboxWatermark loop now gets its own 5s context.
Previously a shared context could already be expired when the second or
third attempt ran, making the retries pointless.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 20:41:40 +08:00
Elysia
e44baa1094 fix: fix outbox watermark context expiry and add in-batch group rebuild dedup
Fixes #1691

- pollOutbox() reused a 10s context for SetOutboxWatermark after event
  processing could take much longer, causing "outbox watermark write
  failed: context deadline exceeded". The watermark never advanced so
  the same 200 events were reprocessed every poll cycle, spiking CPU.
  Now uses an independent 5s context with up to 3 retries (200ms apart).

- When multiple Codex accounts sharing the same 21-22 groups are all
  rate-limited in quick succession, each account_changed event triggered
  redundant bucket rebuild attempts for the same groups. Introduce
  batchSeenKey{groupID, platform} and thread a seen map through the
  handler chain; rebuildBucketsForPlatform skips (group, platform) pairs
  already rebuilt within the same poll batch (~80% fewer rebuild calls in
  the 5-accounts-same-groups scenario).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:09:40 +08:00
Wesley Liddick
e6e73b4f52 Merge pull request #1690 from KnowSky404/fix/ws-codex-scheduler-cache-1662
fix: preserve openai ws flags in scheduler cache
2026-04-16 17:21:32 +08:00
shaw
7ea8e7e667 chore: update sponsors 2026-04-16 17:19:32 +08:00
shaw
a55ead5ea8 chore: remove empty dir Antigravity-Manager 2026-04-16 16:42:40 +08:00
KnowSky404
836092a666 fix: restore ctx pool ws mode option in account ui 2026-04-16 02:13:04 +00:00
KnowSky404
3944b3d216 fix: preserve openai ws flags in scheduler cache 2026-04-16 02:01:50 +00:00
erio
10699eeb34 refactor: extract ReadUpstreamResponseBody to deduplicate upstream response read + too-large error handling
Consolidates 9 call sites of resolveUpstreamResponseReadLimit + readUpstreamResponseBodyLimited + ErrUpstreamResponseBodyTooLarge error handling into a single ReadUpstreamResponseBody function with TooLargeWriter callback for API-format-specific error responses (Anthropic, OpenAI, countTokens).
2026-04-16 01:53:22 +08:00
fjl5
6c89d8d35c add prompt_cache_key injection for messages→responses 2026-04-15 23:56:56 +08:00