feat(web): Introduce Token Sleep Mode and UI enhancements - #2813
feat(web): Introduce Token Sleep Mode and UI enhancements#2813SX2000CN wants to merge 13 commits into
Conversation
- Add duration field to EditTokenModal for auto-activation on first use - Set default auto-activation duration to 30 days - Fix layout shift in EditTokenModal when validation errors occur - Improve button layout in ModelTestModal using Space component - Fix build errors by adjusting CSS imports and adding antd dependency
- Add gap-2 to NoticeModal and FilterModalFooter flex containers - Wrap buttons with Space component in TwoFASetting and TwoFactorAuthModal
- Fix ReferenceError in SettingsPaymentGatewayCreem.jsx - Fix ReferenceError in UpstreamRatioSync.jsx - Add deployment guide documenting common pitfalls
…nModal - Replace separate date picker and duration input with unified validity settings - Add validity type selection (Forever / Custom Duration) - Add activation mode switch (Immediate / On First Use) for custom durations - Auto-calculate expiration time or duration based on user selection - Improve UX by simplifying token validity configuration
- Revert to original DatePicker and shortcut buttons for expiration time - Add 'Sleep Mode' switch for tokens - When Sleep Mode is enabled: - Token remains valid indefinitely until first use - Expiration duration is calculated from the selected expiration date - Countdown starts only upon first activation - Maintain standard expiration behavior when Sleep Mode is disabled
WalkthroughThis PR refactors 40+ modal components to use custom footers instead of Modal's built-in handlers, introduces token sleep mode with duration-based activation, adds Ant Design dependency, updates the Dockerfile to Windows binary compilation, and includes a deployment guide. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes The changes span 60+ files with heterogeneous modifications: consistent modal footer refactoring patterns (reduces cognitive load), token sleep mode logic with state activation (moderate complexity), Dockerfile infrastructure changes, and dependency updates. While many changes follow a repetitive pattern, the token feature introduces new conditional logic, multiple files are modified for feature integration, and infrastructure changes require separate validation. Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
controller/token.go (1)
283-294:⚠️ Potential issue | 🟠 MajorKeep ExpiredTime consistent when Duration > 0 on update.
AddTokenforcesExpiredTime = -1whenDuration > 0, butUpdateTokendoesn’t. This can leave a staleExpiredTimeand break sleep-mode semantics after edits.✅ Suggested fix
cleanToken.CrossGroupRetry = token.CrossGroupRetry cleanToken.Duration = token.Duration + if token.Duration > 0 { + cleanToken.ExpiredTime = -1 + }
🤖 Fix all issues with AI agents
In `@controller/token.go`:
- Around line 194-198: Reject negative Duration values at the API boundary by
adding validation in the token handlers (e.g., CreateToken and UpdateToken) to
check token.Duration >= 0 and return a 400 / validation error when it's
negative; do not propagate negative Duration into the model
(cleanToken.Duration) and ensure any logic that sets cleanToken.ExpiredTime
(referencing cleanToken and token.Duration) only runs for valid, non-negative
durations.
In `@Dockerfile`:
- Around line 1-14: The Dockerfile is building a Windows binary (GOOS=windows
producing new-api-galaxy.exe) and lacks a runtime command and reproducible base
image; change GOOS/GOARCH/CGO_ENABLED to produce Linux binaries matching CI
targets (e.g. remove or set GOOS=linux and appropriate GOARCH), build a non-.exe
artifact (e.g. new-api-galaxy or new-api), add a proper ENTRYPOINT or CMD to run
the Gin HTTP server on port 3000, and pin the base image (replace golang:latest
with a specific tag) so CI/CD/docker-compose images are compatible; update or
remove the "伪造的 web/dist" copy step only if it's temporary test data.
In `@docs/deployment-guide.md`:
- Around line 22-24: The Markdown contains several fenced code blocks missing
language identifiers (triggering MD040); update each ``` block that contains
examples like "Load key \"xxx/id_ed25519\": error in libcrypto",
"/www1/new-api/new-api: cannot execute binary file: Exec format error",
"ReferenceError: Space is not defined", "scp: dest open
\"/www1/new-api/new-api\": Failure", and the directory listing to include
appropriate language tags (e.g., ```text or ```bash) so they render with syntax
highlighting and satisfy MD040; search for the bare ``` fences in the document
and add the language specifier to each affected block.
In `@model/token.go`:
- Around line 92-105: The DB update after setting token.ActivatedTime and
token.ExpiredTime (see token.ExpiredTime, token.Duration, token.ActivatedTime
and DB.Model(token).Select("activated_time","expired_time").Updates(token))
currently ignores errors and can lead to in-memory/db divergence and race
conditions; change this to perform an atomic/conditional update (e.g., within a
transaction or using a WHERE activated_time = 0 conditional update and checking
RowsAffected) and handle the returned error: if the update fails or affects 0
rows, revert the in-memory activation (reset ActivatedTime/ExpiredTime) and
avoid firing cacheSetToken in the gopool.Go block, otherwise proceed to call
cacheSetToken; ensure error is logged via common.SysLog when DB update fails.
In `@web/package.json`:
- Line 13: The project lists "antd" in package.json but no stylesheet import or
usage is present; either add the Ant Design reset CSS import or remove the
dependency: if you intend to use antd, add the line import
'antd/dist/reset.css'; to the entry file (web/src/index.jsx) before rendering so
Ant components are styled; if you do not intend to use antd (keeping
`@douyinfe/semi-ui` only), remove "antd" from package.json and run your package
manager to update lockfiles to avoid unused dependency and style conflicts.
In `@web/src/components/settings/ChannelSelectorModal.jsx`:
- Around line 257-266: In ChannelSelectorModal update the custom footer so
keyboard Enter confirms instead of cancelling: either add autoFocus={true} to
the primary Button (the one that calls onOk) or implement an Enter-key handler
on the modal/content that listens for keydown Enter and calls the onOk handler;
ensure you keep the existing onCancel/onOk bindings and focus behavior
consistent with Semi UI (attach the handler at the Modal content wrapper or
component mount/unmount lifecycle to avoid leaking listeners).
🧹 Nitpick comments (6)
web/src/components/layout/NoticeModal.jsx (1)
188-188: Minor: No-op className ternary.This existing code has a conditional that always evaluates to an empty string regardless of
item.isUnread. If there's intended styling for unread items, the appropriate class should be added; otherwise, this can be simplified.- className={item.isUnread ? '' : ''} + className=''Or remove the attribute entirely if no class is needed.
web/src/pages/Setting/Ratio/ModelSettingsVisualEditor.jsx (1)
525-572: Extract inline logic and reuse existing calculation functions.The
onClickhandler contains duplicated calculation logic:
- Line 539:
tokenPrice / 2duplicatescalculateRatioFromTokenPrice(line 276)- Lines 550-553: completion ratio calculation duplicates
calculateCompletionRatioFromPrices(lines 280-289)Additionally, if
currentModelis falsy, the button silently does nothing - consider showing a validation message or disabling the button.♻️ Proposed refactor to extract handler logic
+ const handleModalConfirm = () => { + if (!currentModel) { + showError(t('请先填写模型信息')); + return; + } + + const valuesToSave = { ...currentModel }; + + if ( + pricingMode === 'per-token' && + pricingSubMode === 'token-price' && + currentModel.tokenPrice + ) { + const tokenPrice = parseFloat(currentModel.tokenPrice); + valuesToSave.ratio = calculateRatioFromTokenPrice(tokenPrice).toString(); + + if (currentModel.completionTokenPrice && currentModel.tokenPrice) { + const completionPrice = parseFloat(currentModel.completionTokenPrice); + const modelPrice = parseFloat(currentModel.tokenPrice); + if (modelPrice > 0) { + valuesToSave.completionRatio = calculateCompletionRatioFromPrices( + modelPrice, + completionPrice, + ).toString(); + } + } + } + + if (pricingMode === 'per-token') { + valuesToSave.price = ''; + } else { + valuesToSave.ratio = ''; + valuesToSave.completionRatio = ''; + } + + addOrUpdateModel(valuesToSave); + }; <Button type='primary' - onClick={() => { - if (currentModel) { - // ... long inline logic - } - }} + onClick={handleModalConfirm} >web/src/components/table/users/modals/EnableDisableUserModal.jsx (1)
39-48: Consider adding aloadingprop for the Confirm button.The modal lacks a loading state for the Confirm button. If
onConfirmtriggers an async operation (e.g., API call to enable/disable the user), users won't see visual feedback.✨ Proposed enhancement to add loading support
const EnableDisableUserModal = ({ visible, onCancel, onConfirm, user, action, t, + loading = false, }) => {- <Button type='warning' theme='solid' onClick={onConfirm}> + <Button type='warning' theme='solid' onClick={onConfirm} loading={loading}> {t('确定')} </Button>docs/deployment-guide.md (1)
173-176: Consider using a dedicated service user instead of root.Running the service as
User=root(line 175) is a security risk. If the application is compromised, attackers gain root access.🔒 Suggested improvement for service user
[Service] Type=simple -User=root +User=newapi WorkingDirectory=/www1/new-apiAdd user creation to the deployment steps:
# Create dedicated service user sudo useradd -r -s /bin/false newapi sudo chown -R newapi:newapi /www1/new-apiweb/src/components/table/tokens/modals/DeleteTokensModal.jsx (1)
36-45: Consider adding loading state to the confirm button for consistency.Other modals in this PR (e.g.,
SettingsAnnouncements,SettingsFAQ) show a loading indicator on the confirm button during async operations. IfonConfirmis async, consider accepting and using aconfirmLoadingprop here as well.Optional: Add loading state
const DeleteTokensModal = ({ visible, onCancel, onConfirm, selectedKeys, t, + confirmLoading, }) => { ... - <Button type='warning' theme='solid' onClick={onConfirm}> + <Button type='warning' theme='solid' onClick={onConfirm} loading={confirmLoading}> {t('确定')} </Button>web/src/components/table/tokens/modals/EditTokenModal.jsx (1)
44-45: Unused imports detected.
RadioandInputGroupare imported but not used in the component. Consider removing them to keep the import list clean.Remove unused imports
- Radio, - InputGroup,
| Duration: token.Duration, | ||
| } | ||
| if token.Duration > 0 { | ||
| cleanToken.ExpiredTime = -1 | ||
| } |
There was a problem hiding this comment.
Validate Duration to avoid invalid negative values.
Right now any negative duration will flow into the model; consider rejecting it at the API boundary for both create and update.
🔧 Suggested guard
if len(token.Name) > 50 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "令牌名称过长",
})
return
}
+if token.Duration < 0 {
+ c.JSON(http.StatusOK, gin.H{
+ "success": false,
+ "message": "Duration 不能为负数",
+ })
+ return
+}🤖 Prompt for AI Agents
In `@controller/token.go` around lines 194 - 198, Reject negative Duration values
at the API boundary by adding validation in the token handlers (e.g.,
CreateToken and UpdateToken) to check token.Duration >= 0 and return a 400 /
validation error when it's negative; do not propagate negative Duration into the
model (cleanToken.Duration) and ensure any logic that sets
cleanToken.ExpiredTime (referencing cleanToken and token.Duration) only runs for
valid, non-negative durations.
| FROM golang:latest | ||
|
|
||
| WORKDIR /build | ||
| COPY web/package.json . | ||
| COPY web/bun.lock . | ||
| RUN bun install | ||
| COPY ./web . | ||
| COPY ./VERSION . | ||
| RUN DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) bun run build | ||
| WORKDIR /app | ||
|
|
||
| FROM golang:alpine AS builder2 | ||
| ENV GO111MODULE=on CGO_ENABLED=0 | ||
|
|
||
| ARG TARGETOS | ||
| ARG TARGETARCH | ||
| ENV GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} | ||
| ENV GOEXPERIMENT=greenteagc | ||
|
|
||
| WORKDIR /build | ||
|
|
||
| ADD go.mod go.sum ./ | ||
| RUN go mod download | ||
| ENV GOOS=windows | ||
| ENV GOARCH=amd64 | ||
| ENV CGO_ENABLED=0 | ||
|
|
||
| # 复制源码(包含我们伪造的 web/dist) | ||
| COPY . . | ||
| COPY --from=builder /build/dist ./web/dist | ||
| RUN go build -ldflags "-s -w -X 'github.com/QuantumNous/new-api/common.Version=$(cat VERSION)'" -o new-api | ||
|
|
||
| FROM debian:bookworm-slim | ||
|
|
||
| RUN apt-get update \ | ||
| && apt-get install -y --no-install-recommends ca-certificates tzdata libasan8 wget \ | ||
| && rm -rf /var/lib/apt/lists/* \ | ||
| && update-ca-certificates | ||
|
|
||
| COPY --from=builder2 /build/new-api / | ||
| EXPOSE 3000 | ||
| WORKDIR /data | ||
| ENTRYPOINT ["/new-api"] | ||
| # 下载依赖并编译 | ||
| RUN go mod download | ||
| RUN go build -ldflags "-s -w" -o new-api-galaxy.exe |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, explore the repo structure
echo "=== Repository structure ==="
ls -la
echo ""
# Look for CI workflows that might reference this Dockerfile
echo "=== CI Workflows ==="
find . -type d -name '.github' -o -name '.gitlab-ci' -o -name '.circleci' | head -20
if [ -d ".github/workflows" ]; then
echo "GitHub workflows found:"
ls -la .github/workflows/
echo ""
fi
# Look for deployment or documentation files
echo "=== Documentation files ==="
fd -e md -e rst | grep -i -E "(deploy|readme|dockerfile|build|docker)" | head -20
echo ""
# Check git history for recent Dockerfile changes
echo "=== Recent Dockerfile history ==="
git log --oneline -n 10 -- Dockerfile 2>/dev/null || echo "Git history not available"
echo ""
# Check for any other Dockerfile variations
echo "=== All Dockerfile variations ==="
fd -type f -name 'Dockerfile*'Repository: QuantumNous/new-api
Length of output: 3423
🏁 Script executed:
#!/bin/bash
# Check the CI workflows to see if they use this Dockerfile
echo "=== docker-image-alpha.yml ==="
cat .github/workflows/docker-image-alpha.yml
echo ""
echo "=== docker-image-arm64.yml ==="
cat .github/workflows/docker-image-arm64.yml
echo ""
# Check git diff for Dockerfile changes
echo "=== Git diff for Dockerfile (last 5 commits) ==="
git diff HEAD~1 -- Dockerfile 2>/dev/null || echo "Cannot get diff"
echo ""
# Check the git log with patches
echo "=== Git log for Dockerfile ==="
git log --oneline --all -- Dockerfile 2>/dev/null | head -20
# Look at the previous version of Dockerfile
echo ""
echo "=== Previous Dockerfile (HEAD~1) ==="
git show HEAD~1:Dockerfile 2>/dev/null || echo "Cannot retrieve previous version"Repository: QuantumNous/new-api
Length of output: 10206
🏁 Script executed:
#!/bin/bash
# Read the current Dockerfile
echo "=== Current Dockerfile content ==="
cat -n Dockerfile
echo ""
# Check git history more carefully - find when Dockerfile last changed meaningfully
echo "=== Last few actual Dockerfile changes ==="
git log -p --follow -- Dockerfile 2>/dev/null | head -200
echo ""
# Look at electron directory since it might be related to Windows builds
echo "=== Electron directory structure ==="
ls -la electron/
echo ""
# Check if there's a build-windows script or similar
echo "=== Windows-related files ==="
find . -type f -name "*windows*" -o -name "*exe*" 2>/dev/null | grep -v node_modules | grep -v .gitRepository: QuantumNous/new-api
Length of output: 1514
🏁 Script executed:
#!/bin/bash
# Check what deployment-guide.md says about Docker
echo "=== deployment-guide.md Docker section ==="
grep -A 20 -i "docker" docs/deployment-guide.md | head -40
echo ""
# Check docker-compose.yml to understand expected setup
echo "=== docker-compose.yml ==="
cat docker-compose.yml | head -50
echo ""
# Check if there's a makefile or build script for Windows
echo "=== makefile content ==="
cat makefile
echo ""
# Look at main.go to understand if this is desktop app or server
echo "=== main.go first 50 lines ==="
head -50 main.goRepository: QuantumNous/new-api
Length of output: 4514
Critical: Dockerfile produces Windows binary, breaking Docker deployments and CI/CD.
This Dockerfile compiles a Windows executable (new-api-galaxy.exe) but the CI/CD workflows (docker-image-alpha.yml, docker-image-arm64.yml) are configured to build for Linux platforms (linux/amd64, linux/arm64) and push to Docker Hub. The docker-compose.yml depends on the Linux image calciumion/new-api:latest. This is a fundamental incompatibility that will break:
- CI/CD pipelines that build and push to Docker registries
- Docker Compose deployments using the published images
- Any container-based deployment
Issues identified:
- GOOS=windows produces
.exebinary incompatible with linux/amd64 and linux/arm64 targets - No ENTRYPOINT/CMD - the image only compiles, doesn't define how to run
- "伪造的 web/dist" comment (fake web/dist) indicates this is temporary/test code
- golang:latest - unpinned tag reduces reproducibility
The application is an HTTP server (Gin framework, port 3000 per main.go), not a Windows desktop app. Restore the Linux-compatible Dockerfile or clarify if this is a separate Windows build artifact (not for Docker deployment).
🤖 Prompt for AI Agents
In `@Dockerfile` around lines 1 - 14, The Dockerfile is building a Windows binary
(GOOS=windows producing new-api-galaxy.exe) and lacks a runtime command and
reproducible base image; change GOOS/GOARCH/CGO_ENABLED to produce Linux
binaries matching CI targets (e.g. remove or set GOOS=linux and appropriate
GOARCH), build a non-.exe artifact (e.g. new-api-galaxy or new-api), add a
proper ENTRYPOINT or CMD to run the Gin HTTP server on port 3000, and pin the
base image (replace golang:latest with a specific tag) so CI/CD/docker-compose
images are compatible; update or remove the "伪造的 web/dist" copy step only if
it's temporary test data.
| ``` | ||
| Load key "xxx/id_ed25519": error in libcrypto | ||
| ``` |
There was a problem hiding this comment.
Add language identifiers to fenced code blocks.
Several code blocks are missing language specifiers, which triggers MD040 warnings and affects syntax highlighting.
📝 Suggested fixes for code block languages
**错误信息:**
-```
+```text
Load key "xxx/id_ed25519": error in libcrypto
```diff
**错误信息:**
-```
+```text
/www1/new-api/new-api: cannot execute binary file: Exec format error
```diff
**错误信息:**
-```
+```text
ReferenceError: Space is not defined
```diff
**错误信息:**
-```
+```text
scp: dest open "/www1/new-api/new-api": Failure
```diff
## 目录结构
-```
+```text
/www1/new-api/
├── new-api # 可执行文件
Also applies to: 41-43, 65-67, 93-95, 213-218
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)
[warning] 22-22: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In `@docs/deployment-guide.md` around lines 22 - 24, The Markdown contains several
fenced code blocks missing language identifiers (triggering MD040); update each
``` block that contains examples like "Load key \"xxx/id_ed25519\": error in
libcrypto", "/www1/new-api/new-api: cannot execute binary file: Exec format
error", "ReferenceError: Space is not defined", "scp: dest open
\"/www1/new-api/new-api\": Failure", and the directory listing to include
appropriate language tags (e.g., ```text or ```bash) so they render with syntax
highlighting and satisfy MD040; search for the bare ``` fences in the document
and add the language specifier to each affected block.
| if token.ExpiredTime == -1 && token.Duration > 0 && token.ActivatedTime == 0 { | ||
| token.ActivatedTime = common.GetTimestamp() | ||
| token.ExpiredTime = token.ActivatedTime + token.Duration | ||
| // Update DB | ||
| DB.Model(token).Select("activated_time", "expired_time").Updates(token) | ||
| // Update Redis if enabled | ||
| if common.RedisEnabled { | ||
| gopool.Go(func() { | ||
| if err := cacheSetToken(*token); err != nil { | ||
| common.SysLog("failed to update token cache: " + err.Error()) | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing error handling for DB update during token activation.
The database update on line 96 does not check the returned error. If the update fails, the token's in-memory state will be inconsistent with the database, and the function will continue as if activation succeeded.
Additionally, there's a potential race condition: if two concurrent requests validate the same sleeping token simultaneously, both may attempt activation before either persists. Consider using a transaction with row-level locking or an atomic conditional update (e.g., WHERE activated_time = 0).
Proposed fix for error handling and atomicity
if token.ExpiredTime == -1 && token.Duration > 0 && token.ActivatedTime == 0 {
- token.ActivatedTime = common.GetTimestamp()
- token.ExpiredTime = token.ActivatedTime + token.Duration
- // Update DB
- DB.Model(token).Select("activated_time", "expired_time").Updates(token)
+ now := common.GetTimestamp()
+ // Atomic conditional update to prevent race conditions
+ result := DB.Model(token).
+ Where("activated_time = ?", 0).
+ Select("activated_time", "expired_time").
+ Updates(map[string]interface{}{
+ "activated_time": now,
+ "expired_time": now + token.Duration,
+ })
+ if result.Error != nil {
+ common.SysLog("failed to activate token: " + result.Error.Error())
+ return token, errors.New("令牌激活失败")
+ }
+ if result.RowsAffected > 0 {
+ token.ActivatedTime = now
+ token.ExpiredTime = now + token.Duration
+ } else {
+ // Another request already activated; reload token
+ token, err = GetTokenByKey(key, true)
+ if err != nil {
+ return nil, err
+ }
+ }
// Update Redis if enabled🤖 Prompt for AI Agents
In `@model/token.go` around lines 92 - 105, The DB update after setting
token.ActivatedTime and token.ExpiredTime (see token.ExpiredTime,
token.Duration, token.ActivatedTime and
DB.Model(token).Select("activated_time","expired_time").Updates(token))
currently ignores errors and can lead to in-memory/db divergence and race
conditions; change this to perform an atomic/conditional update (e.g., within a
transaction or using a WHERE activated_time = 0 conditional update and checking
RowsAffected) and handle the returned error: if the update fails or affects 0
rows, revert the in-memory activation (reset ActivatedTime/ExpiredTime) and
avoid firing cacheSetToken in the gopool.Go block, otherwise proceed to call
cacheSetToken; ensure error is logged via common.SysLog when DB update fails.
| "@visactor/react-vchart": "~1.8.8", | ||
| "@visactor/vchart": "~1.8.8", | ||
| "@visactor/vchart-semi-theme": "~1.8.8", | ||
| "antd": "^6.2.2", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat web/package.jsonRepository: QuantumNous/new-api
Length of output: 2745
🌐 Web query:
Ant Design 6.2.2 peer dependencies React 18 Vite compatibility
💡 Result:
- Peer dependencies (antd 6.2.2):
react >= 18.0.0andreact-dom >= 18.0.0. No other peer deps are required byantditself. [1] - React 18: Fully supported (v6 raised the minimum supported React version to 18). [2]
- Vite compatibility:
antd6.2.2 ships ESM (module: es/index.js) and marks*.cssas side effects, which works cleanly with Vite’s dependency pre-bundling and tree-shaking. [1]- For styles, you can import the provided CSS entry (commonly
antd/dist/reset.css; it exists in 6.2.2). [3]
- For styles, you can import the provided CSS entry (commonly
Sources: [1] [2] [3]
🏁 Script executed:
# Search for existing style/CSS imports in the codebase
rg -i "antd|styles|css|import.*from.*antd" web/src --type js --type jsx --type ts --type tsx -A 2 -B 2 | head -50Repository: QuantumNous/new-api
Length of output: 90
🏁 Script executed:
# Check if there's a main entry point or setup file
fd -e main.js -e main.jsx -e main.ts -e main.tsx -e app.js -e app.jsx -e app.ts -e app.tsx web/srcRepository: QuantumNous/new-api
Length of output: 396
🏁 Script executed:
# Find main entry point in web/src
fd . web/src --max-depth 1 -e js -e jsx -e ts -e tsxRepository: QuantumNous/new-api
Length of output: 96
🏁 Script executed:
# Search for any existing antd or CSS imports
rg "import.*antd|import.*\.css|from.*antd" web/src --max-count 20Repository: QuantumNous/new-api
Length of output: 437
🏁 Script executed:
# Check vite.config file
fd vite.config webRepository: QuantumNous/new-api
Length of output: 81
🏁 Script executed:
cat -n web/src/index.jsx | head -30Repository: QuantumNous/new-api
Length of output: 1475
🏁 Script executed:
cat -n web/src/App.jsx | head -50Repository: QuantumNous/new-api
Length of output: 2610
🏁 Script executed:
cat -n web/vite.config.jsRepository: QuantumNous/new-api
Length of output: 3587
🏁 Script executed:
# Check if any antd components are actually imported/used
rg "from 'antd'|from \"antd\"|Button|Modal|Form|Input|Select" web/src --max-count 15Repository: QuantumNous/new-api
Length of output: 50376
🏁 Script executed:
rg "from\s+['\"]antd['\"]" web/srcRepository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Check if antd is imported/used anywhere at all
rg "import.*from.*['\"]antd['\"]" web/srcRepository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Verify the full index.jsx to see all imports
wc -l web/src/index.jsxRepository: QuantumNous/new-api
Length of output: 83
🏁 Script executed:
cat -n web/src/index.jsx | tail -20Repository: QuantumNous/new-api
Length of output: 664
Add required Ant Design CSS import and clarify intent to use it.
Ant Design v6.2.2 is compatible with React 18.2.0 and Vite 5.2.0, but the integration is incomplete: no CSS is imported in the entry point, so any antd components will render unstyled. Additionally, antd is not imported anywhere in the codebase, yet the project already uses @douyinfe/semi-ui as its complete UI library.
Either add import 'antd/dist/reset.css'; to web/src/index.jsx if antd will be used, or remove the dependency if semi-ui is the intended choice. Using both libraries together will cause style conflicts, increased bundle size, and maintenance burden.
🤖 Prompt for AI Agents
In `@web/package.json` at line 13, The project lists "antd" in package.json but no
stylesheet import or usage is present; either add the Ant Design reset CSS
import or remove the dependency: if you intend to use antd, add the line import
'antd/dist/reset.css'; to the entry file (web/src/index.jsx) before rendering so
Ant components are styled; if you do not intend to use antd (keeping
`@douyinfe/semi-ui` only), remove "antd" from package.json and run your package
manager to update lockfiles to avoid unused dependency and style conflicts.
| footer={ | ||
| <div className='flex justify-end'> | ||
| <Space> | ||
| <Button onClick={onCancel}>{t('取消')}</Button> | ||
| <Button type='primary' onClick={onOk}> | ||
| {t('确定')} | ||
| </Button> | ||
| </Space> | ||
| </div> | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Semi UI Modal custom footer Enter key keyboard confirmation behavior
💡 Result:
In Semi UI Modal, Enter triggers the currently focused element. By default, when the modal opens, focus is on the Cancel button, but you can change that via okButtonProps / cancelButtonProps autoFocus. (semi.design)
If you are using the built-in footer (recommended)
Make OK focused on open, so Enter = OK:
<Modal
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{ autoFocus: true }}
cancelButtonProps={{ autoFocus: false }}
/>(Semi documents that initial focus defaults to Cancel and can be controlled by autoFocus in those props.) (semi.design)
If you set a custom footer
When you override footer, you’re responsible for keyboard-confirm behavior. Semi explicitly supports custom footer rendering. (semi.design)
Typical approach: (1) autoFocus your primary button, and/or (2) add an Enter key handler.
const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
// avoid interfering with multiline inputs if you have them
const tag = (e.target as HTMLElement).tagName;
if (tag === 'TEXTAREA') return;
e.preventDefault();
handleOk();
}
};
<Modal
visible={visible}
onCancel={handleCancel}
footer={
<Button type="primary" onClick={handleOk} autoFocus>
Confirm
</Button>
}
>
<div onKeyDown={onKeyDown}>
{/* modal content */}
</div>
</Modal>If you share your exact footer/content structure (e.g., forms, inputs, textarea), I can tailor the “when to trigger Enter” rules.
Citations:
- 1: https://semi.design/en-US/show/modal?utm_source=openai
- 2: https://semi.design/en-US/show/modal?utm_source=openai
- 3: https://semi.design/en-US/show/modal?utm_source=openai
🏁 Script executed:
# First, let's find and examine the ChannelSelectorModal.jsx file
find . -name "ChannelSelectorModal.jsx" -type fRepository: QuantumNous/new-api
Length of output: 117
🏁 Script executed:
# Read the ChannelSelectorModal.jsx file to see the full context
cat -n ./web/src/components/settings/ChannelSelectorModal.jsx | head -300Repository: QuantumNous/new-api
Length of output: 10513
🏁 Script executed:
# Continue reading the rest of the file
cat -n ./web/src/components/settings/ChannelSelectorModal.jsx | tail -n +300Repository: QuantumNous/new-api
Length of output: 196
Add autoFocus={true} to the OK button or implement Enter-key handling.
The current custom footer lacks keyboard confirmation: the OK button doesn't have autoFocus={true}, and there's no Enter-key handler. Per Semi UI documentation, with a custom footer you're responsible for keyboard behavior. Without an explicit autoFocus or handler, pressing Enter will trigger the Cancel button instead of confirming. Either set autoFocus={true} on the primary Button or add an Enter-key handler to the Modal's content.
🤖 Prompt for AI Agents
In `@web/src/components/settings/ChannelSelectorModal.jsx` around lines 257 - 266,
In ChannelSelectorModal update the custom footer so keyboard Enter confirms
instead of cancelling: either add autoFocus={true} to the primary Button (the
one that calls onOk) or implement an Enter-key handler on the modal/content that
listens for keydown Enter and calls the onOk handler; ensure you keep the
existing onCancel/onOk bindings and focus behavior consistent with Semi UI
(attach the handler at the Modal content wrapper or component mount/unmount
lifecycle to avoid leaking listeners).
Summary
This PR introduces the "Token Sleep Mode" (Auto-activation) feature and includes several UI enhancements and fixes.
Key Changes
EditTokenModal.onOkprops where custom footers are used.CodexOAuthModal).Testing
Summary by CodeRabbit
New Features
UI/UX Improvements
Chores