From 25029e5c745d8917cc2514c5994c0d250994fe0b Mon Sep 17 00:00:00 2001 From: iwvw <2285740204@qq.com> Date: Wed, 21 Jan 2026 21:25:51 +0800 Subject: [PATCH 1/3] fix: CSP error --- src/css/openai.css | 18 +++++++++++------- src/middleware/security.js | 2 ++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/css/openai.css b/src/css/openai.css index 21a6ee2..27ef63d 100644 --- a/src/css/openai.css +++ b/src/css/openai.css @@ -837,7 +837,7 @@ /* Reduced Input Height */ .openai-chat-input-wrapper { - padding: 6px; + padding: 0px 6px 6px 6px; /* Less vertical padding */ background: var(--bg-secondary); /* border-top: 1px solid var(--border-color); */ @@ -1759,17 +1759,17 @@ /* Modern Input Styling */ .openai-chat-input-wrapper { background: transparent !important; - padding: 16px !important; + /* padding: 16px !important; */ } .input-panel { background: rgba(255, 255, 255, 0.75) !important; backdrop-filter: blur(12px); border: 1px solid rgba(0, 0, 0, 0.08) !important; - border-radius: 24px !important; + /* border-radius: 24px !important; */ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05) !important; - padding: 6px 12px !important; - max-width: 900px; + padding: 6px !important; + /* max-width: 900px; */ margin: 0 auto; /* Center on large screens */ } @@ -2342,11 +2342,15 @@ .chat-attachments-preview { display: flex; gap: 12px; - padding: 12px; - background: rgba(0, 0, 0, 0.1); + padding: 12px 16px; + background: linear-gradient(135deg, rgba(16, 163, 127, 0.08) 0%, rgba(16, 163, 127, 0.02) 100%); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + border: 1px solid rgba(16, 163, 127, 0.15); border-bottom: 1px solid var(--border-color); overflow-x: auto; border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .attachment-item { diff --git a/src/middleware/security.js b/src/middleware/security.js index 4145f46..d2fbb32 100644 --- a/src/middleware/security.js +++ b/src/middleware/security.js @@ -50,6 +50,8 @@ function configureHelmet(options = {}) { 'ws:', 'https:', 'http:', // 允许所有 HTTP/HTTPS 连接 + 'data:', // 允许 data URL(图片上传使用) + 'blob:', // 允许 blob URL ], objectSrc: ["'none'"], frameAncestors: ["'self'"], From 0e0e9d6b7f9a08c7729f64df67f67cb7197ed176 Mon Sep 17 00:00:00 2001 From: iwvw <2285740204@qq.com> Date: Wed, 21 Jan 2026 21:46:54 +0800 Subject: [PATCH 2/3] feat: Add multi-architecture Docker image CI/CD pipeline, modify Dockerfile, and introduce a new transitions JavaScript module. --- .github/workflows/docker-publish.yml | 184 +++++++++++++++++++++------ Dockerfile | 42 +++--- src/js/modules/transitions.js | 5 + 3 files changed, 178 insertions(+), 53 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 82e6d66..f5df56f 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -6,6 +6,10 @@ # - 推送到其他分支 → dev 标签 # - 创建版本标签(如 v1.0.0) # - 手动触发(workflow_dispatch) +# +# 构建策略: +# - amd64 和 arm64 分别在原生 runner 上构建(避免 QEMU 问题) +# - 使用 docker manifest 合并多架构镜像 name: Build and Publish Docker Image @@ -25,7 +29,7 @@ on: required: false default: "latest" platforms: - description: "构建平台(可选)" + description: "构建平台(可选,逗号分隔)" required: false default: "linux/amd64,linux/arm64" @@ -39,27 +43,32 @@ env: DOCKERHUB_REPO: ${{ github.repository_owner }}/api-monitor jobs: - build-and-push: - runs-on: ubuntu-latest + # ========================================== + # 阶段 1: 分架构构建(原生 runner,避免 QEMU) + # ========================================== + build: + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + suffix: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + suffix: arm64 + + runs-on: ${{ matrix.runner }} + outputs: + tags: ${{ steps.meta.outputs.tags }} steps: - # 检出代码 - name: Checkout repository uses: actions/checkout@v4 - # 设置 QEMU(用于多架构构建) - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - # 设置 Docker Buildx - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - with: - driver-opts: | - image=moby/buildkit:latest - network=host - # 登录 GitHub Container Registry - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: @@ -67,7 +76,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # 登录 Docker Hub (请确保已在 GitHub Secrets 中配置 DOCKERHUB_USERNAME 和 DOCKERHUB_TOKEN) - name: Login to Docker Hub if: github.event_name != 'pull_request' uses: docker/login-action@v3 @@ -75,7 +83,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - # 提取元数据(标签、标签等) + # 提取元数据(用于生成标签) - name: Extract metadata id: meta uses: docker/metadata-action@v5 @@ -84,19 +92,13 @@ jobs: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} ${{ env.DOCKERHUB_REPO }} tags: | - # 默认 latest 标签(推送到 main 分支或版本标签时都生成) type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} - # dev 标签(非主分支时生成) type=raw,value=dev,enable=${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') }} - # 分支名标签(非主分支时,便于区分不同分支的构建) type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }} - # Git commit SHA(仅在推送到 main 分支时生成) type=sha,prefix=,enable=${{ github.ref == 'refs/heads/main' }} - # 版本标签(如 v1.0.0 -> 1.0.0) type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - # 手动触发时使用输入的标签 type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' }} labels: | org.opencontainers.image.title=API Monitor @@ -104,22 +106,104 @@ jobs: org.opencontainers.image.vendor=iwvw maintainer=iwvw - # 构建并推送镜像 - - name: Build and push Docker image + # 构建并推送单架构镜像(带后缀) + - name: Build and push by digest + id: build uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile + platforms: ${{ matrix.platform }} push: true - platforms: ${{ github.event.inputs.platforms != '' && github.event.inputs.platforms || 'linux/amd64,linux/arm64' }} - tags: ${{ steps.meta.outputs.tags }} + # 使用带架构后缀的临时标签 + tags: | + ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-${{ matrix.suffix }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=${{ matrix.platform }} build-args: | NODE_ENV=production - # 输出镜像信息 + - name: Export digest + run: | + mkdir -p /tmp/digests + echo "${{ steps.build.outputs.digest }}" > /tmp/digests/${{ matrix.suffix }} + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.suffix }} + path: /tmp/digests/* + retention-days: 1 + + # ========================================== + # 阶段 2: 合并多架构 manifest + # ========================================== + merge: + runs-on: ubuntu-latest + needs: build + + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} + ${{ env.DOCKERHUB_REPO }} + tags: | + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} + type=raw,value=dev,enable=${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') }} + type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }} + type=sha,prefix=,enable=${{ github.ref == 'refs/heads/main' }} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' }} + + # 创建并推送多架构 manifest + - name: Create and push manifest + run: | + # 读取各架构 digest + AMD64_DIGEST=$(cat /tmp/digests/amd64) + ARM64_DIGEST=$(cat /tmp/digests/arm64) + + echo "AMD64 Digest: $AMD64_DIGEST" + echo "ARM64 Digest: $ARM64_DIGEST" + + # 为每个目标标签创建 manifest + TAGS="${{ steps.meta.outputs.tags }}" + echo "$TAGS" | while IFS= read -r TAG; do + [ -z "$TAG" ] && continue + echo "Creating manifest for: $TAG" + docker buildx imagetools create -t "$TAG" \ + "${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}@${AMD64_DIGEST}" \ + "${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}@${ARM64_DIGEST}" + done + - name: Image digest run: | echo "## 🐳 Docker 镜像构建成功" >> $GITHUB_STEP_SUMMARY @@ -129,6 +213,8 @@ jobs: echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY + echo "**支持架构:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY echo "**拉取命令:**" >> $GITHUB_STEP_SUMMARY echo '```bash' >> $GITHUB_STEP_SUMMARY echo "# 从 GHCR 拉取" >> $GITHUB_STEP_SUMMARY @@ -137,24 +223,46 @@ jobs: echo "# 从 Docker Hub 拉取" >> $GITHUB_STEP_SUMMARY echo "docker pull ${{ env.DOCKERHUB_REPO }}:latest" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**使用 Docker Compose 运行:**" >> $GITHUB_STEP_SUMMARY - echo '```bash' >> $GITHUB_STEP_SUMMARY - echo "docker-compose up -d" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - # 镜像安全扫描(可选) + # ========================================== + # 阶段 3: 清理临时镜像(可选) + # ========================================== + cleanup: + runs-on: ubuntu-latest + needs: merge + if: always() + continue-on-error: true + + steps: + - name: Delete temporary build images + uses: actions/delete-package-versions@v5 + with: + package-name: ${{ env.IMAGE_NAME }} + package-type: container + min-versions-to-keep: 0 + delete-only-untagged-versions: false + # 只删除临时构建标签 + ignore-versions: "^(?!build-${{ github.run_id }}).*$" + continue-on-error: true + + # ========================================== + # 阶段 4: 安全扫描(可选) + # ========================================== + security-scan: + runs-on: ubuntu-latest + needs: merge + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') + continue-on-error: true + + steps: - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest format: "sarif" output: "trivy-results.sarif" - continue-on-error: true - # 上传扫描结果(可选) - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 with: sarif_file: "trivy-results.sarif" - continue-on-error: true diff --git a/Dockerfile b/Dockerfile index 917ec5a..0b844ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ # =================================== # API Monitor Docker Image # =================================== -# 多阶段构建:Builder -> Runner +# 多阶段构建:Builder -> Native Deps Builder -> Runner -# 阶段 1: 构建前端 (Builder) +# 阶段 1: 构建前端 (Builder) - 始终在构建主机平台运行 FROM --platform=$BUILDPLATFORM node:20-alpine AS builder # 安装构建工具 RUN apk add --no-cache python3 make g++ @@ -47,7 +47,22 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o agent-lin # 构建 Windows amd64 RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o agent-windows-amd64.exe -# 阶段 3: 运行时镜像 (Runner) +# 阶段 3: 预构建生产依赖 (Native Deps Builder) +# 为目标平台安装原生模块 +FROM --platform=$TARGETPLATFORM node:20-alpine AS deps-builder +# 安装构建工具 (用于编译 better-sqlite3 等原生模块,以防预编译不可用) +RUN apk add --no-cache python3 make g++ curl +WORKDIR /app +# 复制依赖定义 +COPY package.json package-lock.json ./ +# 设置镜像源 +RUN npm config set registry https://registry.npmmirror.com +# 尝试使用预编译二进制,如果不可用则编译 +# better-sqlite3 支持 prebuild,会自动下载预编译的 .node 文件 +ENV npm_config_build_from_source=false +RUN npm install --omit=dev --legacy-peer-deps && npm cache clean --force + +# 阶段 4: 运行时镜像 (Runner) - 纯净的运行环境 FROM --platform=$TARGETPLATFORM node:20-alpine AS runner LABEL org.opencontainers.image.title="API Monitor" @@ -65,25 +80,22 @@ WORKDIR /app # 创建数据目录 RUN mkdir -p /app/config /app/data && chown -R nodejs:nodejs /app -# 1. 复制依赖定义 -COPY package.json package-lock.json ./ - -# 设置镜像源 -RUN npm config set registry https://registry.npmmirror.com +# 1. 从 deps-builder 复制预构建的 node_modules (避免在 runner 中编译) +COPY --from=deps-builder --chown=nodejs:nodejs /app/node_modules ./node_modules +COPY --from=deps-builder --chown=nodejs:nodejs /app/package.json ./ -# 2. 仅安装生产依赖 (减小体积) -RUN npm install --omit=dev --legacy-peer-deps && npm cache clean --force - -# 3. 从各阶段复制构建好的资源 +# 2. 从 builder 复制构建好的前端资源 COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist -# 将 Go Agent 二进制文件放入 dist/agent 目录以便静态服务 + +# 3. 将 Go Agent 二进制文件放入 dist/agent 目录以便静态服务 RUN mkdir -p /app/dist/agent COPY --from=agent-builder --chown=nodejs:nodejs /app/agent-go/agent-linux-amd64 /app/dist/agent/ COPY --from=agent-builder --chown=nodejs:nodejs /app/agent-go/agent-linux-arm64 /app/dist/agent/ COPY --from=agent-builder --chown=nodejs:nodejs /app/agent-go/agent-windows-amd64.exe /app/dist/agent/ -# 4. 复制后端源码 -COPY --chown=nodejs:nodejs . . +# 4. 复制后端源码 (不包含 node_modules) +COPY --chown=nodejs:nodejs server.js ./ +COPY --chown=nodejs:nodejs src ./src ENV NODE_ENV=production \ PORT=3000 \ diff --git a/src/js/modules/transitions.js b/src/js/modules/transitions.js index ab4e16c..ea5d896 100644 --- a/src/js/modules/transitions.js +++ b/src/js/modules/transitions.js @@ -6,6 +6,11 @@ export const transitionsMethods = { // 获取标签页动画类 getTabAnimationClass(tabName) { + // 只有当前激活的标签页才应用动画类 + if (this.mainActiveTab !== tabName) { + return ''; + } + if (!this.previousMainTab) { // 首次加载,使用淡入动画 return 'fade-in'; From f41369060432f00cee92cd9746006290aa41786a Mon Sep 17 00:00:00 2001 From: iwvw <2285740204@qq.com> Date: Wed, 21 Jan 2026 22:07:29 +0800 Subject: [PATCH 3/3] feat: add API model configuration matrix and OpenAI module. --- .../antigravity-api/antigravity-matrix.json | 22 ++++++++---- src/js/modules/openai.js | 36 +++++++++++++++++-- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/modules/antigravity-api/antigravity-matrix.json b/modules/antigravity-api/antigravity-matrix.json index c1550d1..ff63cd8 100644 --- a/modules/antigravity-api/antigravity-matrix.json +++ b/modules/antigravity-api/antigravity-matrix.json @@ -1,8 +1,8 @@ { "gemini-2.5-pro": { "base": true, - "fakeStream": true, - "antiTrunc": true + "fakeStream": false, + "antiTrunc": false }, "gemini-2.5-flash": { "base": true, @@ -11,8 +11,8 @@ }, "gemini-2.5-flash-lite": { "base": true, - "fakeStream": true, - "antiTrunc": true + "fakeStream": false, + "antiTrunc": false }, "gemini-3-pro-high": { "base": true, @@ -31,8 +31,8 @@ }, "gpt-oss-120b-medium": { "base": true, - "fakeStream": true, - "antiTrunc": true + "fakeStream": false, + "antiTrunc": false }, "gemini-3-pro-image": { "base": true, @@ -58,5 +58,15 @@ "base": true, "fakeStream": false, "antiTrunc": false + }, + "gemini-2.5-flash-thinking": { + "base": false, + "fakeStream": false, + "antiTrunc": false + }, + "tab_flash_lite_preview": { + "base": false, + "fakeStream": false, + "antiTrunc": false } } \ No newline at end of file diff --git a/src/js/modules/openai.js b/src/js/modules/openai.js index 81c8813..7d1b2ee 100644 --- a/src/js/modules/openai.js +++ b/src/js/modules/openai.js @@ -12,10 +12,30 @@ const imageUploadCache = new Map(); // 图片上传缓存 import { renderMarkdown } from './utils.js'; export const openaiMethods = { + // 从内容中提取思考标签(支持各种变体如 , , 等) + extractThinkingContent(content) { + if (!content || typeof content !== 'string') return { thinking: '', cleaned: content || '' }; + + // 匹配各种思考标签变体: , , , etc. + const thinkingPattern = /<(think(?:ing|_\w+)?)\s*>([\s\S]*?)<\/\1>/gi; + let thinking = ''; + let cleaned = content; + + let match; + while ((match = thinkingPattern.exec(content)) !== null) { + thinking += match[2].trim() + '\n'; + } + + // 移除所有思考标签 + cleaned = content.replace(thinkingPattern, '').trim(); + + return { thinking: thinking.trim(), cleaned }; + }, + // 带缓存的消息渲染(避免 Base64 图片导致的重复计算) getCachedMessageHtml(msg, field = 'content') { if (!msg) return ''; - const content = msg[field]; + let content = msg[field]; if (content === undefined || content === null) return ''; // 生成缓存 key @@ -23,12 +43,24 @@ export const openaiMethods = { const contentKey = `_cachedSource_${field}`; // 检查缓存是否有效(内容未变化) - // 对于数组类型内容,使用 JSON.stringify 比较(虽然有性能开销,但只在首次渲染时执行) const contentHash = typeof content === 'string' ? content : JSON.stringify(content); if (msg[cacheKey] && msg[contentKey] === contentHash) { return msg[cacheKey]; } + // 对于 content 字段,先过滤思考标签 + if (field === 'content' && typeof content === 'string') { + const { thinking, cleaned } = this.extractThinkingContent(content); + + // 如果提取到了思考内容且 msg.reasoning 为空,自动填充 + if (thinking && !msg.reasoning) { + msg.reasoning = thinking; + msg.showReasoning = false; // 默认折叠 + } + + content = cleaned; + } + // 渲染并缓存 const html = renderMarkdown(content); msg[cacheKey] = html;