diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000000..19f5147d0f9b --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,45 @@ +name: Remote Deploy + +on: + push: + branches: [ main ] + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push (amd64) + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + push: true + tags: chasebank2023/new-api:latest + + - name: SSH Remote Deploy + uses: appleboy/ssh-action@v1.0.3 + with: + host: 101.36.104.77 + username: root + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + cd /opt/openclawapi.ai + sed -i 's|image:.*new-api.*|image: chasebank2023/new-api:latest|g' docker-compose.yml + docker compose pull new-api + docker compose up -d new-api + docker image prune -f diff --git a/Dockerfile b/Dockerfile index aa43de1c9603..707eb044c619 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,8 @@ COPY web/bun.lock . RUN bun install COPY ./web . COPY ./VERSION . +# 限制前端构建内存,避免 Docker 内存不足 (cannot allocate memory) +ENV NODE_OPTIONS="--max-old-space-size=2048" RUN DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) bun run build FROM golang:alpine AS builder2 diff --git a/common/constants.go b/common/constants.go index 6823b2c813e6..230c46239d2f 100644 --- a/common/constants.go +++ b/common/constants.go @@ -12,8 +12,8 @@ import ( var StartTime = time.Now().Unix() // unit: second var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change -var SystemName = "New API" -var Footer = "" +var SystemName = "OpenClaw API" +var Footer = "© 2026 OpenClaw API. All rights reserved." var Logo = "" var TopUpLink = "" diff --git a/docker-compose.yml b/docker-compose.yml index a9d00967cf49..98e2a945a262 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,10 @@ version: '3.4' # For compatibility with older Docker versions services: new-api: - image: calciumion/new-api:latest + build: + context: . + dockerfile: Dockerfile + image: new-api:local container_name: new-api restart: always command: --log-dir /app/logs diff --git a/docs/BACKEND-FRONTEND-ALIGNMENT.md b/docs/BACKEND-FRONTEND-ALIGNMENT.md new file mode 100644 index 000000000000..46686051c162 --- /dev/null +++ b/docs/BACKEND-FRONTEND-ALIGNMENT.md @@ -0,0 +1,117 @@ +# 后端与官网视觉/交互统一改造说明 + +本文档说明已完成的改造,以及登录注册、OAuth、用户工作台入口的后续建议。 + +## 一、已完成的改造 + +### 布局与官网一致(顶栏、主内容、页脚、侧栏) + +- **顶栏**:与官网 Navbar 一致——`max-w-7xl` 容器居中、`border-b border-semi-color-border`、`bg-semi-color-bg-0/80 backdrop-blur-xl`,固定顶部。 +- **主内容区**:控制台内容统一包在 `mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6` 容器内,垂直 `flex flex-col gap-6`,避免挤在一起;主布局增加 `marginTop: 64` 避免被固定顶栏遮挡。 +- **页脚**:与官网 Footer 一致——`border-t border-semi-color-border`、`max-w-7xl` 内层、五列网格(品牌/产品/支持/资源/法律)、底栏版权 + 社交链接;**已去除底部装饰性红球/插图**。 +- **侧边栏**:与官网设计体系一致——`border-right: 1px solid var(--semi-color-border)`,沿用现有品牌色悬停与选中样式。 + +### Empty 状态无插图 + +- 404、403、About 空状态、仪表盘监控列表、以及**所有主要表格**(Tokens、Users、UsageLogs、TaskLogs、Subscriptions、Redemptions、Models、Channels、Deployments、MjLogs)的 Empty 已改为 `image={Empty.PRESENTED_IMAGE_SIMPLE}`,**不再使用 semi-illustrations 的插画**。 +- 若其他页面(如部分 Setting 子页、Modal、NoticeModal、DocumentRenderer 等)仍使用 `IllustrationNoResult` / `IllustrationNoContent` / `IllustrationConstruction`,可按同样方式改为 `Empty.PRESENTED_IMAGE_SIMPLE` 并删除对应 `semi-illustrations` 引用。 + +### 1. 主题(深/浅色)与前端完全一致 + +- **存储键统一**:后端与官网共用 `localStorage` 键名 `theme`,取值仅 `dark` | `light`。 +- **移除「跟随系统」**:后端不再提供「自动/跟随系统」选项,仅保留深色/浅色切换,与官网一致。 +- **ThemeToggle 组件**:由下拉(浅色/深色/自动)改为**单一按钮**:当前为深色时显示太阳图标(点击切浅色),当前为浅色时显示月亮图标(点击切深色),交互与官网 Navbar 一致。 +- **效果**:在官网选择深色后,再打开控制台(api.openclawapi.ai)会保持深色;反之亦然。 + +### 2. 语言与前端完全一致 + +- **存储键统一**:后端与官网共用 `localStorage` 键名 `locale`,取值 `en` | `zh`。 +- **i18n 配置**:后端 i18next 使用 `lookupLocalStorage: 'locale'`,检测与缓存均写入 `locale`;切换语言时显式 `localStorage.setItem('locale', lang)`。 +- **效果**:在官网切换为中文后,再打开控制台会保持中文;控制台切换语言后,再回官网也会一致。 + +### 3. 页脚与品牌统一 + +- **版权文案**:与官网一致——中文「© {年份} OpenClaw API. 保留所有权利。」,英文「All rights reserved.」。 +- **品牌描述**:与官网一致,使用 i18n「页脚品牌描述(与官网一致)」:中文为 OpenClaw 官方适配 API 的完整描述,英文为 Enterprise-grade AI model gateway 等。 +- **Footer 链接 hover**:列内链接 hover 使用 accent 色(`var(--oc-accent)`),与官网一致。 +- **自定义 HTML 页脚**:当使用管理员配置的自定义 footer_html 时,不再叠加「设计与开发由 OpenClaw API」署名,避免与官网风格违和。 +- **Demo 站页脚**:移除「相关项目」「友情链接」中的第三方项目链接;保留五列(品牌/产品/支持/资源/法律)。 + +### 4. 语言选择器与前端一致 + +- **展示**:Globe 图标 + 当前语言名称(中文 / English),与官网一致。 +- **仅两档**:仅支持 中文 / English,移除国旗图标,下拉样式与品牌主色一致。 + +### 5. 注释与品牌 + +- 已修改的模块中,将 QuantumNous/AGPL 版权头替换为简短「OpenClaw API」说明,避免露出上游项目信息。 + +--- + +## 二、登录/注册与第三方登录的后续建议 + +您提到的需求包括: +前端 Sign In 与后端用户体系一致、支持 Google/Apple/微软/微信/支付宝/QQ/手机号等登录方式。 + +### 当前后端能力(new-api 已有) + +- **OAuth**:已有 GitHub、Discord、OIDC、LinuxDO 等(见 `oauth/`、`controller/oauth.go`)。 +- **登录入口**:后端提供 `/login`、`/register` 等页面;前端官网 Navbar 的「登录」「获取 API Key」指向 `https://api.openclawapi.ai/login` 与 `https://api.openclawapi.ai/register`,即**同一套账号体系**。 + +### 建议的改造方向(分阶段) + +1. **统一入口与话术** + - 官网「登录」→ 跳转 api.openclawapi.ai 登录页(已如此)。 + - 官网「获取 API Key」→ 未登录时跳转注册/登录,已登录跳转控制台令牌页或首页。 + - 控制台内「用户管理」仅对管理员可见;普通用户只有「个人设置」「令牌」「用量」等,避免与「官网登录」产生概念割裂。 + +2. **第三方登录扩展** + - **Google / Apple / Microsoft**:在现有 OAuth 体系上增加对应 Provider(如 OIDC 或各平台 OAuth2),配置 Client ID/Secret,在登录/注册页增加「使用 Google/Apple/Microsoft 登录」按钮。 + - **微信 / 支付宝 / QQ**:需使用各平台开放平台(微信开放平台、支付宝开放平台、QQ 互联)的 OAuth 或扫码登录,后端新增对应 oauth provider 与路由,前端登录页增加对应按钮。 + - **手机号验证登录**:需短信/验证码服务(如 Twilio、阿里云、腾讯云),后端提供发送验证码、校验接口,登录/注册页提供「手机号 + 验证码」选项。 + +3. **前后端共享登录状态(可选)** + - 若官网与 api 同主域(例如 openclawapi.ai 与 api.openclawapi.ai),可考虑共享 Cookie 或统一 SSO;若跨域,可继续使用「官网点登录 → 跳转 api 子域登录」的当前方式,并在登录后重定向回官网或控制台。 + +以上可作为后续迭代的改造计划,按优先级分阶段实现。 + +--- + +## 三、用户工作台入口建议 + +- **官网**:导航「登录」→ 控制台登录页;「获取 API Key」→ 未登录先注册/登录,已登录→ 控制台(令牌或首页)。 +- **控制台**:登录后首页即「工作台」(用量、令牌、充值等);侧栏根据角色显示「用户管理」(仅管理员)、「令牌」「用量」「充值」等,不单独强调「用户管理」为第一入口,避免与官网「登录」概念混淆。 + +这样用户会自然形成「官网 = 品牌与文档,控制台 = 登录后的工作台」的认知,前后端一体感更强。 + +--- + +## 四、官网与后端一致性审查与巡检(2025-02) + +以下为对官网(openclawapi.ai)与后端控制台(new-api/web)的逐项对比结论与已做修正。 + +### 已修复的不一致 + +| 项目 | 官网 | 后端(修复前) | 修复 | +|------|------|----------------|------| +| 页脚版权 | 「保留所有权利。」/「All rights reserved.» | 「版权所有」/「All rights reserved」 | 统一为「保留所有权利。」并加 i18n | +| 页脚品牌描述 | 完整 tagline(OpenClaw 官方适配…) | 短句「企业级AI模型…」 | 使用 i18n「页脚品牌描述(与官网一致)」 | +| Footer 链接 hover | `hover:text-accent`(青色) | `hover:text-primary`(红) | 改为 `hover:!text-[var(--oc-accent)]` | +| 自定义 HTML 页脚 | 无额外署名 | 有「设计与开发由 OpenClaw API」 | 移除该署名块 | +| Dashboard 顶栏按钮 | 品牌色体系 | 绿/蓝(green-500, blue-500) | 搜索→ primary 红,刷新→ accent 青;标题用 `!text-semi-color-text-0` | + +### 已一致、无需改动的部分 + +- **容器**:顶栏、主内容、页脚均为 `max-w-7xl px-4 sm:px-6 lg:px-8`,与官网一致。 +- **顶栏高度**:`h-16`,与官网一致。 +- **页脚结构**:五列网格(品牌/产品/支持/资源/法律)、底栏版权+社交图标,与官网一致。 +- **字体与排版**:`index.css` 已设 16px、line-height 1.7、标题 600,与官网 globals.css 一致。 +- **品牌色与选中**:`--oc-brand`、`--oc-accent`、选中红色背景深色字,与官网一致。 +- **卡片**:12px 圆角、hover 红色边框与阴影,与官网 card-hover 一致。 + +### 已完成的后续统一项 + +- **顶栏 Logo 文字**:HeaderLogo 内对 `systemName` 中的「Claw」做品牌色高亮(`renderBrandName`),与官网「Open**Claw** API」一致。 +- **Footer 品牌区 logo**:新增 `CrabLogo.jsx`(与官网 SVG 一致),customFooter 品牌列为 logo + 文字,并链至官网。 +- **业务组件颜色**:充值/订阅卡片、定价卡片、仪表盘、签到日历、Auth 链接等处的紫色/蓝色/橙色已改为 `!text-semi-color-primary` 或 `!text-[var(--oc-accent)]`,灰色标题改为 `!text-semi-color-text-0`。 +- **其余 Empty 插图**:UpstreamRatioSync、SettingsUptimeKuma/FAQ/Announcements/APIInfo、TopupHistoryModal、UserSubscriptionsModal、PrefillGroupManagement、MissingModelsModal、PricingTable、PricingCardView、SingleModelSelectModal、ModelSelectModal、MultiKeyManageModal、NoticeModal、UptimePanel、FaqPanel、ApiInfoPanel、AnnouncementsPanel、DocumentRenderer 均已改为 `Empty.PRESENTED_IMAGE_SIMPLE`,并移除 `@douyinfe/semi-illustrations` 引用。 diff --git a/docs/DEPLOY-VIA-DOCKERHUB.md b/docs/DEPLOY-VIA-DOCKERHUB.md new file mode 100644 index 000000000000..831d2947ed22 --- /dev/null +++ b/docs/DEPLOY-VIA-DOCKERHUB.md @@ -0,0 +1,99 @@ +# 通过 Docker Hub 部署后端(本地构建 → 推送 → 服务器拉取重启) + +## 一、本机执行(构建并推送) + +在 **new-api** 项目根目录执行。将 `YOUR_DOCKERHUB_USER` 换成你的 Docker Hub 用户名。 + +```bash +cd /Users/chasebank2017/Downloads/new-api + +# 1. 登录 Docker Hub(未登录时执行一次) +docker login + +# 2. 构建镜像(约 10–15 分钟,Vite 打包较慢) +docker build -t YOUR_DOCKERHUB_USER/new-api:latest . + +# 3. 推送到 Docker Hub +docker push YOUR_DOCKERHUB_USER/new-api:latest +``` + +构建若因内存不足被 Kill,可在 Docker Desktop → Settings → Resources 将 Memory 调到 6GB+ 后重试。 + +--- + +## 二、服务器执行(拉取并重启) + +SSH 登录后,**二选一**: + +### 方式 A:服务器上改用「仅拉镜像、不构建」 + +编辑 `docker-compose.yml`,把 **new-api** 的 `build` 整段删掉,只保留 `image` 并改成你的镜像名: + +```yaml + new-api: + image: YOUR_DOCKERHUB_USER/new-api:latest # 改成你的 Docker Hub 用户名 + container_name: openclawapi-backend + restart: always + ports: + - "3001:3000" + volumes: + - new-api-data:/data + environment: + - SQL_DSN=root:${MYSQL_ROOT_PASSWORD:-openclawapi2024}@tcp(mysql:3306)/new_api + - REDIS_CONN_STRING=redis://redis:6379 + - SESSION_SECRET=${SESSION_SECRET:-openclawapi-session-secret-change-me} + - TZ=Asia/Shanghai + - SYSTEM_NAME=OpenClaw API + - FOOTER_HTML=© 2026 OpenClaw API. All rights reserved. + - LOGO= + - THEME=default + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_started + networks: + - openclawapi +``` + +然后执行: + +```bash +cd /opt/openclawapi.ai +sudo docker compose pull new-api +sudo docker compose up -d new-api +``` + +### 方式 B:不改 compose,每次先拉再强制用新镜像重启 + +若暂时不想改 `docker-compose.yml`(仍保留 `build`),可先拉你的镜像再强制用该镜像起容器: + +```bash +cd /opt/openclawapi.ai +sudo docker pull YOUR_DOCKERHUB_USER/new-api:latest +sudo docker compose stop new-api +sudo docker compose rm -f new-api +# 用刚拉取的镜像起一个新容器(需与当前 compose 里端口、卷、环境一致) +sudo docker run -d --name openclawapi-backend --restart always \ + -p 3001:3000 \ + -v openclawapi_new-api-data:/data \ + -e SQL_DSN="root:${MYSQL_ROOT_PASSWORD:-openclawapi2024}@tcp(mysql:3306)/new_api" \ + -e REDIS_CONN_STRING=redis://redis:6379 \ + -e SESSION_SECRET="${SESSION_SECRET:-openclawapi-session-secret-change-me}" \ + -e TZ=Asia/Shanghai \ + -e SYSTEM_NAME="OpenClaw API" \ + --network openclawapi_openclawapi \ + YOUR_DOCKERHUB_USER/new-api:latest +``` + +推荐用 **方式 A**,把 compose 里 new-api 改成只填 `image: YOUR_DOCKERHUB_USER/new-api:latest`,以后更新只需 `docker compose pull new-api && docker compose up -d new-api`。 + +--- + +## 三、验证 + +```bash +docker ps --filter name=openclawapi-backend --format "{{.Image}} {{.Status}}" +``` + +浏览器访问:`https://api.openclawapi.ai`(或你的后端地址),确认为最新界面。 diff --git a/docs/OPENCLAW_DEPLOY.md b/docs/OPENCLAW_DEPLOY.md new file mode 100644 index 000000000000..3099af3d97b3 --- /dev/null +++ b/docs/OPENCLAW_DEPLOY.md @@ -0,0 +1,104 @@ +# OpenClaw API 定制版部署说明 + +本仓库已按 [OpenClaw API](https://openclawapi.ai) 官网前端的品牌与样式做了定制,包括: + +## 已做修改摘要 + +| 位置 | 修改内容 | +|------|----------| +| **common/constants.go** | 默认 `SystemName = "OpenClaw API"`,`Footer = "© 2026 OpenClaw API. All rights reserved."` | +| **web/index.html** | 标题、描述、theme-color、favicon 指向 OpenClaw 品牌 | +| **web/public/logo.svg** | 使用官网同款螃蟹爪 Logo(coral-red #ff4d4d) | +| **web/src/helpers/utils.jsx** | 未配置时的默认系统名为「OpenClaw API」,默认 logo 为 `/logo.svg` | +| **web/src/components/layout/Footer.jsx** | 页脚品牌与链接改为 OpenClaw API / openclawapi.ai,文档链接指向官网 | +| **web/src/index.css** | 注入 OpenClaw 品牌主题:coral-red 主色、cyan 强调色、深/浅色背景、Satoshi 字体、侧栏选中样式 | + +效果:管理后台与官网在**品牌名、Logo、主色、字体、深色主题**上保持一致。 + +--- + +## 部署方式一:使用官方 New-API 镜像(未改代码) + +若你使用的是上游 `calciumion/new-api:latest` 等官方镜像,未使用本定制代码: + +1. 在 **docker-compose** 中设置环境变量: + ```yaml + environment: + - SYSTEM_NAME=OpenClaw API + - FOOTER_HTML=© 2026 OpenClaw API. All rights reserved. + ``` +2. 登录管理后台 → **系统设置** → **自定义设置** → **自定义 CSS**,粘贴 [OpenClaw API 仓库](https://github.com/openclawapi/openclawapi-ai) 中 `backend-theme/custom.css` 的**全部内容**并保存,即可获得与官网一致的配色与品牌名显示。 + +--- + +## 部署方式二:使用本定制版构建镜像(推荐) + +使用本仓库(已含 OpenClaw 品牌与主题)构建镜像并部署到 openclawapi.ai 栈。 + +### 1. 构建镜像 + +在 **new-api 项目根目录**(即本仓库)执行: + +```bash +docker build -t openclawapi/new-api:latest . +``` + +(若使用 Docker Compose 的 build 方式,见下。) + +### 2. 在 openclawapi-ai 项目中用本镜像替换官方镜像 + +在 **openclawapi-ai** 的 `docker-compose.yml` 中,将 `new-api` 服务由拉取镜像改为**构建本地 new-api**,或使用你刚构建的镜像。 + +**方式 A:使用已构建的镜像** + +```yaml +new-api: + image: openclawapi/new-api:latest # 使用上面构建的镜像 + container_name: openclawapi-backend + # ... 其余配置不变,保留 SYSTEM_NAME、FOOTER_HTML 等环境变量 +``` + +**方式 B:从本地 new-api 目录构建** + +假设 new-api 与 openclawapi-ai 同级目录: + +```yaml +new-api: + build: + context: ../new-api + dockerfile: Dockerfile + image: openclawapi/new-api:latest + container_name: openclawapi-backend + # ... 其余配置不变 +``` + +### 3. 启动/更新 + +```bash +cd /path/to/openclawapi-ai +docker compose up -d --build new-api +``` + +访问 `https://api.openclawapi.ai`,应看到 OpenClaw API 品牌名、Logo、coral-red 主题与 Satoshi 字体;无需再粘贴自定义 CSS 即可与官网风格一致(若仍需微调,仍可在后台粘贴 `backend-theme/custom.css`)。 + +--- + +## 与官网前端的一致性 + +| 项目 | 说明 | +|------|------| +| **品牌名** | 系统名称、页脚、标题均为 OpenClaw API | +| **Logo** | 使用与官网一致的螃蟹爪 SVG(#ff4d4d) | +| **主色** | coral-red #ff4d4d(hover #ff6b6b,active #e63946) | +| **强调色** | cyan #00e5cc(成功/信息) | +| **深色背景** | #050810、#0a0f1a、#111827、#1a2035 | +| **字体** | Satoshi(body),与官网一致 | +| **文档/链接** | 关于、文档、API 文档等指向 openclawapi.ai | + +--- + +## 故障排查 + +- **仍显示「New API」**:清除浏览器缓存并重新登录;确认后台「系统设置」中系统名称已设为「OpenClaw API」并保存。 +- **Logo 不显示**:确认 `web/public/logo.svg` 存在且默认 getLogo 返回 `/logo.svg`;若后台自定义了 Logo 地址,请填写可访问的 URL。 +- **主题未生效**:确认 `web/src/index.css` 已包含 OpenClaw 主题变量;若使用官方镜像,必须在「自定义 CSS」中粘贴完整 `custom.css`。 diff --git a/makefile b/makefile index cbc4ea6ae22d..9610de524b8d 100644 --- a/makefile +++ b/makefile @@ -1,14 +1,32 @@ -FRONTEND_DIR = ./web -BACKEND_DIR = . +# 远程 VPS 配置 +REMOTE_IP = 101.36.104.77 +REMOTE_DIR = /opt/openclawapi.ai +DOCKER_IMAGE = chasebank2023/new-api:latest -.PHONY: all build-frontend start-backend +# 远程部署命令 +DEPLOY_CMD = ssh root@$(REMOTE_IP) "cd $(REMOTE_DIR) && \ + sed -i 's|image:.*new-api.*|image: $(DOCKER_IMAGE)|g' docker-compose.yml && \ + docker compose pull new-api && \ + docker compose up -d new-api && \ + docker image prune -f" -all: build-frontend start-backend +.PHONY: deploy push-only update-remote -build-frontend: - @echo "Building frontend..." - @cd $(FRONTEND_DIR) && bun install && DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) bun run build +# 一键完成:构建(amd64) -> 推送 -> 远程更新 +deploy: + @echo "🚀 [Local] 正在进行交叉编译并推送 amd64 镜像到 Docker Hub..." + docker buildx build --platform linux/amd64 -t $(DOCKER_IMAGE) --push . + @echo "✅ [Local] 镜像推送成功!" + @echo "📡 [Remote] 正在通知 VPS ($(REMOTE_IP)) 执行更新..." + @$(DEPLOY_CMD) + @echo "🎉 [Finish] 远程更新已完成!" -start-backend: - @echo "Starting backend dev server..." - @cd $(BACKEND_DIR) && go run main.go & +# 仅推送镜像 (amd64) +push-only: + @echo "📤 正在推送 amd64 镜像..." + docker buildx build --platform linux/amd64 -t $(DOCKER_IMAGE) --push . + +# 仅触发远程更新 (假设镜像已更新) +update-remote: + @echo "📡 仅触发远程更新..." + @$(DEPLOY_CMD) diff --git a/router/web-router.go b/router/web-router.go index b053a3e63d94..465242c4a43f 100644 --- a/router/web-router.go +++ b/router/web-router.go @@ -17,6 +17,11 @@ func SetWebRouter(router *gin.Engine, buildFS embed.FS, indexPage []byte) { router.Use(gzip.Gzip(gzip.DefaultCompression)) router.Use(middleware.GlobalWebRateLimit()) router.Use(middleware.Cache()) + // 显式处理根路径,确保 api.openclawapi.ai/ 可访问 + router.GET("/", func(c *gin.Context) { + c.Header("Cache-Control", "no-cache") + c.Data(http.StatusOK, "text/html; charset=utf-8", indexPage) + }) router.Use(static.Serve("/", common.EmbedFolder(buildFS, "web/dist"))) router.NoRoute(func(c *gin.Context) { if strings.HasPrefix(c.Request.RequestURI, "/v1") || strings.HasPrefix(c.Request.RequestURI, "/api") || strings.HasPrefix(c.Request.RequestURI, "/assets") { diff --git a/scripts/deploy-remote.sh b/scripts/deploy-remote.sh new file mode 100755 index 000000000000..050aa8301a92 --- /dev/null +++ b/scripts/deploy-remote.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# 在本地执行:SSH 到服务器 → sed 改 compose 镜像 → pull → up → prune +# 用法:./scripts/deploy-remote.sh 或 bash scripts/deploy-remote.sh + +set -e +SERVER="${DEPLOY_SERVER:-root@101.36.104.77}" +COMPOSE_DIR="${DEPLOY_COMPOSE_DIR:-/opt/openclawapi.ai}" +IMAGE="${DEPLOY_IMAGE:-chasebank2023/new-api:latest}" + +echo "→ 在服务器 $SERVER 上更新 new-api 并重启 ..." +ssh "$SERVER" "cd $COMPOSE_DIR && \ + sed -i 's|image:.*new-api.*|image: $IMAGE|g' docker-compose.yml && \ + docker compose pull new-api && \ + docker compose up -d new-api && \ + docker image prune -f" +echo "→ 完成。可访问 https://api.openclawapi.ai 检查。" diff --git a/web/index.html b/web/index.html index 3946f661954a..8aca8df0e6b9 100644 --- a/web/index.html +++ b/web/index.html @@ -2,15 +2,15 @@ - + - + - - New API + + OpenClaw API diff --git a/web/public/logo.svg b/web/public/logo.svg new file mode 100644 index 000000000000..46a323be03c8 --- /dev/null +++ b/web/public/logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/web/src/App.jsx b/web/src/App.jsx index 91c4d1a1e0b6..b58868ba1776 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com */ import React, { lazy, Suspense, useContext, useMemo } from 'react'; -import { Route, Routes, useLocation } from 'react-router-dom'; +import { Route, Routes, useLocation, Navigate } from 'react-router-dom'; import Loading from './components/common/ui/Loading'; import User from './pages/User'; import { AuthRedirect, PrivateRoute, AdminRoute } from './helpers'; @@ -49,10 +49,9 @@ import OAuth2Callback from './components/auth/OAuth2Callback'; import PersonalSetting from './components/settings/PersonalSetting'; import Setup from './pages/Setup'; import SetupCheck from './components/layout/SetupCheck'; +import DocsPage from './pages/Docs'; -const Home = lazy(() => import('./pages/Home')); const Dashboard = lazy(() => import('./pages/Dashboard')); -const About = lazy(() => import('./pages/About')); const UserAgreement = lazy(() => import('./pages/UserAgreement')); const PrivacyPolicy = lazy(() => import('./pages/PrivacyPolicy')); @@ -75,7 +74,6 @@ function App() { // 如果是对象格式,使用requireAuth配置 return modules.pricing?.requireAuth === true; } catch (error) { - console.error('解析顶栏模块配置失败:', error); return false; // 默认不需要登录 } } @@ -85,14 +83,7 @@ function App() { return ( - } key={location.pathname}> - - - } - /> + } /> } /> + } key={location.pathname}> + + + } + /> - } key={location.pathname}> - - - } - /> + } /> { href='/user-agreement' target='_blank' rel='noopener noreferrer' - className='text-blue-600 hover:text-blue-800 mx-1' + className='!text-semi-color-primary hover:!opacity-90 mx-1' > {t('用户协议')} @@ -673,7 +673,7 @@ const LoginForm = () => { href='/privacy-policy' target='_blank' rel='noopener noreferrer' - className='text-blue-600 hover:text-blue-800 mx-1' + className='!text-semi-color-primary hover:!opacity-90 mx-1' > {t('隐私政策')} @@ -690,7 +690,7 @@ const LoginForm = () => { {t('没有账户?')}{' '} {t('注册')} @@ -766,7 +766,7 @@ const LoginForm = () => { href='/user-agreement' target='_blank' rel='noopener noreferrer' - className='text-blue-600 hover:text-blue-800 mx-1' + className='!text-semi-color-primary hover:!opacity-90 mx-1' > {t('用户协议')} @@ -779,7 +779,7 @@ const LoginForm = () => { href='/privacy-policy' target='_blank' rel='noopener noreferrer' - className='text-blue-600 hover:text-blue-800 mx-1' + className='!text-semi-color-primary hover:!opacity-90 mx-1' > {t('隐私政策')} @@ -848,7 +848,7 @@ const LoginForm = () => { {t('没有账户?')}{' '} {t('注册')} diff --git a/web/src/components/auth/PasswordResetConfirm.jsx b/web/src/components/auth/PasswordResetConfirm.jsx index 9bc37b3ce560..da0a2429d6e2 100644 --- a/web/src/components/auth/PasswordResetConfirm.jsx +++ b/web/src/components/auth/PasswordResetConfirm.jsx @@ -202,7 +202,7 @@ const PasswordResetConfirm = () => { {t('返回登录')} diff --git a/web/src/components/auth/PasswordResetForm.jsx b/web/src/components/auth/PasswordResetForm.jsx index 92afc2afa439..9e08640f704a 100644 --- a/web/src/components/auth/PasswordResetForm.jsx +++ b/web/src/components/auth/PasswordResetForm.jsx @@ -164,7 +164,7 @@ const PasswordResetForm = () => { {t('想起来了?')}{' '} {t('登录')} diff --git a/web/src/components/auth/RegisterForm.jsx b/web/src/components/auth/RegisterForm.jsx index 2edc499b1189..9f5887e56c7a 100644 --- a/web/src/components/auth/RegisterForm.jsx +++ b/web/src/components/auth/RegisterForm.jsx @@ -499,7 +499,7 @@ const RegisterForm = () => { {t('已有账户?')}{' '} {t('登录')} @@ -609,7 +609,7 @@ const RegisterForm = () => { href='/user-agreement' target='_blank' rel='noopener noreferrer' - className='text-blue-600 hover:text-blue-800 mx-1' + className='!text-semi-color-primary hover:!opacity-90 mx-1' > {t('用户协议')} @@ -622,7 +622,7 @@ const RegisterForm = () => { href='/privacy-policy' target='_blank' rel='noopener noreferrer' - className='text-blue-600 hover:text-blue-800 mx-1' + className='!text-semi-color-primary hover:!opacity-90 mx-1' > {t('隐私政策')} @@ -680,7 +680,7 @@ const RegisterForm = () => { {t('已有账户?')}{' '} {t('登录')} diff --git a/web/src/components/common/DocumentRenderer/index.jsx b/web/src/components/common/DocumentRenderer/index.jsx index 68e868c51d8a..be6c7f3e7ca5 100644 --- a/web/src/components/common/DocumentRenderer/index.jsx +++ b/web/src/components/common/DocumentRenderer/index.jsx @@ -20,12 +20,9 @@ For commercial licensing, please contact support@quantumnous.com import React, { useEffect, useState } from 'react'; import { API, showError } from '../../../helpers'; import { Empty, Card, Spin, Typography } from '@douyinfe/semi-ui'; -const { Title } = Typography; -import { - IllustrationConstruction, - IllustrationConstructionDark, -} from '@douyinfe/semi-illustrations'; import { useTranslation } from 'react-i18next'; + +const { Title } = Typography; import MarkdownRenderer from '../markdown/MarkdownRenderer'; // 检查是否为 URL @@ -165,12 +162,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
- } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} className='p-8' />
diff --git a/web/src/components/common/markdown/MarkdownRenderer.jsx b/web/src/components/common/markdown/MarkdownRenderer.jsx index 6a71c695f845..3aa47d066a0a 100644 --- a/web/src/components/common/markdown/MarkdownRenderer.jsx +++ b/web/src/components/common/markdown/MarkdownRenderer.jsx @@ -55,7 +55,6 @@ export function Mermaid(props) { }) .catch((e) => { setHasError(true); - console.error('[Mermaid] ', e.message); }); } }, [props.code]); diff --git a/web/src/components/common/ui/ChannelKeyDisplay.jsx b/web/src/components/common/ui/ChannelKeyDisplay.jsx index 79aa3eec7084..83c06a2b371e 100644 --- a/web/src/components/common/ui/ChannelKeyDisplay.jsx +++ b/web/src/components/common/ui/ChannelKeyDisplay.jsx @@ -48,7 +48,6 @@ const parseChannelKeys = (keyData, t) => { } } catch (e) { // 如果解析失败,按普通文本处理 - console.warn('Failed to parse JSON keys:', e); } } diff --git a/web/src/components/common/ui/JSONEditor.jsx b/web/src/components/common/ui/JSONEditor.jsx index 21530db3908e..dd82bb9fba26 100644 --- a/web/src/components/common/ui/JSONEditor.jsx +++ b/web/src/components/common/ui/JSONEditor.jsx @@ -167,7 +167,6 @@ const JSONEditor = ({ } setJsonError(''); } catch (error) { - console.log('JSON解析失败:', error.message); setJsonError(error.message); } }, [value]); diff --git a/web/src/components/dashboard/AnnouncementsPanel.jsx b/web/src/components/dashboard/AnnouncementsPanel.jsx index c62850b3b8fa..f363a9c05947 100644 --- a/web/src/components/dashboard/AnnouncementsPanel.jsx +++ b/web/src/components/dashboard/AnnouncementsPanel.jsx @@ -21,10 +21,6 @@ import React from 'react'; import { Card, Tag, Timeline, Empty } from '@douyinfe/semi-ui'; import { Bell } from 'lucide-react'; import { marked } from 'marked'; -import { - IllustrationConstruction, - IllustrationConstructionDark, -} from '@douyinfe/semi-illustrations'; import ScrollableContainer from '../common/ui/ScrollableContainer'; const AnnouncementsPanel = ({ @@ -109,10 +105,7 @@ const AnnouncementsPanel = ({ ) : (
} - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} title={t('暂无系统公告')} description={t('请联系管理员在系统设置中配置公告信息')} /> diff --git a/web/src/components/dashboard/ApiInfoPanel.jsx b/web/src/components/dashboard/ApiInfoPanel.jsx index 1c3c3dd3d2e6..092fb5f2a52a 100644 --- a/web/src/components/dashboard/ApiInfoPanel.jsx +++ b/web/src/components/dashboard/ApiInfoPanel.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React from 'react'; import { Card, Avatar, Tag, Divider, Empty } from '@douyinfe/semi-ui'; import { Server, Gauge, ExternalLink } from 'lucide-react'; -import { - IllustrationConstruction, - IllustrationConstructionDark, -} from '@douyinfe/semi-illustrations'; import ScrollableContainer from '../common/ui/ScrollableContainer'; const ApiInfoPanel = ({ @@ -59,7 +55,7 @@ const ApiInfoPanel = ({
- + {api.route}
@@ -102,10 +98,7 @@ const ApiInfoPanel = ({ ) : (
} - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} title={t('暂无API信息')} description={t('请联系管理员在系统设置中配置API信息')} /> diff --git a/web/src/components/dashboard/DashboardHeader.jsx b/web/src/components/dashboard/DashboardHeader.jsx index c2867e90c2a0..3afeb79a6a82 100644 --- a/web/src/components/dashboard/DashboardHeader.jsx +++ b/web/src/components/dashboard/DashboardHeader.jsx @@ -29,29 +29,30 @@ const DashboardHeader = ({ loading, t, }) => { - const ICON_BUTTON_CLASS = 'text-white hover:bg-opacity-80 !rounded-full'; + const ICON_BUTTON_CLASS = '!text-white hover:!opacity-90 !rounded-full'; return (

{getGreeting}

diff --git a/web/src/components/dashboard/FaqPanel.jsx b/web/src/components/dashboard/FaqPanel.jsx index a092abbd1089..bb988ff85dc8 100644 --- a/web/src/components/dashboard/FaqPanel.jsx +++ b/web/src/components/dashboard/FaqPanel.jsx @@ -22,10 +22,6 @@ import { Card, Collapse, Empty } from '@douyinfe/semi-ui'; import { HelpCircle } from 'lucide-react'; import { IconPlus, IconMinus } from '@douyinfe/semi-icons'; import { marked } from 'marked'; -import { - IllustrationConstruction, - IllustrationConstructionDark, -} from '@douyinfe/semi-illustrations'; import ScrollableContainer from '../common/ui/ScrollableContainer'; const FaqPanel = ({ @@ -71,10 +67,7 @@ const FaqPanel = ({ ) : (
} - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} title={t('暂无常见问答')} description={t('请联系管理员在系统设置中配置常见问答')} /> diff --git a/web/src/components/dashboard/UptimePanel.jsx b/web/src/components/dashboard/UptimePanel.jsx index ed978d99e900..2ad2d79dd291 100644 --- a/web/src/components/dashboard/UptimePanel.jsx +++ b/web/src/components/dashboard/UptimePanel.jsx @@ -28,10 +28,6 @@ import { Empty, } from '@douyinfe/semi-ui'; import { Gauge, RefreshCw } from 'lucide-react'; -import { - IllustrationConstruction, - IllustrationConstructionDark, -} from '@douyinfe/semi-illustrations'; import ScrollableContainer from '../common/ui/ScrollableContainer'; const UptimePanel = ({ @@ -117,10 +113,7 @@ const UptimePanel = ({ ) : (
} - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} title={t('暂无监控数据')} description={t('请联系管理员在系统设置中配置Uptime')} /> diff --git a/web/src/components/layout/CrabLogo.jsx b/web/src/components/layout/CrabLogo.jsx new file mode 100644 index 000000000000..bc19e31e8e2a --- /dev/null +++ b/web/src/components/layout/CrabLogo.jsx @@ -0,0 +1,49 @@ +/** + * OpenClaw 品牌 logo(与官网 openclawapi.ai CrabLogo 一致) + */ +import React from 'react'; + +export default function CrabLogo({ className = 'h-6 w-6' }) { + return ( + + + + + + + + + + + + + + + ); +} diff --git a/web/src/components/layout/Footer.jsx b/web/src/components/layout/Footer.jsx index c827a581be62..bfc5f1df5072 100644 --- a/web/src/components/layout/Footer.jsx +++ b/web/src/components/layout/Footer.jsx @@ -1,248 +1,109 @@ -/* -Copyright (C) 2025 QuantumNous +/** + * 页脚与官网 openclawapi.ai 一致:品牌为 OpenClaw API,版权与链接统一。 + * Console 页面(有侧边栏)显示精简版页脚;非 Console 页面显示完整页脚。 + * 页脚始终在内容区域内,不与侧边栏重叠。 + */ -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ - -import React, { useEffect, useState, useMemo, useContext } from 'react'; +import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { Typography } from '@douyinfe/semi-ui'; -import { getFooterHTML, getLogo, getSystemName } from '../../helpers'; -import { StatusContext } from '../../context/Status'; +import { Link, useLocation } from 'react-router-dom'; +import CrabLogo from './CrabLogo'; + +const BRAND_NAME = 'OpenClaw API'; const FooterBar = () => { const { t } = useTranslation(); - const [footer, setFooter] = useState(getFooterHTML()); - const systemName = getSystemName(); - const logo = getLogo(); - const [statusState] = useContext(StatusContext); - const isDemoSiteMode = statusState?.status?.demo_site_enabled || false; - - const loadFooter = () => { - let footer_html = localStorage.getItem('footer_html'); - if (footer_html) { - setFooter(footer_html); - } - }; - const currentYear = new Date().getFullYear(); + const location = useLocation(); + const isConsoleRoute = location.pathname.startsWith('/console'); const customFooter = useMemo( () => ( - ), - [logo, systemName, t, currentYear, isDemoSiteMode], + [t, currentYear, isConsoleRoute], ); - useEffect(() => { - loadFooter(); - }, []); - - return ( -
- {footer ? ( -
-
-
- {t('设计与开发由')} - - New API - -
-
- ) : ( - customFooter - )} -
- ); + return customFooter; }; export default FooterBar; diff --git a/web/src/components/layout/NoticeModal.jsx b/web/src/components/layout/NoticeModal.jsx index c8197a58ba7b..1a2fa2815eaf 100644 --- a/web/src/components/layout/NoticeModal.jsx +++ b/web/src/components/layout/NoticeModal.jsx @@ -29,10 +29,6 @@ import { import { useTranslation } from 'react-i18next'; import { API, showError, getRelativeTime } from '../../helpers'; import { marked } from 'marked'; -import { - IllustrationNoContent, - IllustrationNoContentDark, -} from '@douyinfe/semi-illustrations'; import { StatusContext } from '../../context/Status'; import { Bell, Megaphone } from 'lucide-react'; @@ -129,12 +125,7 @@ const NoticeModal = ({ return (
- } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('暂无公告')} />
@@ -154,12 +145,7 @@ const NoticeModal = ({ return (
- } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('暂无系统公告')} />
diff --git a/web/src/components/layout/PageLayout.jsx b/web/src/components/layout/PageLayout.jsx index 49c47e690341..d13f3ddddc53 100644 --- a/web/src/components/layout/PageLayout.jsx +++ b/web/src/components/layout/PageLayout.jsx @@ -23,7 +23,7 @@ import SiderBar from './SiderBar'; import App from '../../App'; import FooterBar from './Footer'; import { ToastContainer } from 'react-toastify'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import { useIsMobile } from '../../hooks/common/useIsMobile'; import { useSidebarCollapsed } from '../../hooks/common/useSidebarCollapsed'; import { useTranslation } from 'react-i18next'; @@ -33,6 +33,8 @@ import { getSystemName, showError, setStatusData, + getCookie, + setCookie, } from '../../helpers'; import { UserContext } from '../../context/User'; import { StatusContext } from '../../context/Status'; @@ -45,22 +47,11 @@ const PageLayout = () => { const isMobile = useIsMobile(); const [collapsed, , setCollapsed] = useSidebarCollapsed(); const [drawerOpen, setDrawerOpen] = useState(false); + const contentWrapperRef = useRef(null); const { i18n } = useTranslation(); const location = useLocation(); - const cardProPages = [ - '/console/channel', - '/console/log', - '/console/redemption', - '/console/user', - '/console/token', - '/console/midjourney', - '/console/task', - '/console/models', - '/pricing', - ]; - - const shouldHideFooter = cardProPages.includes(location.pathname); + // 已移除 cardProPages/shouldHideFooter - 所有页面统一显示页脚 const shouldInnerPadding = location.pathname.includes('/console') && @@ -113,9 +104,16 @@ const PageLayout = () => { linkElement.href = logo; } } - const savedLang = localStorage.getItem('i18nextLng'); - if (savedLang) { + // 与前端同步:优先读 cookie(domain=.openclawapi.ai),再 localStorage + const cookieLocale = getCookie('oc_locale'); + const savedLang = cookieLocale || localStorage.getItem('locale'); + if (savedLang && (savedLang === 'en' || savedLang === 'zh')) { i18n.changeLanguage(savedLang); + try { + localStorage.setItem('locale', savedLang); + } catch (e) { + // ignore + } } }, [i18n]); @@ -149,27 +147,41 @@ const PageLayout = () => { overflow: isMobile ? 'visible' : 'auto', display: 'flex', flexDirection: 'column', + marginTop: 64, + flex: 1, }} > {showSider && ( - - { - if (isMobile) setDrawerOpen(false); + <> + {/* Mobile backdrop overlay */} + {isMobile && drawerOpen && ( +
setDrawerOpen(false)} + aria-hidden + /> + )} + - + > + { + if (isMobile) setDrawerOpen(false); + }} + /> + + )} { flex: '1 1 auto', display: 'flex', flexDirection: 'column', + minHeight: 'calc(100vh - 64px)', }} > - - - {!shouldHideFooter && ( - - - - )} + +
+ + diff --git a/web/src/components/layout/SiderBar.jsx b/web/src/components/layout/SiderBar.jsx index 34971c397426..10413364f76c 100644 --- a/web/src/components/layout/SiderBar.jsx +++ b/web/src/components/layout/SiderBar.jsx @@ -31,7 +31,6 @@ import SkeletonWrapper from './components/SkeletonWrapper'; import { Nav, Divider, Button } from '@douyinfe/semi-ui'; const routerMap = { - home: '/', channel: '/console/channel', token: '/console/token', redemption: '/console/redemption', @@ -41,7 +40,6 @@ const routerMap = { log: '/console/log', midjourney: '/console/midjourney', setting: '/console/setting', - about: '/about', detail: '/console', pricing: '/pricing', task: '/console/task', @@ -62,7 +60,7 @@ const SiderBar = ({ onNavigate = () => {} }) => { const showSkeleton = useMinimumLoadingTime(sidebarLoading, 200); - const [selectedKeys, setSelectedKeys] = useState(['home']); + const [selectedKeys, setSelectedKeys] = useState(['detail']); const [chatItems, setChatItems] = useState([]); const [openedKeys, setOpenedKeys] = useState([]); const location = useLocation(); diff --git a/web/src/components/layout/headerbar/ActionButtons.jsx b/web/src/components/layout/headerbar/ActionButtons.jsx index 545b5227b224..a4123bd7c891 100644 --- a/web/src/components/layout/headerbar/ActionButtons.jsx +++ b/web/src/components/layout/headerbar/ActionButtons.jsx @@ -1,21 +1,4 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ +/** OpenClaw API - Header action buttons (theme, language, user). */ import React from 'react'; import NewYearButton from './NewYearButton'; diff --git a/web/src/components/layout/headerbar/HeaderLogo.jsx b/web/src/components/layout/headerbar/HeaderLogo.jsx index 73be0516b2f0..a387490e9d9e 100644 --- a/web/src/components/layout/headerbar/HeaderLogo.jsx +++ b/web/src/components/layout/headerbar/HeaderLogo.jsx @@ -18,10 +18,28 @@ For commercial licensing, please contact support@quantumnous.com */ import React from 'react'; -import { Link } from 'react-router-dom'; import { Typography, Tag } from '@douyinfe/semi-ui'; import SkeletonWrapper from '../components/SkeletonWrapper'; +const BRAND_DISPLAY_NAME = 'OpenClaw API'; + +const renderBrandName = (name) => { + if (!name || typeof name !== 'string') return name; + const i = name.indexOf('Claw'); + if (i === -1) { + return {name}; + } + const before = name.slice(0, i); + const after = name.slice(i + 4); + return ( + + {before} + Claw + {after} + + ); +}; + const HeaderLogo = ({ isMobile, isConsoleRoute, @@ -37,8 +55,9 @@ const HeaderLogo = ({ return null; } + const frontendHome = 'https://openclawapi.ai'; return ( - +
- {systemName} + {renderBrandName(systemName || BRAND_DISPLAY_NAME)} {(isSelfUseMode || isDemoSiteMode) && !isLoading && ( @@ -74,7 +93,7 @@ const HeaderLogo = ({ )}
- + ); }; diff --git a/web/src/components/layout/headerbar/LanguageSelector.jsx b/web/src/components/layout/headerbar/LanguageSelector.jsx index fdcc0d6ad6e0..b4e2074de67e 100644 --- a/web/src/components/layout/headerbar/LanguageSelector.jsx +++ b/web/src/components/layout/headerbar/LanguageSelector.jsx @@ -1,88 +1,26 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ +/** + * 与官网一致:点击直接切换语言(无下拉),显示目标语言名称。 + */ import React from 'react'; -import { Button, Dropdown } from '@douyinfe/semi-ui'; -import { Languages } from 'lucide-react'; -import { CN, GB, FR, RU, JP, VN } from 'country-flag-icons/react/3x2'; +import { Button } from '@douyinfe/semi-ui'; +import { Globe } from 'lucide-react'; + +const localeNames = { en: 'English', zh: '中文' }; const LanguageSelector = ({ currentLang, onLanguageChange, t }) => { + const nextLang = currentLang === 'zh' ? 'en' : 'zh'; return ( - - {/* Language sorting: Order by English name (Chinese, English, French, Japanese, Russian) */} - onLanguageChange('zh')} - className={`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} - > - - 中文 - - onLanguageChange('en')} - className={`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'en' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} - > - - English - - onLanguageChange('fr')} - className={`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'fr' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} - > - - Français - - onLanguageChange('ja')} - className={`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'ja' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} - > - {/* Japanese flag using emoji as country-flag-icons/react/3x2 does not export JP */} - - 日本語 - - onLanguageChange('ru')} - className={`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'ru' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} - > - - Русский - - onLanguageChange('vi')} - className={`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'vi' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} - > - - Tiếng Việt - - - } + ); }; diff --git a/web/src/components/layout/headerbar/ThemeToggle.jsx b/web/src/components/layout/headerbar/ThemeToggle.jsx index 45e7918d4b1b..718f01fa73b8 100644 --- a/web/src/components/layout/headerbar/ThemeToggle.jsx +++ b/web/src/components/layout/headerbar/ThemeToggle.jsx @@ -1,108 +1,23 @@ -/* -Copyright (C) 2025 QuantumNous +/** + * 与官网 openclawapi.ai 一致:单一按钮切换深色/浅色,无“跟随系统”选项。 + */ -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ - -import React, { useMemo } from 'react'; -import { Button, Dropdown } from '@douyinfe/semi-ui'; -import { Sun, Moon, Monitor } from 'lucide-react'; -import { useActualTheme } from '../../../context/Theme'; +import React from 'react'; +import { Button } from '@douyinfe/semi-ui'; +import { Sun, Moon } from 'lucide-react'; const ThemeToggle = ({ theme, onThemeToggle, t }) => { - const actualTheme = useActualTheme(); - - const themeOptions = useMemo( - () => [ - { - key: 'light', - icon: , - buttonIcon: , - label: t('浅色模式'), - description: t('始终使用浅色主题'), - }, - { - key: 'dark', - icon: , - buttonIcon: , - label: t('深色模式'), - description: t('始终使用深色主题'), - }, - { - key: 'auto', - icon: , - buttonIcon: , - label: t('自动模式'), - description: t('跟随系统主题设置'), - }, - ], - [t], - ); - - const getItemClassName = (isSelected) => - isSelected - ? '!bg-semi-color-primary-light-default !font-semibold' - : 'hover:!bg-semi-color-fill-1'; - - const currentButtonIcon = useMemo(() => { - const currentOption = themeOptions.find((option) => option.key === theme); - return currentOption?.buttonIcon || themeOptions[2].buttonIcon; - }, [theme, themeOptions]); + const isDark = theme === 'dark'; return ( - - {themeOptions.map((option) => ( - onThemeToggle(option.key)} - className={getItemClassName(theme === option.key)} - > -
- {option.label} - - {option.description} - -
-
- ))} - - {theme === 'auto' && ( - <> - -
- {t('当前跟随系统')}: - {actualTheme === 'dark' ? t('深色') : t('浅色')} -
- - )} - - } - > -
@@ -148,13 +142,13 @@ const UserArea = ({ 'flex items-center justify-center !py-[10px] !px-1.5'; const loginButtonSpecificStyling = - '!bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 transition-colors'; + '!bg-semi-color-fill-0 hover:!bg-semi-color-fill-1 transition-colors'; let loginButtonClasses = `${commonSizingAndLayoutClass} ${loginButtonSpecificStyling}`; let registerButtonClasses = `${commonSizingAndLayoutClass}`; const loginButtonTextSpanClass = - '!text-xs !text-semi-color-text-1 dark:!text-gray-300 !p-1.5'; + '!text-xs !text-semi-color-text-1 !p-1.5'; const registerButtonTextSpanClass = '!text-xs !text-white !p-1.5'; if (showRegisterButton) { diff --git a/web/src/components/layout/headerbar/index.jsx b/web/src/components/layout/headerbar/index.jsx index 81b51d7fe486..4cd0798d17a5 100644 --- a/web/src/components/layout/headerbar/index.jsx +++ b/web/src/components/layout/headerbar/index.jsx @@ -1,21 +1,4 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ +/** OpenClaw API - Console header (theme/language aligned with openclawapi.ai). */ import React from 'react'; import { useHeaderBar } from '../../../hooks/common/useHeaderBar'; @@ -65,7 +48,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => { const { mainNavLinks } = useNavigation(t, docsLink, headerNavModules); return ( -
+
{ unreadKeys={getUnreadKeys()} /> -
+
{ } } catch (err) { Toast.error(t('复制失败')); - console.error('Copy failed:', err); } }, [content, t]); diff --git a/web/src/components/playground/CustomInputRender.jsx b/web/src/components/playground/CustomInputRender.jsx index c995773a7f8e..e5f0294f998e 100644 --- a/web/src/components/playground/CustomInputRender.jsx +++ b/web/src/components/playground/CustomInputRender.jsx @@ -70,7 +70,6 @@ const CustomInputRender = (props) => { } }; reader.onerror = () => { - console.error('Failed to read image file:', reader.error); Toast.error({ content: t('粘贴图片失败'), duration: 2, @@ -78,7 +77,6 @@ const CustomInputRender = (props) => { }; reader.readAsDataURL(file); } catch (error) { - console.error('Failed to paste image:', error); Toast.error({ content: t('粘贴图片失败'), duration: 2, diff --git a/web/src/components/playground/SSEViewer.jsx b/web/src/components/playground/SSEViewer.jsx index 17768d8be5e0..856223959a79 100644 --- a/web/src/components/playground/SSEViewer.jsx +++ b/web/src/components/playground/SSEViewer.jsx @@ -112,7 +112,6 @@ const SSEViewer = ({ sseData }) => { setTimeout(() => setCopied(false), 2000); } catch (err) { Toast.error(t('复制失败')); - console.error('Copy failed:', err); } }, [parsedSSEData, t]); diff --git a/web/src/components/playground/configStorage.js b/web/src/components/playground/configStorage.js index 86674eb587b8..ace3c1881a06 100644 --- a/web/src/components/playground/configStorage.js +++ b/web/src/components/playground/configStorage.js @@ -36,7 +36,6 @@ export const saveConfig = (config) => { }; localStorage.setItem(STORAGE_KEYS.CONFIG, JSON.stringify(configToSave)); } catch (error) { - console.error('保存配置失败:', error); } }; @@ -52,7 +51,6 @@ export const saveMessages = (messages) => { }; localStorage.setItem(STORAGE_KEYS.MESSAGES, JSON.stringify(messagesToSave)); } catch (error) { - console.error('保存消息失败:', error); } }; @@ -86,7 +84,6 @@ export const loadConfig = () => { return mergedConfig; } } catch (error) { - console.error('加载配置失败:', error); } return DEFAULT_CONFIG; @@ -104,7 +101,6 @@ export const loadMessages = () => { return parsedMessages.messages || null; } } catch (error) { - console.error('加载消息失败:', error); } return null; @@ -118,7 +114,6 @@ export const clearConfig = () => { localStorage.removeItem(STORAGE_KEYS.CONFIG); localStorage.removeItem(STORAGE_KEYS.MESSAGES); // 同时清除消息 } catch (error) { - console.error('清除配置失败:', error); } }; @@ -129,7 +124,6 @@ export const clearMessages = () => { try { localStorage.removeItem(STORAGE_KEYS.MESSAGES); } catch (error) { - console.error('清除消息失败:', error); } }; @@ -141,7 +135,6 @@ export const hasStoredConfig = () => { try { return localStorage.getItem(STORAGE_KEYS.CONFIG) !== null; } catch (error) { - console.error('检查配置失败:', error); return false; } }; @@ -158,7 +151,6 @@ export const getConfigTimestamp = () => { return parsedConfig.timestamp || null; } } catch (error) { - console.error('获取配置时间戳失败:', error); } return null; }; @@ -187,7 +179,6 @@ export const exportConfig = (config, messages = null) => { URL.revokeObjectURL(link.href); } catch (error) { - console.error('导出配置失败:', error); } }; diff --git a/web/src/components/settings/DashboardSetting.jsx b/web/src/components/settings/DashboardSetting.jsx index 7bf4249437ab..5c2d73bb9990 100644 --- a/web/src/components/settings/DashboardSetting.jsx +++ b/web/src/components/settings/DashboardSetting.jsx @@ -78,7 +78,6 @@ const DashboardSetting = () => { await getOptions(); } catch (error) { showError('刷新失败'); - console.error(error); } finally { setLoading(false); } @@ -114,7 +113,6 @@ const DashboardSetting = () => { await onRefresh(); setShowMigrateModal(false); } catch (err) { - console.error(err); showError('迁移失败: ' + (err.message || '未知错误')); } finally { setLoading(false); diff --git a/web/src/components/settings/ModelDeploymentSetting.jsx b/web/src/components/settings/ModelDeploymentSetting.jsx index c872f631d00f..a0be0eaddc82 100644 --- a/web/src/components/settings/ModelDeploymentSetting.jsx +++ b/web/src/components/settings/ModelDeploymentSetting.jsx @@ -61,7 +61,6 @@ const ModelDeploymentSetting = () => { await getOptions(); } catch (error) { showError('刷新失败'); - console.error(error); } finally { setLoading(false); } diff --git a/web/src/components/settings/ModelSetting.jsx b/web/src/components/settings/ModelSetting.jsx index fb0b844a037f..4bcf9ad6db69 100644 --- a/web/src/components/settings/ModelSetting.jsx +++ b/web/src/components/settings/ModelSetting.jsx @@ -72,7 +72,6 @@ const ModelSetting = () => { item.value = JSON.stringify(JSON.parse(item.value), null, 2); } catch (e) { // Keep raw value so user can fix it, and avoid crashing the page. - console.error(`Invalid JSON for option ${item.key}:`, e); } } } @@ -96,7 +95,6 @@ const ModelSetting = () => { // showSuccess('刷新成功'); } catch (error) { showError('刷新失败'); - console.error(error); } finally { setLoading(false); } diff --git a/web/src/components/settings/OtherSetting.jsx b/web/src/components/settings/OtherSetting.jsx index f8e0b53756a1..85df8d2d2767 100644 --- a/web/src/components/settings/OtherSetting.jsx +++ b/web/src/components/settings/OtherSetting.jsx @@ -97,7 +97,6 @@ const OtherSetting = () => { await updateOption('Notice', inputs.Notice); showSuccess(t('公告已更新')); } catch (error) { - console.error(t('公告更新失败'), error); showError(t('公告更新失败')); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false })); @@ -116,7 +115,6 @@ const OtherSetting = () => { ); showSuccess(t('用户协议已更新')); } catch (error) { - console.error(t('用户协议更新失败'), error); showError(t('用户协议更新失败')); } finally { setLoadingInput((loadingInput) => ({ @@ -138,7 +136,6 @@ const OtherSetting = () => { ); showSuccess(t('隐私政策已更新')); } catch (error) { - console.error(t('隐私政策更新失败'), error); showError(t('隐私政策更新失败')); } finally { setLoadingInput((loadingInput) => ({ @@ -159,7 +156,6 @@ const OtherSetting = () => { await updateOption('SystemName', inputs.SystemName); showSuccess(t('系统名称已更新')); } catch (error) { - console.error(t('系统名称更新失败'), error); showError(t('系统名称更新失败')); } finally { setLoadingInput((loadingInput) => ({ @@ -176,7 +172,6 @@ const OtherSetting = () => { await updateOption('Logo', inputs.Logo); showSuccess('Logo 已更新'); } catch (error) { - console.error('Logo 更新失败', error); showError('Logo 更新失败'); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: false })); @@ -192,7 +187,6 @@ const OtherSetting = () => { await updateOption(key, inputs[key]); showSuccess('首页内容已更新'); } catch (error) { - console.error('首页内容更新失败', error); showError('首页内容更新失败'); } finally { setLoadingInput((loadingInput) => ({ @@ -208,7 +202,6 @@ const OtherSetting = () => { await updateOption('About', inputs.About); showSuccess('关于内容已更新'); } catch (error) { - console.error('关于内容更新失败', error); showError('关于内容更新失败'); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, About: false })); @@ -221,7 +214,6 @@ const OtherSetting = () => { await updateOption('Footer', inputs.Footer); showSuccess('页脚内容已更新'); } catch (error) { - console.error('页脚内容更新失败', error); showError('页脚内容更新失败'); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: false })); @@ -269,7 +261,6 @@ const OtherSetting = () => { setShowUpdateModal(true); } } catch (error) { - console.error('Failed to check for updates:', error); showError('检查更新失败,请稍后再试'); } finally { setLoadingInput((loadingInput) => ({ @@ -444,22 +435,7 @@ const OtherSetting = () => { - - + {/* 首页内容设置已移除 */} { 2, ); } catch (error) { - console.error('解析TopupGroupRatio出错:', error); newInputs[item.key] = item.value; } break; @@ -78,7 +77,6 @@ const PaymentSetting = () => { 2, ); } catch (error) { - console.error('解析AmountOptions出错:', error); newInputs['AmountOptions'] = item.value; } break; @@ -90,7 +88,6 @@ const PaymentSetting = () => { 2, ); } catch (error) { - console.error('解析AmountDiscount出错:', error); newInputs['AmountDiscount'] = item.value; } break; diff --git a/web/src/components/settings/PersonalSetting.jsx b/web/src/components/settings/PersonalSetting.jsx index 8ee6415ac5f4..9f7ef4106256 100644 --- a/web/src/components/settings/PersonalSetting.jsx +++ b/web/src/components/settings/PersonalSetting.jsx @@ -39,7 +39,7 @@ import { useTranslation } from 'react-i18next'; import UserInfoHeader from './personal/components/UserInfoHeader'; import AccountManagement from './personal/cards/AccountManagement'; import NotificationSettings from './personal/cards/NotificationSettings'; -import PreferencesSettings from './personal/cards/PreferencesSettings'; +// 语言偏好已移除,与顶栏语言切换一致 import CheckinCalendar from './personal/cards/CheckinCalendar'; import EmailBindModal from './personal/modals/EmailBindModal'; import WeChatBindModal from './personal/modals/WeChatBindModal'; @@ -484,8 +484,6 @@ const PersonalSetting = () => { onPasskeyDelete={handleRemovePasskey} /> - {/* 偏好设置(语言等) */} -
{/* 右侧:其他设置 */} diff --git a/web/src/components/settings/SystemSetting.jsx b/web/src/components/settings/SystemSetting.jsx index 8334c01db04b..829ed41719fc 100644 --- a/web/src/components/settings/SystemSetting.jsx +++ b/web/src/components/settings/SystemSetting.jsx @@ -520,7 +520,6 @@ const SystemSetting = () => { inputs['oidc.user_info_endpoint'] = res.data['userinfo_endpoint']; showSuccess(t('获取 OIDC 配置成功!')); } catch (err) { - console.error(err); showError( t('获取 OIDC 配置失败,请检查网络状况和 Well-Known URL 是否正确'), ); @@ -1209,7 +1208,7 @@ const SystemSetting = () => { label={t('允许的 Origins')} placeholder={t('填写带https的域名,逗号分隔')} extraText={t( - '为空则默认使用服务器地址,多个 Origin 用逗号分隔,例如 https://newapi.pro,https://newapi.com ,注意不能携带[],需使用https', + '为空则默认使用服务器地址,多个 Origin 用逗号分隔,例如 https://openclawapi.ai,https://api.openclawapi.ai ,注意不能携带[],需使用https', )} /> diff --git a/web/src/components/settings/personal/cards/CheckinCalendar.jsx b/web/src/components/settings/personal/cards/CheckinCalendar.jsx index 2298ae94d9de..c254cc004e7f 100644 --- a/web/src/components/settings/personal/cards/CheckinCalendar.jsx +++ b/web/src/components/settings/personal/cards/CheckinCalendar.jsx @@ -297,13 +297,13 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
{t('累计签到')}
-
+
{renderQuota(monthlyQuota, 6)}
{t('本月获得')}
-
+
{renderQuota(checkinData.stats?.total_quota || 0, 6)}
{t('累计获得')}
diff --git a/web/src/components/settings/personal/cards/NotificationSettings.jsx b/web/src/components/settings/personal/cards/NotificationSettings.jsx index 964a730e41d1..2c8656028e04 100644 --- a/web/src/components/settings/personal/cards/NotificationSettings.jsx +++ b/web/src/components/settings/personal/cards/NotificationSettings.jsx @@ -207,7 +207,6 @@ const NotificationSettings = ({ setSidebarModulesUser(userConf); } } catch (error) { - console.error('加载边栏配置失败:', error); } }; @@ -335,7 +334,6 @@ const NotificationSettings = ({ saveNotificationSettings(); }) .catch((errors) => { - console.log('表单验证失败:', errors); Toast.error(t('请检查表单填写是否正确')); }); } else { diff --git a/web/src/components/settings/personal/cards/PreferencesSettings.jsx b/web/src/components/settings/personal/cards/PreferencesSettings.jsx index 6417061f6539..4dd29089db68 100644 --- a/web/src/components/settings/personal/cards/PreferencesSettings.jsx +++ b/web/src/components/settings/personal/cards/PreferencesSettings.jsx @@ -21,7 +21,7 @@ import React, { useState, useEffect, useContext } from 'react'; import { Card, Select, Typography, Avatar } from '@douyinfe/semi-ui'; import { Languages } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { API, showSuccess, showError } from '../../../../helpers'; +import { API, showSuccess, showError, setCookie } from '../../../../helpers'; import { UserContext } from '../../../../context/User'; // Language options with native names and flags @@ -68,6 +68,12 @@ const PreferencesSettings = ({ t }) => { // Update language immediately for responsive UX setCurrentLanguage(lang); i18n.changeLanguage(lang); + try { + localStorage.setItem('locale', lang); + setCookie('oc_locale', lang); + } catch (e) { + // ignore + } // Save to backend const res = await API.put('/api/user/self', { diff --git a/web/src/components/settings/personal/components/UserInfoHeader.jsx b/web/src/components/settings/personal/components/UserInfoHeader.jsx index 3209a17ee84a..4cd2af208edc 100644 --- a/web/src/components/settings/personal/components/UserInfoHeader.jsx +++ b/web/src/components/settings/personal/components/UserInfoHeader.jsx @@ -58,11 +58,8 @@ const UserInfoHeader = ({ t, userState }) => {
{/* 用户信息内容 */} @@ -75,7 +72,7 @@ const UserInfoHeader = ({ t, userState }) => {
{getUsername()}
diff --git a/web/src/components/setup/SetupWizard.jsx b/web/src/components/setup/SetupWizard.jsx index 0d500fdd8fcb..5b66bfde2651 100644 --- a/web/src/components/setup/SetupWizard.jsx +++ b/web/src/components/setup/SetupWizard.jsx @@ -96,7 +96,6 @@ const SetupWizard = () => { showError(t('获取初始化状态失败')); } } catch (error) { - console.error('Failed to fetch setup status:', error); showError(t('获取初始化状态失败')); } }; @@ -165,7 +164,6 @@ const SetupWizard = () => { const onSubmit = () => { if (!formRef.current) { - console.error('Form reference is null'); showError(t('表单引用错误,请刷新页面重试')); return; } @@ -217,7 +215,6 @@ const SetupWizard = () => { } }) .catch((error) => { - console.error('API error:', error); showError(t('系统初始化失败,请重试')); setLoading(false); }) diff --git a/web/src/components/table/channels/ChannelsTable.jsx b/web/src/components/table/channels/ChannelsTable.jsx index abad235eb27c..b1479e3fdbd0 100644 --- a/web/src/components/table/channels/ChannelsTable.jsx +++ b/web/src/components/table/channels/ChannelsTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo } from 'react'; import { Empty } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getChannelsColumns } from './ChannelsColumnDefs'; const ChannelsTable = (channelsData) => { @@ -153,10 +149,7 @@ const ChannelsTable = (channelsData) => { } empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/channels/modals/CodexOAuthModal.jsx b/web/src/components/table/channels/modals/CodexOAuthModal.jsx index 7f3f349b20a4..9ba408cda740 100644 --- a/web/src/components/table/channels/modals/CodexOAuthModal.jsx +++ b/web/src/components/table/channels/modals/CodexOAuthModal.jsx @@ -46,12 +46,10 @@ const CodexOAuthModal = ({ visible, onCancel, onSuccess }) => { { skipErrorHandler: true }, ); if (!res?.data?.success) { - console.error('Codex OAuth start failed:', res?.data?.message); throw new Error(t('启动授权失败')); } const url = res?.data?.data?.authorize_url || ''; if (!url) { - console.error( 'Codex OAuth start response missing authorize_url:', res?.data, ); @@ -81,13 +79,11 @@ const CodexOAuthModal = ({ visible, onCancel, onSuccess }) => { { skipErrorHandler: true }, ); if (!res?.data?.success) { - console.error('Codex OAuth complete failed:', res?.data?.message); throw new Error(t('授权失败')); } const key = res?.data?.data?.key || ''; if (!key) { - console.error('Codex OAuth complete response missing key:', res?.data); throw new Error(t('响应缺少凭据')); } diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index 6e85ca982cf7..2c68964f237a 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -294,7 +294,6 @@ const EditChannelModal = (props) => { } = useSecureVerification({ onSuccess: (result) => { // 验证成功后显示密钥 - console.log('Verification success, result:', result); if (result && result.success && result.data?.key) { showSuccess(t('密钥获取成功')); setKeyDisplayState({ @@ -426,7 +425,6 @@ const EditChannelModal = (props) => { try { settings = JSON.parse(inputs.settings); } catch (error) { - console.error('解析设置失败:', error); } } settings[key] = value; @@ -600,7 +598,6 @@ const EditChannelModal = (props) => { data.system_prompt_override = parsedSettings.system_prompt_override || false; } catch (error) { - console.error('解析渠道设置失败:', error); data.force_format = false; data.thinking_to_content = false; data.proxy = ''; @@ -636,7 +633,6 @@ const EditChannelModal = (props) => { parsedSettings.allow_safety_identifier || false; data.claude_beta_query = parsedSettings.claude_beta_query || false; } catch (error) { - console.error('解析其他设置失败:', error); data.azure_responses_version = ''; data.region = ''; data.vertex_key_type = 'json'; @@ -760,7 +756,6 @@ const EditChannelModal = (props) => { err = true; } } catch (error) { - console.error('Error fetching models:', error); err = true; } } @@ -892,7 +887,6 @@ const EditChannelModal = (props) => { }); } } catch (error) { - console.error('Failed to view channel key:', error); showError(error.message || t('获取密钥失败')); } }; @@ -1367,7 +1361,6 @@ const EditChannelModal = (props) => { try { settings = JSON.parse(localInputs.settings); } catch (error) { - console.error('解析settings失败:', error); } } diff --git a/web/src/components/table/channels/modals/ModelSelectModal.jsx b/web/src/components/table/channels/modals/ModelSelectModal.jsx index b38580b66f5f..213f47d3ca6a 100644 --- a/web/src/components/table/channels/modals/ModelSelectModal.jsx +++ b/web/src/components/table/channels/modals/ModelSelectModal.jsx @@ -30,10 +30,6 @@ import { Collapse, Tooltip, } from '@douyinfe/semi-ui'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { IconSearch, IconInfoCircle } from '@douyinfe/semi-icons'; import { useTranslation } from 'react-i18next'; import { getModelCategories } from '../../../../helpers/render'; @@ -347,12 +343,7 @@ const ModelSelectModal = ({
{filteredModels.length === 0 ? ( - } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('暂无匹配模型')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/channels/modals/MultiKeyManageModal.jsx b/web/src/components/table/channels/modals/MultiKeyManageModal.jsx index 5e496131f484..2ef931d0cf22 100644 --- a/web/src/components/table/channels/modals/MultiKeyManageModal.jsx +++ b/web/src/components/table/channels/modals/MultiKeyManageModal.jsx @@ -37,10 +37,6 @@ import { Progress, Card, } from '@douyinfe/semi-ui'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { API, showError, @@ -110,7 +106,6 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => { showError(res.data.message); } } catch (error) { - console.error(error); showError(t('获取密钥状态失败')); } finally { setLoading(false); @@ -715,16 +710,7 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => { scroll={{ x: 'max-content' }} empty={ - } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} title={t('暂无密钥数据')} description={t('请检查渠道配置或刷新重试')} style={{ padding: 30 }} diff --git a/web/src/components/table/channels/modals/OllamaModelModal.jsx b/web/src/components/table/channels/modals/OllamaModelModal.jsx index 684d2eb46538..7cb967676c41 100644 --- a/web/src/components/table/channels/modals/OllamaModelModal.jsx +++ b/web/src/components/table/channels/modals/OllamaModelModal.jsx @@ -410,7 +410,6 @@ const OllamaModelModal = ({ return; } } catch (e) { - console.error('Failed to parse SSE data:', e); } } } @@ -426,7 +425,6 @@ const OllamaModelModal = ({ setEventSource(null); return; } - console.error('Stream processing error:', error); showError(t('数据传输中断')); setPullProgress(null); setPullLoading(false); diff --git a/web/src/components/table/channels/modals/SingleModelSelectModal.jsx b/web/src/components/table/channels/modals/SingleModelSelectModal.jsx index fdeb29152cae..f3d36122136f 100644 --- a/web/src/components/table/channels/modals/SingleModelSelectModal.jsx +++ b/web/src/components/table/channels/modals/SingleModelSelectModal.jsx @@ -28,10 +28,6 @@ import { Radio, Typography, } from '@douyinfe/semi-ui'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { IconSearch } from '@douyinfe/semi-icons'; import { getModelCategories } from '../../../../helpers/render'; @@ -140,10 +136,7 @@ const SingleModelSelectModal = ({
{filteredModels.length === 0 ? ( } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('暂无匹配模型')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/mj-logs/MjLogsTable.jsx b/web/src/components/table/mj-logs/MjLogsTable.jsx index 490a18037286..7ae0ad17d08e 100644 --- a/web/src/components/table/mj-logs/MjLogsTable.jsx +++ b/web/src/components/table/mj-logs/MjLogsTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo } from 'react'; import { Empty } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getMjLogsColumns } from './MjLogsColumnDefs'; const MjLogsTable = (mjLogsData) => { @@ -83,10 +79,7 @@ const MjLogsTable = (mjLogsData) => { size='middle' empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/model-deployments/DeploymentsTable.jsx b/web/src/components/table/model-deployments/DeploymentsTable.jsx index 7a7eb981e13e..c84f792aadd3 100644 --- a/web/src/components/table/model-deployments/DeploymentsTable.jsx +++ b/web/src/components/table/model-deployments/DeploymentsTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo, useState } from 'react'; import { Empty } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getDeploymentsColumns } from './DeploymentsColumnDefs'; // Import all the new modals @@ -186,10 +182,7 @@ const DeploymentsTable = (deploymentsData) => { rowSelection={batchOperationsEnabled ? rowSelection : undefined} empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/model-deployments/modals/CreateDeploymentModal.jsx b/web/src/components/table/model-deployments/modals/CreateDeploymentModal.jsx index 35887b15b83e..cb3ed9cc41f0 100644 --- a/web/src/components/table/model-deployments/modals/CreateDeploymentModal.jsx +++ b/web/src/components/table/model-deployments/modals/CreateDeploymentModal.jsx @@ -493,7 +493,6 @@ const CreateDeploymentModal = ({ visible, onCancel, onSuccess, t }) => { } } catch (error) { if (replicaRequestIdRef.current === requestId) { - console.error('Load available replicas error:', error); setLocationTotalAvailable(null); } } finally { @@ -529,7 +528,6 @@ const CreateDeploymentModal = ({ visible, onCancel, onSuccess, t }) => { setPriceEstimation(null); } } catch (error) { - console.error('Price calculation error:', error); setPriceEstimation(null); } finally { setLoadingPrice(false); diff --git a/web/src/components/table/model-deployments/modals/EditDeploymentModal.jsx b/web/src/components/table/model-deployments/modals/EditDeploymentModal.jsx index 4d95b91ac3f2..95b5ffe95210 100644 --- a/web/src/components/table/model-deployments/modals/EditDeploymentModal.jsx +++ b/web/src/components/table/model-deployments/modals/EditDeploymentModal.jsx @@ -97,7 +97,6 @@ const EditDeploymentModal = ({ setModels(modelOptions); } } catch (error) { - console.error('Failed to load models:', error); showError(t('加载模型列表失败')); } setLoadingModels(false); @@ -128,7 +127,6 @@ const EditDeploymentModal = ({ showError(res.data.message || t('更新失败')); } } catch (error) { - console.error('Submit error:', error); showError(t('更新失败,请检查输入信息')); } setLoading(false); diff --git a/web/src/components/table/model-pricing/layout/header/PricingVendorIntro.jsx b/web/src/components/table/model-pricing/layout/header/PricingVendorIntro.jsx index 60226264470b..fbd6a5de1a3e 100644 --- a/web/src/components/table/model-pricing/layout/header/PricingVendorIntro.jsx +++ b/web/src/components/table/model-pricing/layout/header/PricingVendorIntro.jsx @@ -260,12 +260,9 @@ const PricingVendorIntro = memo( ); const createCoverStyle = useCallback( - (primaryColor) => ({ - '--palette-primary-darkerChannel': primaryColor, - backgroundImage: `linear-gradient(0deg, rgba(var(--palette-primary-darkerChannel) / 80%), rgba(var(--palette-primary-darkerChannel) / 80%)), url('/cover-4.webp')`, - backgroundSize: 'cover', - backgroundPosition: 'center', - backgroundRepeat: 'no-repeat', + () => ({ + backgroundColor: 'var(--semi-color-bg-1)', + borderBottom: '1px solid var(--semi-color-border)', }), [], ); @@ -324,7 +321,7 @@ const PricingVendorIntro = memo( cover={
diff --git a/web/src/components/table/model-pricing/layout/header/PricingVendorIntroSkeleton.jsx b/web/src/components/table/model-pricing/layout/header/PricingVendorIntroSkeleton.jsx index 6905c58847ef..68ee6ea81cd5 100644 --- a/web/src/components/table/model-pricing/layout/header/PricingVendorIntroSkeleton.jsx +++ b/web/src/components/table/model-pricing/layout/header/PricingVendorIntroSkeleton.jsx @@ -47,28 +47,22 @@ const SIZES = { }; const SKELETON_STYLES = { - cover: (primaryColor) => ({ - '--palette-primary-darkerChannel': primaryColor, - backgroundImage: `linear-gradient(0deg, rgba(var(--palette-primary-darkerChannel) / 80%), rgba(var(--palette-primary-darkerChannel) / 80%)), url('/cover-4.webp')`, - backgroundSize: 'cover', - backgroundPosition: 'center', - backgroundRepeat: 'no-repeat', + cover: () => ({ + backgroundColor: 'var(--semi-color-bg-1)', + borderBottom: '1px solid var(--semi-color-border)', }), title: { - backgroundColor: 'rgba(255, 255, 255, 0.25)', + backgroundColor: 'var(--semi-color-fill-0)', borderRadius: 8, - backdropFilter: 'blur(4px)', }, tag: { - backgroundColor: 'rgba(255, 255, 255, 0.2)', + backgroundColor: 'var(--semi-color-fill-1)', borderRadius: 9999, - backdropFilter: 'blur(4px)', - border: '1px solid rgba(255,255,255,0.3)', + border: '1px solid var(--semi-color-border)', }, description: { - backgroundColor: 'rgba(255, 255, 255, 0.2)', + backgroundColor: 'var(--semi-color-fill-1)', borderRadius: 4, - backdropFilter: 'blur(4px)', }, avatar: (isAllVendors) => { const colors = isAllVendors @@ -104,11 +98,7 @@ const PricingVendorIntroSkeleton = memo( cover={
diff --git a/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx b/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx index 266bbf9478c2..1df7114b2b1e 100644 --- a/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx +++ b/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx @@ -135,7 +135,7 @@ const ModelPricingTable = ({ dataIndex: 'inputPrice', render: (text) => ( <> -
{text}
+
{text}
/ {tokenUnit === 'K' ? '1K' : '1M'} tokens
@@ -147,7 +147,7 @@ const ModelPricingTable = ({ dataIndex: 'outputPrice', render: (text) => ( <> -
{text}
+
{text}
/ {tokenUnit === 'K' ? '1K' : '1M'} tokens
@@ -162,7 +162,7 @@ const ModelPricingTable = ({ dataIndex: 'fixedPrice', render: (text) => ( <> -
{text}
+
{text}
/ 次
), diff --git a/web/src/components/table/model-pricing/view/card/PricingCardView.jsx b/web/src/components/table/model-pricing/view/card/PricingCardView.jsx index 6d532869f652..39e6dab7469d 100644 --- a/web/src/components/table/model-pricing/view/card/PricingCardView.jsx +++ b/web/src/components/table/model-pricing/view/card/PricingCardView.jsx @@ -30,10 +30,6 @@ import { } from '@douyinfe/semi-ui'; import { IconHelpCircle } from '@douyinfe/semi-icons'; import { Copy } from 'lucide-react'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { stringToColor, calculateModelPrice, @@ -222,10 +218,7 @@ const PricingCardView = ({ return (
} - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} />
@@ -261,7 +254,7 @@ const PricingCardView = ({
{getModelIcon(model)}
-

+

{model.model_name}

diff --git a/web/src/components/table/model-pricing/view/table/PricingTable.jsx b/web/src/components/table/model-pricing/view/table/PricingTable.jsx index a93bd5260d3c..e7ed1cdee4b8 100644 --- a/web/src/components/table/model-pricing/view/table/PricingTable.jsx +++ b/web/src/components/table/model-pricing/view/table/PricingTable.jsx @@ -19,10 +19,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo } from 'react'; import { Card, Table, Empty } from '@douyinfe/semi-ui'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getPricingTableColumns } from './PricingTableColumns'; const PricingTable = ({ @@ -105,12 +101,7 @@ const PricingTable = ({ })} empty={ - } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/models/ModelsTable.jsx b/web/src/components/table/models/ModelsTable.jsx index 643bff66d196..1d5530e41014 100644 --- a/web/src/components/table/models/ModelsTable.jsx +++ b/web/src/components/table/models/ModelsTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo } from 'react'; import { Empty } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getModelsColumns } from './ModelsColumnDefs'; const ModelsTable = (modelsData) => { @@ -91,10 +87,7 @@ const ModelsTable = (modelsData) => { onRow={handleRow} empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/models/modals/MissingModelsModal.jsx b/web/src/components/table/models/modals/MissingModelsModal.jsx index f8fa78fdd6c4..faa0e26cade9 100644 --- a/web/src/components/table/models/modals/MissingModelsModal.jsx +++ b/web/src/components/table/models/modals/MissingModelsModal.jsx @@ -27,10 +27,6 @@ import { Empty, Input, } from '@douyinfe/semi-ui'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { IconSearch } from '@douyinfe/semi-icons'; import { API, showError } from '../../../../helpers'; import { MODEL_TABLE_PAGE_SIZE } from '../../../../constants'; @@ -135,10 +131,7 @@ const MissingModelsModal = ({ visible, onClose, onConfigureModel, t }) => { {missingModels.length === 0 && !loading ? ( } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('暂无缺失模型')} style={{ padding: 30 }} /> @@ -174,14 +167,7 @@ const MissingModelsModal = ({ visible, onClose, onConfigureModel, t }) => { /> ) : ( - } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={ searchKeyword ? t('未找到匹配的模型') : t('暂无缺失模型') } diff --git a/web/src/components/table/models/modals/PrefillGroupManagement.jsx b/web/src/components/table/models/modals/PrefillGroupManagement.jsx index 59ba1c787a5b..c7c8f5a09f58 100644 --- a/web/src/components/table/models/modals/PrefillGroupManagement.jsx +++ b/web/src/components/table/models/modals/PrefillGroupManagement.jsx @@ -31,10 +31,6 @@ import { Empty, } from '@douyinfe/semi-ui'; import { IconPlus, IconLayers } from '@douyinfe/semi-icons'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { API, showError, @@ -277,14 +273,7 @@ const PrefillGroupManagement = ({ visible, onClose }) => { /> ) : ( - } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('暂无预填组')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/redemptions/RedemptionsTable.jsx b/web/src/components/table/redemptions/RedemptionsTable.jsx index f106b1fcd30a..042f3ad64408 100644 --- a/web/src/components/table/redemptions/RedemptionsTable.jsx +++ b/web/src/components/table/redemptions/RedemptionsTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo, useState } from 'react'; import { Empty } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getRedemptionsColumns, isExpired } from './RedemptionsColumnDefs'; import DeleteRedemptionModal from './modals/DeleteRedemptionModal'; @@ -115,10 +111,7 @@ const RedemptionsTable = (redemptionsData) => { onRow={handleRow} empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/subscriptions/SubscriptionsTable.jsx b/web/src/components/table/subscriptions/SubscriptionsTable.jsx index 4892f66caae6..f516c7466d08 100644 --- a/web/src/components/table/subscriptions/SubscriptionsTable.jsx +++ b/web/src/components/table/subscriptions/SubscriptionsTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo } from 'react'; import { Empty } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getSubscriptionsColumns } from './SubscriptionsColumnDefs'; const SubscriptionsTable = (subscriptionsData) => { @@ -69,10 +65,7 @@ const SubscriptionsTable = (subscriptionsData) => { rowKey={(row) => row?.plan?.id} empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('暂无订阅套餐')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/task-logs/TaskLogsTable.jsx b/web/src/components/table/task-logs/TaskLogsTable.jsx index c3b4d5145e2e..c1a7797452bd 100644 --- a/web/src/components/table/task-logs/TaskLogsTable.jsx +++ b/web/src/components/table/task-logs/TaskLogsTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo } from 'react'; import { Empty } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getTaskLogsColumns } from './TaskLogsColumnDefs'; const TaskLogsTable = (taskLogsData) => { @@ -83,10 +79,7 @@ const TaskLogsTable = (taskLogsData) => { size='middle' empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/tokens/TokensTable.jsx b/web/src/components/table/tokens/TokensTable.jsx index eab5707cdac9..0baaa1552efc 100644 --- a/web/src/components/table/tokens/TokensTable.jsx +++ b/web/src/components/table/tokens/TokensTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo } from 'react'; import { Empty } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getTokensColumns } from './TokensColumnDefs'; const TokensTable = (tokensData) => { @@ -107,10 +103,7 @@ const TokensTable = (tokensData) => { onRow={handleRow} empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/tokens/index.jsx b/web/src/components/table/tokens/index.jsx index f6194a35a38f..83d6da439dbe 100644 --- a/web/src/components/table/tokens/index.jsx +++ b/web/src/components/table/tokens/index.jsx @@ -276,7 +276,6 @@ function TokensPage() { const existing = document.querySelector(selector); if (existing) { - console.log('Fluent container detected (initial):', existing); window.dispatchEvent( new CustomEvent('fluent-container:appeared', { detail: existing }), ); @@ -298,7 +297,6 @@ function TokensPage() { if (isOrContainsTarget(added)) { const el = document.querySelector(selector); if (el) { - console.log('Fluent container appeared:', el); window.dispatchEvent( new CustomEvent('fluent-container:appeared', { detail: el }), ); @@ -311,7 +309,6 @@ function TokensPage() { if (isOrContainsTarget(removed)) { const elNow = document.querySelector(selector); if (!elNow) { - console.log('Fluent container removed'); window.dispatchEvent(new CustomEvent('fluent-container:removed')); } break; diff --git a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx index d38dc61937a8..4264a17d0155 100644 --- a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx +++ b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx @@ -399,7 +399,6 @@ export const getLogsColumns = ({ try { other = JSON.parse(record.other); } catch (e) { - console.error( `Failed to parse record.other: "${record.other}".`, e, ); diff --git a/web/src/components/table/usage-logs/UsageLogsTable.jsx b/web/src/components/table/usage-logs/UsageLogsTable.jsx index 103dc9302cb1..53b7d9e80dfb 100644 --- a/web/src/components/table/usage-logs/UsageLogsTable.jsx +++ b/web/src/components/table/usage-logs/UsageLogsTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo } from 'react'; import { Empty, Descriptions } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getLogsColumns } from './UsageLogsColumnDefs'; const LogsTable = (logsData) => { @@ -102,10 +98,7 @@ const LogsTable = (logsData) => { size='middle' empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/users/UsersTable.jsx b/web/src/components/table/users/UsersTable.jsx index e0f8a9cec343..17fc82614319 100644 --- a/web/src/components/table/users/UsersTable.jsx +++ b/web/src/components/table/users/UsersTable.jsx @@ -20,10 +20,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo, useState } from 'react'; import { Empty } from '@douyinfe/semi-ui'; import CardTable from '../../common/ui/CardTable'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { getUsersColumns } from './UsersColumnDefs'; import PromoteUserModal from './modals/PromoteUserModal'; import DemoteUserModal from './modals/DemoteUserModal'; @@ -188,10 +184,7 @@ const UsersTable = (usersData) => { onRow={handleRow} empty={ } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/components/table/users/modals/UserSubscriptionsModal.jsx b/web/src/components/table/users/modals/UserSubscriptionsModal.jsx index 455f7b188095..5f2d7f56116a 100644 --- a/web/src/components/table/users/modals/UserSubscriptionsModal.jsx +++ b/web/src/components/table/users/modals/UserSubscriptionsModal.jsx @@ -29,10 +29,6 @@ import { Typography, } from '@douyinfe/semi-ui'; import { IconPlusCircle } from '@douyinfe/semi-icons'; -import { - IllustrationNoResult, - IllustrationNoResultDark, -} from '@douyinfe/semi-illustrations'; import { API, showError, showSuccess } from '../../../../helpers'; import { convertUSDToCurrency } from '../../../../helpers/render'; import { useIsMobile } from '../../../../hooks/common/useIsMobile'; @@ -413,12 +409,7 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { }} empty={ - } - darkModeImage={ - - } + image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('暂无订阅记录')} style={{ padding: 30 }} /> diff --git a/web/src/components/topup/InvitationCard.jsx b/web/src/components/topup/InvitationCard.jsx index 7ae6b0a5a23e..fbfe1f7970a0 100644 --- a/web/src/components/topup/InvitationCard.jsx +++ b/web/src/components/topup/InvitationCard.jsx @@ -63,17 +63,14 @@ const InvitationCard = ({
{/* 标题和按钮 */}
- + {t('收益统计')}