Scrollbar:
- Override scrollbar-width to 'auto' (was 'thin' ~6px, nearly invisible)
- Increase scrollbar-color opacity to 0.7 (was 0.5)
- Add explicit ::-webkit-scrollbar width 10px for older Chrome
Text selection:
- Block selectstart in Phase 1 (onMouseDown) instead of Phase 2
- Clear existing text selection with removeAllRanges() on drag start
Stability:
- Add window blur handler to cleanup drag on tab switch / window leave
- Guard onWheel RAF callback against stale drag state
- Defensive removeMarquee() before createMarquee()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The global CSS makes all scrollbars transparent by default (only visible
on hover). For table containers, scrollbars should always be visible so
users know the content is scrollable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Binary search in findRowIndexAtY (O(n) → O(log n))
- Short-circuit applyRange when row index unchanged
- Cache scrollParent in beginDrag instead of per-frame lookup
- Skip autoScroll recomputation when scrollTop unchanged
- Detect scrollbar clicks on inner containers, not just document
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace document.body.style.userSelect manipulation with selectstart
event listener to prevent text selection (no body style mutation)
- Skip mousedown on scrollbar area (clientX >= documentElement.clientWidth)
- Extract cleanupDrag() for robust state cleanup in all code paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Allow starting drag from anywhere on the page (not just inside tbody)
- Use 5px drag threshold to prevent accidental selection on click
- Support mouse wheel scrolling during drag to select new rows
- Auto-scroll when dragging near viewport edges with row re-detection
- Find closest row by viewport Y coordinate instead of requiring exact hit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove selectedRowIds/isRowSelected/row-selected CSS from DataTable.
Add semi-transparent marquee rectangle overlay during drag selection,
with dark mode theme support using primary colors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add selectedRowIds prop to DataTable. Selected rows get a blue tint
background (primary-50), including sticky columns. Integrated into
AccountsView and ProxiesView for visual feedback during swipe select.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add useSwipeSelect composable that enables drag-to-select rows in
DataTable. Mousedown on a row and drag up/down to select/deselect
a contiguous range. Integrated into AccountsView and ProxiesView.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
P0 fixes:
- StorageType: UploadFromURL now returns actual storageType, fixing
GDrive tasks being incorrectly recorded as "s3"
- Concurrency: selectAccount returns ReleaseFunc to caller, fixing
premature slot release before task creation
- Race condition: applyRecoveredToken uses copy-on-write for
account.Credentials map to prevent concurrent write panic
- GetCameoStatus: use direct HTTP call to populate missing fields
(StatusMessage, InstructionSetHint, InstructionSet)
- Frontend: progress bar uses reactive now.value instead of
non-reactive Date.now()
P1 fixes:
- EditImage: pass req.Image through to CreateImageRequest
- ListPending: add LIMIT 200 to prevent unbounded queries
- Worker encapsulation: add ListPendingTasks/UpdateTask proxy methods
to SoraTaskService, worker no longer accesses repo directly
- Frontend polling: add retry counter (3 attempts) before giving up
P2-P3 fixes:
- QuotaBar: handle both quota_source and source field names
- i18n: replace hardcoded Chinese labels in SoraPromptBar and
example prompts in SoraGeneratePage with i18n keys
- Fix regenrate → regenerate i18n typo
- SoraMediaPreview: skip Escape handler when not visible
- SoraGeneratePage: void handleGenerate promise in handleRetry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- QuotaLimitCard: label and input on same row for reduced height
- CreateAccountModal: move AffinityConfigCard into OAuth/SetupToken
quota control section, eliminating duplicate "配额控制" cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge the separate "配额限制" and "配额控制" sections into one unified
"配额控制" section per account type. For Anthropic OAuth/SetupToken
accounts, AffinityConfigCard is now inside the quota control section
instead of a separate card.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move both cards under a unified "配额限制" section wrapper for Anthropic
platform. Remove independent section wrappers from both components so
they render as plain rounded cards. Non-Anthropic apikey accounts get
their own quota section with just QuotaLimitCard.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- AffinityConfigCard redesigned to match QuotaLimitCard style (section title + toggle card)
- Moved from standalone section to quota area, visible for all Anthropic account types
- Green zone (base) allows empty = no limit, min=1 when set; yellow zone hidden when base unset
- Deduplicated affinity save logic in EditAccountModal (single block for all Anthropic types)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend: Add AffinityZone type with GetAffinityBase/GetAffinityBuffer/GetAffinityZone
methods. Integrate classifyByAffinityZone into Layer 2 scheduling and red zone
check into Layer 1.6 affinity hit path. 17 unit tests covering all zone boundaries.
Frontend: Add AffinityConfigCard for base/buffer configuration in EditAccountModal.
Move affinity badge from status column to capacity column with zone-based coloring
(count/limit format). Extract CapacityBadge and QuotaBadge components to reduce
AccountCapacityCell from 444 to 215 lines.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Anthropic OAuth/setup-token accounts are throttled by actual proxy
connection (host:port:username). Other platforms bypass the queue
entirely. Also adds unit tests for affinity and usage queue features.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New API: GET /accounts/:id/affinity-clients returns client list with
last_active timestamps from Redis ZSET scores
- New Lua script: get_affinity_clients_with_scores.lua uses ZREVRANGEBYSCORE
WITHSCORES to return both client IDs and their timestamps
- New frontend component: AffinityBadge.vue replaces inline badge with
hover popover that lazy-loads client details on first hover
- Shows client ID (monospace) + relative time (e.g. "3h ago")
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Widen the per-group delay between Anthropic usage API calls from
1-1.5s to 1-2s to avoid upstream 429 rate limiting when multiple
accounts share the same proxy exit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add colored circle badge showing affinity client count before the account
status indicator. Badge color reflects count severity (gray=0, green=1-5,
yellow=6-15, red=16+). Tooltip shows full client ID list on hover.
Backend: AccountHandler batch queries reverse affinity index via
GetAccountAffinityClientsBatch, returns affinity_client_count and
affinity_clients in DTO. GatewayCache interface extended with the new
batch method backed by a Lua script (get_affinity_clients.lua).
All test mocks synchronized with updated GatewayCache interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The client affinity setting was only available when editing an account.
Now it's also available during account creation for all Anthropic
platform types (OAuth, Setup Token, API Key).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move storage provider selection (S3/GDrive) from separate modal into
the drawer itself, shown as the first step when creating a new profile
- Provider type is locked once selected (read-only badge in header)
- Remove default_storage_quota_gb field from form and table column
- Remove unused formatStorageQuotaGB function and providerSelectOpen ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add quota_daily_limit/used and quota_weekly_limit/used to DTO and
frontend Account type
- AccountCapacityCell shows D (daily), W (weekly), and $ (total) badges
with color-coded status (green/yellow/red)
- AccountActionMenu shows reset button when any quota dimension is set
- Extract quotaBadgeClass/quotaBadgeTooltip as shared functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Split compound "Profile" column into separate Profile ID and Name columns
- Replace "Endpoint" column with "Storage Path" showing bucket/prefix or folder info
- Add GDrive quota display (capacity/used) via new backend API endpoint
- Add video count statistics per storage type via new backend API endpoint
- Add inline test button per profile with 15s timeout and per-profile loading state
- Backend: GetQuotaInfo, CountByStorageType, GetGDriveQuota, GetStorageVideoStats
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When customErrorCodes is disabled or modelMapping is empty, explicitly
delete the fields inherited from currentCredentials spread to avoid
preserving stale values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace single-period-selector (三选一) with three independently configurable
quota dimensions (日/周/总) that can all be set simultaneously. Account is
paused when ANY dimension is exceeded. Periodic quotas (daily/weekly) use
lazy reset via atomic SQL — no cron job needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The OAuth flow opens a popup window that redirects to /admin/gdrive-oauth-callback,
but this route and page were missing. Add the callback view that extracts the
authorization code from the URL and sends it back to the opener window via
postMessage, completing the OAuth authorization flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Define SoraObjectStorage interface abstracting S3 and GDrive backends
- Implement SoraGDriveStorage with OAuth2 and Service Account auth
- Add SoraStorageRouter to route requests based on active profile provider
- Add GDrive OAuth handler for authorization flow (start + callback)
- Extend profile model with provider, auth_type, and GDrive-specific fields
- Update frontend UI with provider selection, dynamic form fields, and i18n
- Migrate API paths from /sora-s3 to /sora-storage (old paths preserved)
- All existing S3 functionality remains backward compatible
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When editing an apikey account, the credentials object was built from
scratch, causing fields like tier_id that are not exposed in the UI to
be silently dropped on save. Spread currentCredentials first so unknown
fields are retained, then let the known fields overwrite them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>