diff --git a/.env.example b/.env.example deleted file mode 100644 index 452c28a..0000000 --- a/.env.example +++ /dev/null @@ -1,10 +0,0 @@ -# Discourse SSO 配置 -# Discourse 网站地址 -DISCOURSE_URL=https://q58.pro - -# SSO 密钥 (必需) -# 可以使用以下命令生成: openssl rand -hex 32 -DISCOURSE_SSO_SECRET=your_sso_secret_here - -# 服务器配置 -PORT=8000 \ No newline at end of file diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..ed5e88d --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,59 @@ +name: Docker Build and Push + +on: + push: + branches: [ "main" ] + tags: [ 'v*.*.*' ] + pull_request: + branches: [ "main" ] + +env: + DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }} + IMAGE_NAME: ${{ secrets.DOCKER_HUB_USERNAME }}/aimodels-prices + +jobs: + build: + runs-on: ubuntu-latest + + 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 + + # 登录到 Docker Hub + - name: Log into Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + # 提取版本信息 + - name: Extract version + id: version + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + else + echo "VERSION=latest" >> $GITHUB_OUTPUT + fi + + # 构建并推送 Docker 镜像 + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + platforms: linux/amd64,linux/arm64 + tags: | + ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} + ${{ env.IMAGE_NAME }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.gitignore b/.gitignore index e83a6d2..d9ee314 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ kaifa.md .env .env.local .env.*.local +wrangler.toml # 系统文件 .DS_Store diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1fbe96c65917121592d4d49b8308bf00e2e4238540275494cd335eb76f5bfcda.sqlite b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1fbe96c65917121592d4d49b8308bf00e2e4238540275494cd335eb76f5bfcda.sqlite new file mode 100644 index 0000000..0b58f0d Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1fbe96c65917121592d4d49b8308bf00e2e4238540275494cd335eb76f5bfcda.sqlite differ diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1fbe96c65917121592d4d49b8308bf00e2e4238540275494cd335eb76f5bfcda.sqlite-shm b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1fbe96c65917121592d4d49b8308bf00e2e4238540275494cd335eb76f5bfcda.sqlite-shm new file mode 100644 index 0000000..d6fcf24 Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1fbe96c65917121592d4d49b8308bf00e2e4238540275494cd335eb76f5bfcda.sqlite-shm differ diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1fbe96c65917121592d4d49b8308bf00e2e4238540275494cd335eb76f5bfcda.sqlite-wal b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1fbe96c65917121592d4d49b8308bf00e2e4238540275494cd335eb76f5bfcda.sqlite-wal new file mode 100644 index 0000000..cf773dc Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1fbe96c65917121592d4d49b8308bf00e2e4238540275494cd335eb76f5bfcda.sqlite-wal differ diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/38dd4d5a44c85be39743d2dd03425e2bc1680446bbb634117b50e659261268fc.sqlite b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/38dd4d5a44c85be39743d2dd03425e2bc1680446bbb634117b50e659261268fc.sqlite new file mode 100644 index 0000000..d1a7064 Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/38dd4d5a44c85be39743d2dd03425e2bc1680446bbb634117b50e659261268fc.sqlite differ diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/97ea68c203504371df2eaae8a072fd30e7ff10d9b1da886e5e58eb2fc78cb772.sqlite b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/97ea68c203504371df2eaae8a072fd30e7ff10d9b1da886e5e58eb2fc78cb772.sqlite new file mode 100644 index 0000000..d1dcec6 Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/97ea68c203504371df2eaae8a072fd30e7ff10d9b1da886e5e58eb2fc78cb772.sqlite differ diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/97ea68c203504371df2eaae8a072fd30e7ff10d9b1da886e5e58eb2fc78cb772.sqlite-shm b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/97ea68c203504371df2eaae8a072fd30e7ff10d9b1da886e5e58eb2fc78cb772.sqlite-shm new file mode 100644 index 0000000..8213601 Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/97ea68c203504371df2eaae8a072fd30e7ff10d9b1da886e5e58eb2fc78cb772.sqlite-shm differ diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/97ea68c203504371df2eaae8a072fd30e7ff10d9b1da886e5e58eb2fc78cb772.sqlite-wal b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/97ea68c203504371df2eaae8a072fd30e7ff10d9b1da886e5e58eb2fc78cb772.sqlite-wal new file mode 100644 index 0000000..a729bfa Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/97ea68c203504371df2eaae8a072fd30e7ff10d9b1da886e5e58eb2fc78cb772.sqlite-wal differ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0a8940b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,58 @@ +# 第一阶段:构建后端 +FROM golang:1.21-alpine AS backend-builder + +WORKDIR /app/backend + +# 安装依赖 +COPY backend/go.mod backend/go.sum ./ +RUN go mod download + +# 复制后端源代码 +COPY backend/ . + +# 编译后端 +RUN CGO_ENABLED=0 GOOS=linux go build -o main . + +# 第二阶段:构建前端 +FROM node:18-alpine AS frontend-builder + +WORKDIR /app/frontend + +# 安装依赖 +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci + +# 复制前端源代码 +COPY frontend/ . + +# 构建前端 +RUN npm run build + +# 第三阶段:最终镜像 +FROM alpine:3.18 + +WORKDIR /app + +# 安装 nginx +RUN apk add --no-cache nginx + +# 创建数据目录 +RUN mkdir -p /app/data + +# 复制后端二进制文件 +COPY --from=backend-builder /app/backend/main ./ +COPY backend/config/nginx.conf /etc/nginx/nginx.conf + +# 复制前端构建产物 +COPY --from=frontend-builder /app/frontend/.next/static /app/frontend/static +COPY --from=frontend-builder /app/frontend/public /app/frontend/public +COPY --from=frontend-builder /app/frontend/.next/standalone /app/frontend + +# 复制启动脚本 +COPY scripts/start.sh ./ +RUN chmod +x start.sh + +EXPOSE 80 + +# 启动服务 +CMD ["./start.sh"] \ No newline at end of file diff --git a/backend/config/config.go b/backend/config/config.go new file mode 100644 index 0000000..d35e6e8 --- /dev/null +++ b/backend/config/config.go @@ -0,0 +1,47 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/joho/godotenv" +) + +type Config struct { + DBPath string + ServerPort string +} + +func LoadConfig() (*Config, error) { + // 确保数据目录存在 + dbDir := "./data" + if err := os.MkdirAll(dbDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create data directory: %v", err) + } + + // 尝试从 data 目录加载 .env 文件 + envPath := filepath.Join(dbDir, ".env") + if err := godotenv.Load(envPath); err != nil { + fmt.Printf("Warning: .env file not found in data directory: %v\n", err) + // 如果 data/.env 不存在,尝试加载项目根目录的 .env + if err := godotenv.Load(); err != nil { + fmt.Printf("Warning: .env file not found in root directory: %v\n", err) + } + } + + config := &Config{ + DBPath: filepath.Join(dbDir, "aimodels.db"), + ServerPort: getEnv("PORT", "8080"), + } + + return config, nil +} + +func getEnv(key, defaultValue string) string { + value := os.Getenv(key) + if value == "" { + return defaultValue + } + return value +} diff --git a/backend/database/db.go b/backend/database/db.go new file mode 100644 index 0000000..54b19bc --- /dev/null +++ b/backend/database/db.go @@ -0,0 +1,67 @@ +package database + +import ( + "database/sql" + "log" + + _ "modernc.org/sqlite" + + "aimodels-prices/models" +) + +// DB 是数据库连接的全局实例 +var DB *sql.DB + +// InitDB 初始化数据库连接 +func InitDB(dbPath string) error { + var err error + DB, err = sql.Open("sqlite", dbPath) + if err != nil { + return err + } + + // 测试连接 + if err = DB.Ping(); err != nil { + return err + } + + // 设置连接池参数 + DB.SetMaxOpenConns(10) + DB.SetMaxIdleConns(5) + + // 创建表结构 + if err = createTables(); err != nil { + return err + } + + return nil +} + +// createTables 创建数据库表 +func createTables() error { + // 创建用户表 + if _, err := DB.Exec(models.CreateUserTableSQL()); err != nil { + log.Printf("Failed to create user table: %v", err) + return err + } + + // 创建会话表 + if _, err := DB.Exec(models.CreateSessionTableSQL()); err != nil { + log.Printf("Failed to create session table: %v", err) + return err + } + + // 创建供应商表 + if _, err := DB.Exec(models.CreateProviderTableSQL()); err != nil { + log.Printf("Failed to create provider table: %v", err) + return err + } + + // 创建价格表 + if _, err := DB.Exec(models.CreatePriceTableSQL()); err != nil { + log.Printf("Failed to create price table: %v", err) + return err + } + + return nil +} diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..c7064ed --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,51 @@ +module aimodels-prices + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/joho/godotenv v1.5.1 + modernc.org/sqlite v1.28.0 +) + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.6.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.29.0 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect +) diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..7a1109b --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,134 @@ +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= +modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= +modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= +modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/backend/handlers/auth.go b/backend/handlers/auth.go new file mode 100644 index 0000000..e76919e --- /dev/null +++ b/backend/handlers/auth.go @@ -0,0 +1,291 @@ +package handlers + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "database/sql" + "encoding/base64" + "encoding/hex" + "fmt" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/gin-gonic/gin" + + "aimodels-prices/models" +) + +func generateSessionID() string { + b := make([]byte, 32) + rand.Read(b) + return hex.EncodeToString(b) +} + +func GetAuthStatus(c *gin.Context) { + cookie, err := c.Cookie("session") + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Not logged in"}) + return + } + + db := c.MustGet("db").(*sql.DB) + var session models.Session + err = db.QueryRow("SELECT id, user_id, expires_at, created_at, updated_at, deleted_at FROM session WHERE id = ?", cookie).Scan( + &session.ID, &session.UserID, &session.ExpiresAt, &session.CreatedAt, &session.UpdatedAt, &session.DeletedAt) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid session"}) + return + } + + if session.ExpiresAt.Before(time.Now()) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Session expired"}) + return + } + + user, err := session.GetUser(db) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user"}) + return + } + + c.Set("user", user) + c.JSON(http.StatusOK, gin.H{ + "user": user, + }) +} + +func Login(c *gin.Context) { + // 开发环境下使用测试账号 + if gin.Mode() != gin.ReleaseMode { + db := c.MustGet("db").(*sql.DB) + + // 创建测试用户(如果不存在) + var count int + err := db.QueryRow("SELECT COUNT(*) FROM user WHERE username = 'admin'").Scan(&count) + if err != nil || count == 0 { + _, err = db.Exec("INSERT INTO user (username, email, role) VALUES (?, ?, ?)", + "admin", "admin@test.com", "admin") + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create test user"}) + return + } + } + + // 获取用户ID + var userID uint + err = db.QueryRow("SELECT id FROM user WHERE username = 'admin'").Scan(&userID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user"}) + return + } + + // 创建会话 + sessionID := generateSessionID() + expiresAt := time.Now().Add(24 * time.Hour) + _, err = db.Exec("INSERT INTO session (id, user_id, expires_at) VALUES (?, ?, ?)", + sessionID, userID, expiresAt) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create session"}) + return + } + + // 设置cookie + c.SetCookie("session", sessionID, int(24*time.Hour.Seconds()), "/", "", false, true) + c.JSON(http.StatusOK, gin.H{"message": "Logged in successfully"}) + return + } + + // 生产环境使用 Discourse SSO + discourseURL := os.Getenv("DISCOURSE_URL") + if discourseURL == "" { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Discourse URL not configured"}) + return + } + + // 生成随机 nonce + nonce := make([]byte, 16) + if _, err := rand.Read(nonce); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate nonce"}) + return + } + nonceStr := hex.EncodeToString(nonce) + + // 构建 payload + payload := url.Values{} + payload.Set("nonce", nonceStr) + payload.Set("return_sso_url", fmt.Sprintf("%s/api/auth/callback", c.Request.Host)) + + // Base64 编码 + payloadStr := base64.StdEncoding.EncodeToString([]byte(payload.Encode())) + + // 计算签名 + ssoSecret := os.Getenv("DISCOURSE_SSO_SECRET") + if ssoSecret == "" { + c.JSON(http.StatusInternalServerError, gin.H{"error": "SSO secret not configured"}) + return + } + + h := hmac.New(sha256.New, []byte(ssoSecret)) + h.Write([]byte(payloadStr)) + sig := hex.EncodeToString(h.Sum(nil)) + + // 构建重定向 URL + redirectURL := fmt.Sprintf("%s/session/sso_provider?sso=%s&sig=%s", + discourseURL, url.QueryEscape(payloadStr), sig) + + c.Redirect(http.StatusTemporaryRedirect, redirectURL) +} + +func Logout(c *gin.Context) { + cookie, err := c.Cookie("session") + if err == nil { + db := c.MustGet("db").(*sql.DB) + db.Exec("DELETE FROM session WHERE id = ?", cookie) + } + + c.SetCookie("session", "", -1, "/", "", false, true) + c.JSON(http.StatusOK, gin.H{"message": "Logged out successfully"}) +} + +func GetUser(c *gin.Context) { + cookie, err := c.Cookie("session") + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Not logged in"}) + return + } + + db := c.MustGet("db").(*sql.DB) + var session models.Session + if err := db.QueryRow("SELECT id, user_id, expires_at, created_at, updated_at, deleted_at FROM session WHERE id = ?", cookie).Scan( + &session.ID, &session.UserID, &session.ExpiresAt, &session.CreatedAt, &session.UpdatedAt, &session.DeletedAt); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid session"}) + return + } + + user, err := session.GetUser(db) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "user": user, + }) +} + +func AuthCallback(c *gin.Context) { + sso := c.Query("sso") + sig := c.Query("sig") + + if sso == "" || sig == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Missing parameters"}) + return + } + + // 获取 SSO 密钥 + ssoSecret := os.Getenv("DISCOURSE_SSO_SECRET") + if ssoSecret == "" { + c.JSON(http.StatusInternalServerError, gin.H{"error": "SSO secret not configured"}) + return + } + + // 验证签名 + h := hmac.New(sha256.New, []byte(ssoSecret)) + h.Write([]byte(sso)) + computedSig := hex.EncodeToString(h.Sum(nil)) + if computedSig != sig { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid signature"}) + return + } + + // 解码 SSO payload + payload, err := base64.StdEncoding.DecodeString(sso) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid SSO payload"}) + return + } + + // 解析 payload + values, err := url.ParseQuery(string(payload)) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid payload format"}) + return + } + + // 获取用户信息 + username := values.Get("username") + email := values.Get("email") + groups := values.Get("groups") + admin := values.Get("admin") // Discourse 管理员标志 + moderator := values.Get("moderator") // Discourse 版主标志 + if username == "" || email == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Missing user information"}) + return + } + + // 判断用户角色 + role := "user" + // 如果是管理员、版主或属于 admins 组,都赋予管理权限 + if admin == "true" || moderator == "true" || (groups != "" && strings.Contains(groups, "admins")) { + role = "admin" + } + + db := c.MustGet("db").(*sql.DB) + + // 检查用户是否存在 + var user models.User + err = db.QueryRow("SELECT id, username, email, role FROM user WHERE email = ?", email).Scan( + &user.ID, &user.Username, &user.Email, &user.Role) + + if err == sql.ErrNoRows { + // 创建新用户 + result, err := db.Exec(` + INSERT INTO user (username, email, role) + VALUES (?, ?, ?)`, + username, email, role) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"}) + return + } + userID, _ := result.LastInsertId() + user = models.User{ + ID: uint(userID), + Username: username, + Email: email, + Role: role, + } + } else if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"}) + return + } else { + // 更新现有用户的角色(如果需要) + if user.Role != role { + _, err = db.Exec("UPDATE user SET role = ? WHERE id = ?", role, user.ID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update user role"}) + return + } + user.Role = role + } + } + + // 创建会话 + sessionID := generateSessionID() + expiresAt := time.Now().Add(24 * time.Hour) + _, err = db.Exec("INSERT INTO session (id, user_id, expires_at) VALUES (?, ?, ?)", + sessionID, user.ID, expiresAt) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create session"}) + return + } + + // 设置 cookie + c.SetCookie("session", sessionID, int(24*time.Hour.Seconds()), "/", "", false, true) + + // 重定向到前端 + c.Redirect(http.StatusTemporaryRedirect, "/") +} diff --git a/backend/handlers/prices.go b/backend/handlers/prices.go new file mode 100644 index 0000000..cc47db7 --- /dev/null +++ b/backend/handlers/prices.go @@ -0,0 +1,274 @@ +package handlers + +import ( + "database/sql" + "net/http" + "time" + + "github.com/gin-gonic/gin" + + "aimodels-prices/models" +) + +func GetPrices(c *gin.Context) { + db := c.MustGet("db").(*sql.DB) + rows, err := db.Query(` + SELECT id, model, billing_type, channel_type, currency, input_price, output_price, + price_source, status, created_at, updated_at, created_by, + temp_model, temp_billing_type, temp_channel_type, temp_currency, + temp_input_price, temp_output_price, temp_price_source, updated_by + FROM price ORDER BY created_at DESC`) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch prices"}) + return + } + defer rows.Close() + + var prices []models.Price + for rows.Next() { + var price models.Price + if err := rows.Scan( + &price.ID, &price.Model, &price.BillingType, &price.ChannelType, &price.Currency, + &price.InputPrice, &price.OutputPrice, &price.PriceSource, &price.Status, + &price.CreatedAt, &price.UpdatedAt, &price.CreatedBy, + &price.TempModel, &price.TempBillingType, &price.TempChannelType, &price.TempCurrency, + &price.TempInputPrice, &price.TempOutputPrice, &price.TempPriceSource, &price.UpdatedBy); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan price"}) + return + } + prices = append(prices, price) + } + + c.JSON(http.StatusOK, prices) +} + +func CreatePrice(c *gin.Context) { + var price models.Price + if err := c.ShouldBindJSON(&price); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // 验证供应商ID是否存在 + db := c.MustGet("db").(*sql.DB) + var providerExists bool + err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM provider WHERE id = ?)", price.ChannelType).Scan(&providerExists) + if err != nil || !providerExists { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid provider ID"}) + return + } + + now := time.Now() + result, err := db.Exec(` + INSERT INTO price (model, billing_type, channel_type, currency, input_price, output_price, + price_source, status, created_by, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)`, + price.Model, price.BillingType, price.ChannelType, price.Currency, + price.InputPrice, price.OutputPrice, price.PriceSource, price.CreatedBy, + now, now) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create price"}) + return + } + + id, _ := result.LastInsertId() + price.ID = uint(id) + price.Status = "pending" + price.CreatedAt = now + price.UpdatedAt = now + + c.JSON(http.StatusCreated, price) +} + +func UpdatePriceStatus(c *gin.Context) { + id := c.Param("id") + var input struct { + Status string `json:"status" binding:"required,oneof=approved rejected"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + db := c.MustGet("db").(*sql.DB) + now := time.Now() + + if input.Status == "approved" { + // 如果是批准,将临时字段的值更新到正式字段 + _, err := db.Exec(` + UPDATE price + SET model = COALESCE(temp_model, model), + billing_type = COALESCE(temp_billing_type, billing_type), + channel_type = COALESCE(temp_channel_type, channel_type), + currency = COALESCE(temp_currency, currency), + input_price = COALESCE(temp_input_price, input_price), + output_price = COALESCE(temp_output_price, output_price), + price_source = COALESCE(temp_price_source, price_source), + status = ?, + updated_at = ?, + temp_model = NULL, + temp_billing_type = NULL, + temp_channel_type = NULL, + temp_currency = NULL, + temp_input_price = NULL, + temp_output_price = NULL, + temp_price_source = NULL, + updated_by = NULL + WHERE id = ?`, input.Status, now, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update price status"}) + return + } + } else { + // 如果是拒绝,清除临时字段 + _, err := db.Exec(` + UPDATE price + SET status = ?, + updated_at = ?, + temp_model = NULL, + temp_billing_type = NULL, + temp_channel_type = NULL, + temp_currency = NULL, + temp_input_price = NULL, + temp_output_price = NULL, + temp_price_source = NULL, + updated_by = NULL + WHERE id = ?`, input.Status, now, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update price status"}) + return + } + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Status updated successfully", + "status": input.Status, + "updated_at": now, + }) +} + +// UpdatePrice 更新价格 +func UpdatePrice(c *gin.Context) { + id := c.Param("id") + var price models.Price + if err := c.ShouldBindJSON(&price); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // 验证供应商ID是否存在 + db := c.MustGet("db").(*sql.DB) + var providerExists bool + err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM provider WHERE id = ?)", price.ChannelType).Scan(&providerExists) + if err != nil || !providerExists { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid provider ID"}) + return + } + + // 获取当前用户 + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"}) + return + } + currentUser := user.(*models.User) + + now := time.Now() + // 将新的价格信息存储到临时字段 + _, err = db.Exec(` + UPDATE price + SET temp_model = ?, temp_billing_type = ?, temp_channel_type = ?, temp_currency = ?, + temp_input_price = ?, temp_output_price = ?, temp_price_source = ?, + updated_by = ?, updated_at = ?, status = 'pending' + WHERE id = ?`, + price.Model, price.BillingType, price.ChannelType, price.Currency, + price.InputPrice, price.OutputPrice, price.PriceSource, + currentUser.Username, now, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update price"}) + return + } + + // 获取更新后的价格信息 + err = db.QueryRow(` + SELECT id, model, billing_type, channel_type, currency, input_price, output_price, + price_source, status, created_at, updated_at, created_by, + temp_model, temp_billing_type, temp_channel_type, temp_currency, + temp_input_price, temp_output_price, temp_price_source, updated_by + FROM price WHERE id = ?`, id).Scan( + &price.ID, &price.Model, &price.BillingType, &price.ChannelType, &price.Currency, + &price.InputPrice, &price.OutputPrice, &price.PriceSource, &price.Status, + &price.CreatedAt, &price.UpdatedAt, &price.CreatedBy, + &price.TempModel, &price.TempBillingType, &price.TempChannelType, &price.TempCurrency, + &price.TempInputPrice, &price.TempOutputPrice, &price.TempPriceSource, &price.UpdatedBy) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get updated price"}) + return + } + + c.JSON(http.StatusOK, price) +} + +// DeletePrice 删除价格 +func DeletePrice(c *gin.Context) { + id := c.Param("id") + db := c.MustGet("db").(*sql.DB) + + _, err := db.Exec("DELETE FROM price WHERE id = ?", id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete price"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Price deleted successfully"}) +} + +// PriceRate 价格倍率结构 +type PriceRate struct { + Model string `json:"model"` + Type string `json:"type"` + ChannelType uint `json:"channel_type"` + Input float64 `json:"input"` + Output float64 `json:"output"` +} + +// GetPriceRates 获取价格倍率 +func GetPriceRates(c *gin.Context) { + db := c.MustGet("db").(*sql.DB) + rows, err := db.Query(` + SELECT model, billing_type, channel_type, + CASE + WHEN currency = 'USD' THEN input_price / 2 + ELSE input_price / 14 + END as input_rate, + CASE + WHEN currency = 'USD' THEN output_price / 2 + ELSE output_price / 14 + END as output_rate + FROM price + WHERE status = 'approved' + ORDER BY model, channel_type`) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch price rates"}) + return + } + defer rows.Close() + + var rates []PriceRate + for rows.Next() { + var rate PriceRate + if err := rows.Scan( + &rate.Model, + &rate.Type, + &rate.ChannelType, + &rate.Input, + &rate.Output); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan price rate"}) + return + } + rates = append(rates, rate) + } + + c.JSON(http.StatusOK, rates) +} diff --git a/backend/handlers/providers.go b/backend/handlers/providers.go new file mode 100644 index 0000000..8b462c6 --- /dev/null +++ b/backend/handlers/providers.go @@ -0,0 +1,188 @@ +package handlers + +import ( + "database/sql" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + + "aimodels-prices/models" +) + +// GetProviders 获取所有供应商 +func GetProviders(c *gin.Context) { + db := c.MustGet("db").(*sql.DB) + rows, err := db.Query(` + SELECT id, name, icon, created_at, updated_at, created_by + FROM provider ORDER BY id`) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch providers"}) + return + } + defer rows.Close() + + var providers []models.Provider + for rows.Next() { + var provider models.Provider + if err := rows.Scan( + &provider.ID, &provider.Name, &provider.Icon, + &provider.CreatedAt, &provider.UpdatedAt, &provider.CreatedBy); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan provider"}) + return + } + providers = append(providers, provider) + } + + c.JSON(http.StatusOK, providers) +} + +// CreateProvider 创建供应商 +func CreateProvider(c *gin.Context) { + var provider models.Provider + if err := c.ShouldBindJSON(&provider); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // 检查ID是否已存在 + db := c.MustGet("db").(*sql.DB) + var existingID int + err := db.QueryRow("SELECT id FROM provider WHERE id = ?", provider.ID).Scan(&existingID) + if err != sql.ErrNoRows { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID already exists"}) + return + } + + // 获取当前用户 + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"}) + return + } + currentUser := user.(*models.User) + + now := time.Now() + _, err = db.Exec(` + INSERT INTO provider (id, name, icon, created_at, updated_at, created_by) + VALUES (?, ?, ?, ?, ?, ?)`, + provider.ID, provider.Name, provider.Icon, now, now, currentUser.Username) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create provider"}) + return + } + + provider.CreatedAt = now + provider.UpdatedAt = now + provider.CreatedBy = currentUser.Username + + c.JSON(http.StatusCreated, provider) +} + +// UpdateProvider 更新供应商 +func UpdateProvider(c *gin.Context) { + id, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) + return + } + + var provider models.Provider + if err := c.ShouldBindJSON(&provider); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + db := c.MustGet("db").(*sql.DB) + now := time.Now() + _, err = db.Exec(` + UPDATE provider + SET name = ?, icon = ?, updated_at = ? + WHERE id = ?`, + provider.Name, provider.Icon, now, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update provider"}) + return + } + + // 获取更新后的供应商信息 + err = db.QueryRow(` + SELECT id, name, icon, created_at, updated_at, created_by + FROM provider WHERE id = ?`, id).Scan( + &provider.ID, &provider.Name, &provider.Icon, + &provider.CreatedAt, &provider.UpdatedAt, &provider.CreatedBy) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get updated provider"}) + return + } + + c.JSON(http.StatusOK, provider) +} + +// UpdateProviderStatus 更新供应商状态 +func UpdateProviderStatus(c *gin.Context) { + id := c.Param("id") + var input struct { + Status string `json:"status" binding:"required,oneof=approved rejected"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + db := c.MustGet("db").(*sql.DB) + now := time.Now() + + if input.Status == "approved" { + // 如果是批准,将临时字段的值更新到正式字段 + _, err := db.Exec(` + UPDATE provider + SET name = COALESCE(temp_name, name), + icon = COALESCE(temp_icon, icon), + status = ?, + updated_at = ?, + temp_name = NULL, + temp_icon = NULL, + updated_by = NULL + WHERE id = ?`, input.Status, now, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update provider status"}) + return + } + } else { + // 如果是拒绝,清除临时字段 + _, err := db.Exec(` + UPDATE provider + SET status = ?, + updated_at = ?, + temp_name = NULL, + temp_icon = NULL, + updated_by = NULL + WHERE id = ?`, input.Status, now, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update provider status"}) + return + } + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Status updated successfully", + "status": input.Status, + "updated_at": now, + }) +} + +// DeleteProvider 删除供应商 +func DeleteProvider(c *gin.Context) { + id := c.Param("id") + db := c.MustGet("db").(*sql.DB) + _, err := db.Exec("DELETE FROM provider WHERE id = ?", id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete provider"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Provider deleted successfully"}) +} diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 0000000..c75bc93 --- /dev/null +++ b/backend/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "log" + + "github.com/gin-gonic/gin" + + "aimodels-prices/config" + "aimodels-prices/database" + "aimodels-prices/handlers" + "aimodels-prices/middleware" +) + +func main() { + // 加载配置 + cfg, err := config.LoadConfig() + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + // 初始化数据库 + if err := database.InitDB(cfg.DBPath); err != nil { + log.Fatalf("Failed to initialize database: %v", err) + } + defer database.DB.Close() + + // 设置gin模式 + if gin.Mode() == gin.ReleaseMode { + gin.SetMode(gin.ReleaseMode) + } + + r := gin.Default() + + // 注入数据库 + r.Use(func(c *gin.Context) { + c.Set("db", database.DB) + c.Next() + }) + + // CORS中间件 + r.Use(func(c *gin.Context) { + origin := c.Request.Header.Get("Origin") + if origin != "" { + c.Writer.Header().Set("Access-Control-Allow-Origin", origin) + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") + } + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + }) + + // API路由组 + api := r.Group("/api") + { + // 价格相关路由 + prices := api.Group("/prices") + { + prices.GET("", handlers.GetPrices) + prices.GET("/rates", handlers.GetPriceRates) + prices.POST("", middleware.AuthRequired(), handlers.CreatePrice) + prices.PUT("/:id", middleware.AuthRequired(), handlers.UpdatePrice) + prices.DELETE("/:id", middleware.AuthRequired(), handlers.DeletePrice) + prices.PUT("/:id/status", middleware.AuthRequired(), middleware.AdminRequired(), handlers.UpdatePriceStatus) + } + + // 供应商相关路由 + providers := api.Group("/providers") + { + providers.GET("", handlers.GetProviders) + providers.POST("", middleware.AuthRequired(), handlers.CreateProvider) + providers.PUT("/:id", middleware.AuthRequired(), middleware.AdminRequired(), handlers.UpdateProvider) + providers.DELETE("/:id", middleware.AuthRequired(), middleware.AdminRequired(), handlers.DeleteProvider) + } + + // 认证相关路由 + auth := api.Group("/auth") + { + auth.GET("/status", handlers.GetAuthStatus) + auth.POST("/login", handlers.Login) + auth.POST("/logout", handlers.Logout) + auth.GET("/user", handlers.GetUser) + auth.GET("/callback", handlers.AuthCallback) + } + } + + // 启动服务器 + addr := fmt.Sprintf(":%s", cfg.ServerPort) + if err := r.Run(addr); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} diff --git a/backend/middleware/auth.go b/backend/middleware/auth.go new file mode 100644 index 0000000..b5fc917 --- /dev/null +++ b/backend/middleware/auth.go @@ -0,0 +1,97 @@ +package middleware + +import ( + "database/sql" + "net/http" + "time" + + "github.com/gin-gonic/gin" + + "aimodels-prices/models" +) + +func AuthRequired() gin.HandlerFunc { + return func(c *gin.Context) { + cookie, err := c.Cookie("session") + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Not logged in"}) + c.Abort() + return + } + + db := c.MustGet("db").(*sql.DB) + var session models.Session + err = db.QueryRow("SELECT id, user_id, expires_at, created_at, updated_at, deleted_at FROM session WHERE id = ?", cookie).Scan( + &session.ID, &session.UserID, &session.ExpiresAt, &session.CreatedAt, &session.UpdatedAt, &session.DeletedAt) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid session"}) + c.Abort() + return + } + + if session.ExpiresAt.Before(time.Now()) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Session expired"}) + c.Abort() + return + } + + user, err := session.GetUser(db) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user"}) + c.Abort() + return + } + + c.Set("user", user) + c.Next() + } +} + +func AdminRequired() gin.HandlerFunc { + return func(c *gin.Context) { + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Not logged in"}) + c.Abort() + return + } + + if u, ok := user.(*models.User); !ok || u.Role != "admin" { + c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"}) + c.Abort() + return + } + + c.Next() + } +} + +func RequireAuth() gin.HandlerFunc { + return func(c *gin.Context) { + _, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) + c.Abort() + return + } + c.Next() + } +} + +func RequireAdmin() gin.HandlerFunc { + return func(c *gin.Context) { + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) + c.Abort() + return + } + + if u, ok := user.(*models.User); !ok || u.Role != "admin" { + c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"}) + c.Abort() + return + } + c.Next() + } +} diff --git a/backend/middleware/db.go b/backend/middleware/db.go new file mode 100644 index 0000000..10fad7f --- /dev/null +++ b/backend/middleware/db.go @@ -0,0 +1,14 @@ +package middleware + +import ( + "aimodels-prices/database" + + "github.com/gin-gonic/gin" +) + +func Database() gin.HandlerFunc { + return func(c *gin.Context) { + c.Set("db", database.DB) + c.Next() + } +} diff --git a/backend/models/price.go b/backend/models/price.go new file mode 100644 index 0000000..8e46e61 --- /dev/null +++ b/backend/models/price.go @@ -0,0 +1,57 @@ +package models + +import ( + "time" +) + +type Price struct { + ID uint `json:"id"` + Model string `json:"model"` + BillingType string `json:"billing_type"` // tokens or times + ChannelType string `json:"channel_type"` + Currency string `json:"currency"` // USD or CNY + InputPrice float64 `json:"input_price"` + OutputPrice float64 `json:"output_price"` + PriceSource string `json:"price_source"` + Status string `json:"status"` // pending, approved, rejected + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CreatedBy string `json:"created_by"` + // 临时字段,用于存储待审核的更新 + TempModel *string `json:"temp_model,omitempty"` + TempBillingType *string `json:"temp_billing_type,omitempty"` + TempChannelType *string `json:"temp_channel_type,omitempty"` + TempCurrency *string `json:"temp_currency,omitempty"` + TempInputPrice *float64 `json:"temp_input_price,omitempty"` + TempOutputPrice *float64 `json:"temp_output_price,omitempty"` + TempPriceSource *string `json:"temp_price_source,omitempty"` + UpdatedBy *string `json:"updated_by,omitempty"` +} + +// CreatePriceTableSQL 返回创建价格表的 SQL +func CreatePriceTableSQL() string { + return ` + CREATE TABLE IF NOT EXISTS price ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + model TEXT NOT NULL, + billing_type TEXT NOT NULL, + channel_type TEXT NOT NULL, + currency TEXT NOT NULL, + input_price REAL NOT NULL, + output_price REAL NOT NULL, + price_source TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_by TEXT NOT NULL, + temp_model TEXT, + temp_billing_type TEXT, + temp_channel_type TEXT, + temp_currency TEXT, + temp_input_price REAL, + temp_output_price REAL, + temp_price_source TEXT, + updated_by TEXT, + FOREIGN KEY (channel_type) REFERENCES provider(id) + )` +} diff --git a/backend/models/provider.go b/backend/models/provider.go new file mode 100644 index 0000000..93b29b7 --- /dev/null +++ b/backend/models/provider.go @@ -0,0 +1,25 @@ +package models + +import "time" + +type Provider struct { + ID uint `json:"id"` + Name string `json:"name"` + Icon string `json:"icon"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CreatedBy string `json:"created_by"` +} + +// CreateProviderTableSQL 返回创建供应商表的 SQL +func CreateProviderTableSQL() string { + return ` + CREATE TABLE IF NOT EXISTS provider ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + icon TEXT, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_by TEXT NOT NULL + )` +} diff --git a/backend/models/user.go b/backend/models/user.go new file mode 100644 index 0000000..7be300b --- /dev/null +++ b/backend/models/user.go @@ -0,0 +1,64 @@ +package models + +import ( + "database/sql" + "time" +) + +type User struct { + ID uint `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Role string `json:"role"` // admin or user + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt *time.Time `json:"deleted_at,omitempty"` +} + +type Session struct { + ID string `json:"id"` + UserID uint `json:"user_id"` + ExpiresAt time.Time `json:"expires_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt *time.Time `json:"deleted_at,omitempty"` +} + +// CreateTableSQL 返回创建用户表的 SQL +func CreateUserTableSQL() string { + return ` + CREATE TABLE IF NOT EXISTS user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + email TEXT UNIQUE NOT NULL, + role TEXT NOT NULL DEFAULT 'user', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at DATETIME + )` +} + +// CreateSessionTableSQL 返回创建会话表的 SQL +func CreateSessionTableSQL() string { + return ` + CREATE TABLE IF NOT EXISTS session ( + id TEXT PRIMARY KEY, + user_id INTEGER NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at DATETIME, + FOREIGN KEY (user_id) REFERENCES user(id) + )` +} + +// GetUser 获取会话关联的用户 +func (s *Session) GetUser(db *sql.DB) (*User, error) { + var user User + err := db.QueryRow("SELECT id, username, email, role, created_at, updated_at, deleted_at FROM user WHERE id = ?", s.UserID).Scan( + &user.ID, &user.Username, &user.Email, &user.Role, &user.CreatedAt, &user.UpdatedAt, &user.DeletedAt) + if err != nil { + return nil, err + } + return &user, nil +} diff --git a/backend/router/router.go b/backend/router/router.go new file mode 100644 index 0000000..23f8654 --- /dev/null +++ b/backend/router/router.go @@ -0,0 +1,37 @@ +package router + +import ( + "github.com/gin-gonic/gin" + + "aimodels-prices/handlers" + "aimodels-prices/middleware" +) + +// SetupRouter 设置路由 +func SetupRouter() *gin.Engine { + r := gin.Default() + + // 添加数据库中间件 + r.Use(middleware.Database()) + + // 认证相关路由 + auth := r.Group("/auth") + { + auth.GET("/status", handlers.GetAuthStatus) + auth.POST("/login", handlers.Login) + auth.POST("/logout", handlers.Logout) + } + + // 供应商相关路由 + providers := r.Group("/providers") + { + providers.GET("", handlers.GetProviders) + providers.Use(middleware.RequireAuth()) + providers.Use(middleware.RequireAdmin()) + providers.POST("", handlers.CreateProvider) + providers.PUT("/:id", handlers.UpdateProvider) + providers.DELETE("/:id", handlers.DeleteProvider) + } + + return r +} diff --git a/deno.json b/deno.json deleted file mode 100644 index e313f2a..0000000 --- a/deno.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"], - "strict": true - }, - "importMap": "./import_map.json", - "tasks": { - "start": "deno run --allow-net --allow-read --allow-env main.ts" - } -} \ No newline at end of file diff --git a/deno.jsonc b/deno.jsonc deleted file mode 100644 index 29c06ef..0000000 --- a/deno.jsonc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "lib": ["dom", "dom.iterable", "deno.ns"], - "strict": true, - "types": ["https://deno.land/x/types/deno.ns.d.ts"] - }, - "importMap": "import_map.json", - "tasks": { - "start": "deno run --allow-net --allow-read --allow-env main.ts", - "dev": "deno run --watch --allow-net --allow-read --allow-env main.ts" - } -} \ No newline at end of file diff --git a/deps.ts b/deps.ts deleted file mode 100644 index 6a7b5bb..0000000 --- a/deps.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { serve } from "https://deno.land/std@0.208.0/http/server.ts"; -export { crypto } from "https://deno.land/std@0.208.0/crypto/mod.ts"; -export { decode as base64Decode } from "https://deno.land/std@0.208.0/encoding/base64.ts"; \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..3caead1 Binary files /dev/null and b/favicon.ico differ diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..b4b954d --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + + AI模型价格 + + + +
+ + + \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..c9eb3ce --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1482 @@ +{ + "name": "aimodels-prices-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aimodels-prices-frontend", + "version": "0.1.0", + "dependencies": { + "axios": "^1.6.7", + "element-plus": "^2.5.3", + "vue": "^3.4.15", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.3", + "vite": "^5.0.12" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", + "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", + "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", + "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", + "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", + "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", + "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", + "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", + "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", + "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", + "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", + "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", + "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", + "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", + "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", + "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", + "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", + "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", + "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", + "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", + "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/element-plus": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.9.4.tgz", + "integrity": "sha512-sGnW0wd9zf6lEGixXV2gfwx3X6VTMkP52qTkX7zbURJ2oariyslrKTBh2txt1sdn1pUvj2l0KY3OfSXoZGmDOw==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.13", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", + "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.34.6", + "@rollup/rollup-android-arm64": "4.34.6", + "@rollup/rollup-darwin-arm64": "4.34.6", + "@rollup/rollup-darwin-x64": "4.34.6", + "@rollup/rollup-freebsd-arm64": "4.34.6", + "@rollup/rollup-freebsd-x64": "4.34.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", + "@rollup/rollup-linux-arm-musleabihf": "4.34.6", + "@rollup/rollup-linux-arm64-gnu": "4.34.6", + "@rollup/rollup-linux-arm64-musl": "4.34.6", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", + "@rollup/rollup-linux-riscv64-gnu": "4.34.6", + "@rollup/rollup-linux-s390x-gnu": "4.34.6", + "@rollup/rollup-linux-x64-gnu": "4.34.6", + "@rollup/rollup-linux-x64-musl": "4.34.6", + "@rollup/rollup-win32-arm64-msvc": "4.34.6", + "@rollup/rollup-win32-ia32-msvc": "4.34.6", + "@rollup/rollup-win32-x64-msvc": "4.34.6", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", + "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..6d42add --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "aimodels-prices-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.6.7", + "element-plus": "^2.5.3", + "vue": "^3.4.15", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.3", + "vite": "^5.0.12" + } +} \ No newline at end of file diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..3caead1 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..26141e5 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,171 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..612f8d1 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,15 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import App from './App.vue' +import router from './router' +import axios from 'axios' + +// 配置 axios +axios.defaults.withCredentials = true + +const app = createApp(App) + +app.use(ElementPlus) +app.use(router) +app.mount('#app') \ No newline at end of file diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..745943e --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,33 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Prices from '../views/Prices.vue' +import Providers from '../views/Providers.vue' +import Login from '../views/Login.vue' +import Home from '../views/Home.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: Home + }, + { + path: '/prices', + name: 'prices', + component: Prices + }, + { + path: '/providers', + name: 'providers', + component: Providers + }, + { + path: '/login', + name: 'login', + component: Login + } + ] +}) + +export default router \ No newline at end of file diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue new file mode 100644 index 0000000..c24eeac --- /dev/null +++ b/frontend/src/views/Home.vue @@ -0,0 +1,236 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue new file mode 100644 index 0000000..8eacecb --- /dev/null +++ b/frontend/src/views/Login.vue @@ -0,0 +1,62 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Prices.vue b/frontend/src/views/Prices.vue new file mode 100644 index 0000000..24c50b5 --- /dev/null +++ b/frontend/src/views/Prices.vue @@ -0,0 +1,483 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Providers.vue b/frontend/src/views/Providers.vue new file mode 100644 index 0000000..de9a561 --- /dev/null +++ b/frontend/src/views/Providers.vue @@ -0,0 +1,217 @@ + + + + + \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..d6da2d6 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + publicDir: 'public', + server: { + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true + } + } + } +}) \ No newline at end of file diff --git a/import_map.json b/import_map.json deleted file mode 100644 index daf44f0..0000000 --- a/import_map.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "imports": { - "std/": "https://deno.land/std@0.220.1/", - "deno/": "https://deno.land/x/deno@v1.40.5/" - } -} \ No newline at end of file diff --git a/main.ts b/main.ts deleted file mode 100644 index 797787e..0000000 --- a/main.ts +++ /dev/null @@ -1,1261 +0,0 @@ -/// - -import { serve, crypto, base64Decode } from "./deps.ts"; - -// 声明 Deno 命名空间 -declare namespace Deno { - interface Kv { - get(key: unknown[]): Promise<{ value: any }>; - set(key: unknown[], value: unknown, options?: { expireIn?: number }): Promise; - delete(key: unknown[]): Promise; - } - - interface Env { - get(key: string): string | undefined; - } - - const env: Env; - const exit: (code: number) => never; - const openKv: () => Promise; -} - -// 类型定义 -interface Vendor { - id: number; - name: string; - icon: string; -} - -interface VendorResponse { - data: { - [key: string]: Vendor; - }; -} - -interface Price { - id?: string; - model: string; - billing_type: 'tokens' | 'times'; - channel_type: number; - currency: 'CNY' | 'USD'; - input_price: number; - output_price: number; - input_ratio: number; - output_ratio: number; - price_source: string; - status: 'pending' | 'approved' | 'rejected'; - created_by: string; - created_at: string; - reviewed_by?: string; - reviewed_at?: string; -} - -// 声明全局变量 -declare const vendors: { [key: string]: Vendor }; - -// 缓存供应商数据 -let vendorsCache: VendorResponse | null = null; -let vendorsCacheTime: number = 0; -const CACHE_DURATION = 1000 * 60 * 5; // 5分钟缓存 - -// 初始化 KV 存储 -let kv: Deno.Kv; - -try { - kv = await Deno.openKv(); -} catch (error) { - console.error('初始化 KV 存储失败:', error); - Deno.exit(1); -} - -// 获取供应商数据 -async function getVendors(): Promise { - const now = Date.now(); - if (vendorsCache && (now - vendorsCacheTime) < CACHE_DURATION) { - return vendorsCache; - } - - try { - const response = await fetch('https://oapi.czl.net/api/ownedby'); - const data = await response.json() as VendorResponse; - vendorsCache = data; - vendorsCacheTime = now; - return data; - } catch (error) { - console.error('获取供应商数据失败:', error); - throw new Error('获取供应商数据失败'); - } -} - -// 计算倍率 -function calculateRatio(price: number, currency: 'CNY' | 'USD'): number { - return currency === 'USD' ? price / 2 : price / 14; -} - -// 修改验证价格数据函数 -function validatePrice(data: any): string | null { - console.log('验证数据:', data); // 添加日志 - - if (!data.model || !data.billing_type || !data.channel_type || - !data.currency || data.input_price === undefined || data.output_price === undefined || - !data.price_source) { - return "所有字段都是必需的"; - } - - if (data.billing_type !== 'tokens' && data.billing_type !== 'times') { - return "计费类型必须是 tokens 或 times"; - } - - if (data.currency !== 'CNY' && data.currency !== 'USD') { - return "币种必须是 CNY 或 USD"; - } - - const channel_type = Number(data.channel_type); - const input_price = Number(data.input_price); - const output_price = Number(data.output_price); - - if (isNaN(channel_type) || isNaN(input_price) || isNaN(output_price)) { - return "价格和供应商ID必须是数字"; - } - - if (channel_type < 0 || input_price < 0 || output_price < 0) { - return "价格和供应商ID不能为负数"; - } - - return null; -} - -// 添加 Discourse SSO 配置 -const DISCOURSE_URL = Deno.env.get('DISCOURSE_URL') || 'https://discourse.czl.net'; -const DISCOURSE_SSO_SECRET = Deno.env.get('DISCOURSE_SSO_SECRET'); - -// 验证必需的环境变量 -if (!DISCOURSE_SSO_SECRET) { - console.error('错误: 必须设置 DISCOURSE_SSO_SECRET 环境变量'); - Deno.exit(1); -} - -// 添加认证相关函数 -async function verifyDiscourseSSO(request: Request): Promise { - const cookie = request.headers.get('cookie'); - if (!cookie) return null; - - const sessionMatch = cookie.match(/session=([^;]+)/); - if (!sessionMatch) return null; - - const sessionId = sessionMatch[1]; - const session = await kv.get(['sessions', sessionId]); - - if (!session.value) return null; - return session.value.username; -} - -// 修改 generateSSO 函数 -async function generateSSO(returnUrl: string): Promise { - const encoder = new TextEncoder(); - const nonce = crypto.randomUUID(); - const rawPayload = `nonce=${nonce}&return_sso_url=${encodeURIComponent(returnUrl)}`; - const base64Payload = btoa(rawPayload); - const key = await crypto.subtle.importKey( - "raw", - encoder.encode(DISCOURSE_SSO_SECRET), - { name: "HMAC", hash: "SHA-256" }, - false, - ["sign"] - ); - const signature = await crypto.subtle.sign( - "HMAC", - key, - encoder.encode(base64Payload) - ); - const sig = Array.from(new Uint8Array(signature)) - .map(b => b.toString(16).padStart(2, '0')) - .join(''); - - // 存储 nonce 用于验证回调 - await kv.set(['sso_nonce', nonce], { - return_url: returnUrl, - created_at: new Date().toISOString() - }, { expireIn: 5 * 60 * 1000 }); // 5分钟过期 - - return `${DISCOURSE_URL}/session/sso_provider?sso=${encodeURIComponent(base64Payload)}&sig=${sig}`; -} - -// HTML 页面 -const html = ` - - - AI Models Price API - - - - - - -
- - -
- 价格json: - https://aimodels-price.deno.dev/api/prices - -
- -
-
- - -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
模型名称计费类型供应商币种输入价格(M)输出价格(M)输入倍率输出倍率价格依据状态操作
-
- 加载中... -
-
-
- -
-
状态说明:
-
- 待审核 - 新提交的价格记录 -
-
- 已通过 - 管理员审核通过 -
-
- 已拒绝 - 管理员拒绝 -
-
-
-
-
- -
-
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
-
-
-
-
-
- - -
- -
-
- - - - -`; - -// 读取价格数据 -async function readPrices(): Promise { - try { - const prices = await kv.get(["prices"]); - return prices.value || []; - } catch (error) { - console.error('读取价格数据失败:', error); - return []; - } -} - -// 写入价格数据 -async function writePrices(prices: Price[]): Promise { - try { - await kv.set(["prices"], prices); - } catch (error) { - console.error('写入价格数据失败:', error); - throw new Error('写入价格数据失败'); - } -} - -// 修改验证函数 -function validateData(data: any): string | null { - if (!data.model || !data.type || data.channel_type === undefined || data.input === undefined || data.output === undefined) { - return "所有字段都是必需的"; - } - - // 确保数字字段是数字类型 - const channel_type = Number(data.channel_type); - const input = Number(data.input); - const output = Number(data.output); - - if (isNaN(channel_type) || isNaN(input) || isNaN(output)) { - return "数字字段格式无效"; - } - - // 验证数字范围(允许等于0) - if (channel_type < 0 || input < 0 || output < 0) { - return "数字不能小于0"; - } - - return null; -} - -// 修改处理函数 -async function handler(req: Request): Promise { - const headers = { - "Access-Control-Allow-Origin": req.headers.get("origin") || "*", - "Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, DELETE", - "Access-Control-Allow-Headers": "Content-Type, Cookie, Authorization, Accept", - "Access-Control-Allow-Credentials": "true", - "Access-Control-Max-Age": "86400" - }; - - const jsonHeaders = { - ...headers, - "Content-Type": "application/json" - }; - - const htmlHeaders = { - ...headers, - "Content-Type": "text/html; charset=utf-8" - }; - - try { - const url = new URL(req.url); - console.log('Received request:', req.method, url.pathname); - - // 处理预检请求 - if (req.method === "OPTIONS") { - return new Response(null, { - status: 204, - headers - }); - } - - // 获取价格列表 - if (url.pathname === "/api/prices" && req.method === "GET") { - try { - console.log('Reading prices from KV store...'); - const prices = await readPrices(); - console.log('Prices read successfully:', prices.length); - return new Response(JSON.stringify(prices), { - headers: jsonHeaders - }); - } catch (error) { - console.error('获取价格列表失败:', error); - return new Response(JSON.stringify({ - error: "获取价格列表失败", - details: error.message - }), { - status: 500, - headers: jsonHeaders - }); - } - } - - // 认证状态检查 - if (url.pathname === "/api/auth/status") { - try { - const username = await verifyDiscourseSSO(req); - return new Response(JSON.stringify({ - authenticated: !!username, - user: username - }), { headers: jsonHeaders }); - } catch (error) { - console.error('验证用户状态失败:', error); - return new Response(JSON.stringify({ - error: "验证用户状态失败", - details: error.message - }), { - status: 500, - headers: jsonHeaders - }); - } - } - - // 登录处理 - if (url.pathname === "/api/auth/login") { - const params = new URLSearchParams(url.search); - const returnUrl = params.get('return_url'); - if (!returnUrl) { - return new Response(JSON.stringify({ error: "缺少 return_url 参数" }), { - status: 400, - headers: jsonHeaders - }); - } - - const ssoUrl = await generateSSO(returnUrl); - return new Response(null, { - status: 302, - headers: { - ...headers, - "Location": ssoUrl - } - }); - } - - // SSO 回调处理 - if (url.pathname === "/auth/callback") { - const params = new URLSearchParams(url.search); - const sso = params.get('sso'); - const sig = params.get('sig'); - - if (!sso || !sig) { - return new Response("Invalid SSO parameters", { - status: 400, - headers: { - "Content-Type": "text/plain", - ...headers - } - }); - } - - try { - // 验证签名 - const key = await crypto.subtle.importKey( - "raw", - new TextEncoder().encode(DISCOURSE_SSO_SECRET), - { name: "HMAC", hash: "SHA-256" }, - false, - ["sign"] - ); - const signature = await crypto.subtle.sign( - "HMAC", - key, - new TextEncoder().encode(sso) - ); - const expectedSig = Array.from(new Uint8Array(signature)) - .map(b => b.toString(16).padStart(2, '0')) - .join(''); - - if (sig !== expectedSig) { - throw new Error('Invalid signature'); - } - - // 解码 payload - const payload = atob(sso); - const payloadParams = new URLSearchParams(payload); - - // 验证 nonce - const nonce = payloadParams.get('nonce'); - if (!nonce) { - throw new Error('Missing nonce'); - } - - const nonceData = await kv.get(['sso_nonce', nonce]); - if (!nonceData.value) { - throw new Error('Invalid or expired nonce'); - } - - // 删除已使用的 nonce - await kv.delete(['sso_nonce', nonce]); - - const username = payloadParams.get('username'); - if (!username) { - throw new Error('Missing username'); - } - - // 设置 session cookie - const sessionId = crypto.randomUUID(); - await kv.set(['sessions', sessionId], { - username, - created_at: new Date().toISOString() - }, { expireIn: 24 * 60 * 60 * 1000 }); // 24小时过期 - - return new Response(null, { - status: 302, - headers: { - ...headers, - "Location": "/", - "Set-Cookie": `session=${sessionId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400` - } - }); - } catch (error) { - console.error('SSO 回调处理失败:', error); - return new Response("SSO verification failed: " + error.message, { - status: 400, - headers: { - "Content-Type": "text/plain", - ...headers - } - }); - } - } - - // 登出处理 - if (url.pathname === "/api/auth/logout" && req.method === "POST") { - const cookie = req.headers.get('cookie'); - if (cookie) { - const sessionMatch = cookie.match(/session=([^;]+)/); - if (sessionMatch) { - const sessionId = sessionMatch[1]; - await kv.delete(['sessions', sessionId]); - } - } - - return new Response(JSON.stringify({ success: true }), { - headers: { - ...jsonHeaders, - "Set-Cookie": "session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0" - } - }); - } - - // 价格审核 - if (url.pathname.match(/^\/api\/prices\/\d+\/review$/)) { - const username = await verifyDiscourseSSO(req); - if (!username || username !== 'wood') { - return new Response(JSON.stringify({ error: "未授权" }), { - status: 403, - headers: jsonHeaders - }); - } - - if (req.method === "POST") { - try { - const id = url.pathname.split('/')[3]; - const { status } = await req.json(); - - if (status !== 'approved' && status !== 'rejected') { - throw new Error("无效的状态"); - } - - const prices = await readPrices(); - const priceIndex = prices.findIndex(p => p.id === id); - - if (priceIndex === -1) { - throw new Error("价格记录不存在"); - } - - prices[priceIndex].status = status; - prices[priceIndex].reviewed_by = username; - prices[priceIndex].reviewed_at = new Date().toISOString(); - - await writePrices(prices); - - return new Response(JSON.stringify({ success: true }), { - headers: jsonHeaders - }); - } catch (error) { - return new Response(JSON.stringify({ - error: error.message || "审核失败" - }), { - status: 400, - headers: jsonHeaders - }); - } - } - } - - // 提交新价格 - if (url.pathname === "/api/prices" && req.method === "POST") { - const username = await verifyDiscourseSSO(req); - if (!username) { - return new Response(JSON.stringify({ error: "请先登录" }), { - status: 401, - headers: jsonHeaders - }); - } - - try { - let rawData; - const contentType = req.headers.get("content-type") || ""; - - if (contentType.includes("application/json")) { - rawData = await req.json(); - } else if (contentType.includes("application/x-www-form-urlencoded")) { - const formData = await req.formData(); - rawData = {}; - for (const [key, value] of formData.entries()) { - rawData[key] = value; - } - } else { - throw new Error("不支持的内容类型"); - } - - console.log('接收到的数据:', rawData); // 添加日志 - - // 处理数据 - const newPrice: Price = { - model: String(rawData.model).trim(), - billing_type: rawData.billing_type as 'tokens' | 'times', - channel_type: Number(rawData.channel_type), - currency: rawData.currency as 'CNY' | 'USD', - input_price: Number(rawData.input_price), - output_price: Number(rawData.output_price), - input_ratio: calculateRatio(Number(rawData.input_price), rawData.currency as 'CNY' | 'USD'), - output_ratio: calculateRatio(Number(rawData.output_price), rawData.currency as 'CNY' | 'USD'), - price_source: String(rawData.price_source), - status: 'pending', - created_by: username, - created_at: new Date().toISOString() - }; - - console.log('处理后的数据:', newPrice); // 添加日志 - - // 验证数据 - const error = validatePrice(newPrice); - if (error) { - return new Response(JSON.stringify({ error }), { - status: 400, - headers: jsonHeaders - }); - } - - // 读取现有数据 - const prices = await readPrices(); - - // 生成唯一ID - newPrice.id = Date.now().toString(); - - // 添加新数据 - prices.push(newPrice); - - // 保存数据 - await writePrices(prices); - - return new Response(JSON.stringify({ - success: true, - data: newPrice - }), { - headers: jsonHeaders - }); - } catch (error) { - console.error("处理价格提交失败:", error); - return new Response(JSON.stringify({ - error: error.message, - details: "数据处理失败,请检查输入格式" - }), { - status: 500, - headers: jsonHeaders - }); - } - } - - // 提供静态页面 - if (url.pathname === "/" || url.pathname === "/index.html") { - return new Response(html, { - headers: htmlHeaders - }); - } - - return new Response("Not Found", { - status: 404, - headers: { - ...headers, - "Content-Type": "text/plain" - } - }); - } catch (error) { - console.error('处理请求失败:', error); - return new Response(JSON.stringify({ - error: "处理请求失败", - details: error.message - }), { - status: 500, - headers: jsonHeaders - }); - } -} - -// 启动服务器 -serve(handler); \ No newline at end of file diff --git a/prices.json b/prices.json index 3c8ac87..e6a9f8a 100644 --- a/prices.json +++ b/prices.json @@ -1,1374 +1 @@ -[ - { - "model": "babbage-002", - "type": "tokens", - "channel_type": 1, - "input": 0.2, - "output": 0.2 - }, - { - "model": "dall-e-2", - "type": "tokens", - "channel_type": 1, - "input": 8, - "output": 8 - }, - { - "model": "dall-e-3", - "type": "tokens", - "channel_type": 1, - "input": 20, - "output": 20 - }, - { - "model": "davinci-002", - "type": "tokens", - "channel_type": 1, - "input": 1, - "output": 1 - }, - { - "model": "gpt-3.5-turbo", - "type": "tokens", - "channel_type": 1, - "input": 0.25, - "output": 0.75 - }, - { - "model": "gpt-3.5-turbo-0125", - "type": "tokens", - "channel_type": 1, - "input": 0.25, - "output": 0.75 - }, - { - "model": "gpt-3.5-turbo-0301", - "type": "tokens", - "channel_type": 1, - "input": 0.75, - "output": 1 - }, - { - "model": "gpt-3.5-turbo-0613", - "type": "tokens", - "channel_type": 1, - "input": 0.75, - "output": 1 - }, - { - "model": "gpt-3.5-turbo-1106", - "type": "tokens", - "channel_type": 1, - "input": 0.5, - "output": 1 - }, - { - "model": "gpt-3.5-turbo-16k", - "type": "tokens", - "channel_type": 1, - "input": 1.5, - "output": 2 - }, - { - "model": "gpt-3.5-turbo-16k-0613", - "type": "tokens", - "channel_type": 1, - "input": 1.5, - "output": 2 - }, - { - "model": "gpt-3.5-turbo-instruct", - "type": "tokens", - "channel_type": 1, - "input": 0.75, - "output": 1 - }, - { - "model": "gpt-4", - "type": "tokens", - "channel_type": 1, - "input": 15, - "output": 30 - }, - { - "model": "gpt-4-turbo", - "type": "tokens", - "channel_type": 1, - "input": 5, - "output": 15 - }, - { - "model": "gpt-4-turbo-2024-04-09", - "type": "tokens", - "channel_type": 1, - "input": 5, - "output": 15 - }, - { - "model": "o1-mini", - "type": "tokens", - "channel_type": 1, - "input": 1.5, - "output": 6 - }, - { - "model": "o1-mini-2024-09-12", - "type": "tokens", - "channel_type": 1, - "input": 1.5, - "output": 6 - }, - { - "model": "o1-preview", - "type": "tokens", - "channel_type": 1, - "input": 7.5, - "output": 30 - }, - { - "model": "o1-preview-2024-09-12", - "type": "tokens", - "channel_type": 1, - "input": 7.5, - "output": 30 - }, - { - "model": "chatgpt-4o-latest", - "type": "tokens", - "channel_type": 1, - "input": 2.5, - "output": 7.5 - }, - { - "model": "gpt-4o", - "type": "tokens", - "channel_type": 1, - "input": 2.5, - "output": 7.5 - }, - { - "model": "gpt-4o-2024-05-13", - "type": "tokens", - "channel_type": 1, - "input": 2.5, - "output": 7.5 - }, - { - "model": "gpt-4o-2024-08-06", - "type": "tokens", - "channel_type": 1, - "input": 1.25, - "output": 5 - }, - { - "model": "gpt-4o-mini", - "type": "tokens", - "channel_type": 1, - "input": 0.075, - "output": 0.3 - }, - { - "model": "gpt-4o-mini-2024-07-18", - "type": "tokens", - "channel_type": 1, - "input": 0.075, - "output": 0.3 - }, - { - "model": "gpt-4o-realtime-preview", - "type": "tokens", - "channel_type": 1, - "input": 2.5, - "output": 10 - }, - { - "model": "gpt-4o-realtime-preview-2024-10-01", - "type": "tokens", - "channel_type": 1, - "input": 2.5, - "output": 10 - }, - { - "model": "gpt-4o-audio-preview", - "type": "tokens", - "channel_type": 1, - "input": 1.25, - "output": 5 - }, - { - "model": "gpt-4o-audio-preview-2024-10-01", - "type": "tokens", - "channel_type": 1, - "input": 1.25, - "output": 5 - }, - { - "model": "gpt-4-0125-preview", - "type": "tokens", - "channel_type": 1, - "input": 5, - "output": 15 - }, - { - "model": "gpt-4-0314", - "type": "tokens", - "channel_type": 1, - "input": 15, - "output": 30 - }, - { - "model": "gpt-4-0613", - "type": "tokens", - "channel_type": 1, - "input": 15, - "output": 30 - }, - { - "model": "gpt-4-1106-preview", - "type": "tokens", - "channel_type": 1, - "input": 5, - "output": 15 - }, - { - "model": "gpt-4-32k", - "type": "tokens", - "channel_type": 1, - "input": 30, - "output": 60 - }, - { - "model": "gpt-4-32k-0314", - "type": "tokens", - "channel_type": 1, - "input": 30, - "output": 60 - }, - { - "model": "gpt-4-32k-0613", - "type": "tokens", - "channel_type": 1, - "input": 30, - "output": 60 - }, - { - "model": "gpt-4-preview", - "type": "tokens", - "channel_type": 1, - "input": 5, - "output": 15 - }, - { - "model": "gpt-4-turbo-preview", - "type": "tokens", - "channel_type": 1, - "input": 5, - "output": 15 - }, - { - "model": "gpt-4-vision-preview", - "type": "tokens", - "channel_type": 1, - "input": 5, - "output": 15 - }, - { - "model": "text-embedding-3-large", - "type": "tokens", - "channel_type": 1, - "input": 0.065, - "output": 0.065 - }, - { - "model": "text-embedding-3-small", - "type": "tokens", - "channel_type": 1, - "input": 0.01, - "output": 0.01 - }, - { - "model": "text-embedding-ada-002", - "type": "tokens", - "channel_type": 1, - "input": 0.05, - "output": 0.05 - }, - { - "model": "text-moderation-latest", - "type": "tokens", - "channel_type": 1, - "input": 0.1, - "output": 0.1 - }, - { - "model": "text-moderation-stable", - "type": "tokens", - "channel_type": 1, - "input": 0.1, - "output": 0.1 - }, - { - "model": "tts-1", - "type": "tokens", - "channel_type": 1, - "input": 7.5, - "output": 7.5 - }, - { - "model": "tts-1-1106", - "type": "tokens", - "channel_type": 1, - "input": 7.5, - "output": 7.5 - }, - { - "model": "tts-1-hd", - "type": "tokens", - "channel_type": 1, - "input": 15, - "output": 15 - }, - { - "model": "tts-1-hd-1106", - "type": "tokens", - "channel_type": 1, - "input": 15, - "output": 15 - }, - { - "model": "whisper-1", - "type": "tokens", - "channel_type": 1, - "input": 15, - "output": 15 - }, - { - "model": "PaLM-2", - "type": "tokens", - "channel_type": 11, - "input": 1, - "output": 1 - }, - { - "model": "claude-2.0", - "type": "tokens", - "channel_type": 14, - "input": 4, - "output": 12 - }, - { - "model": "claude-2.1", - "type": "tokens", - "channel_type": 14, - "input": 4, - "output": 12 - }, - { - "model": "claude-3-haiku-20240307", - "type": "tokens", - "channel_type": 14, - "input": 0.125, - "output": 0.625 - }, - { - "model": "claude-3-5-haiku-20241022", - "type": "tokens", - "channel_type": 14, - "input": 0.5, - "output": 2.5 - }, - { - "model": "claude-3-5-haiku-latest", - "type": "tokens", - "channel_type": 14, - "input": 0.5, - "output": 2.5 - }, - { - "model": "claude-3-opus-20240229", - "type": "tokens", - "channel_type": 14, - "input": 7.5, - "output": 37.5 - }, - { - "model": "claude-3-sonnet-20240229", - "type": "tokens", - "channel_type": 14, - "input": 1.5, - "output": 7.5 - }, - { - "model": "claude-3-5-sonnet-20240620", - "type": "tokens", - "channel_type": 14, - "input": 1.5, - "output": 7.5 - }, - { - "model": "claude-3-5-sonnet-20241022", - "type": "tokens", - "channel_type": 14, - "input": 1.5, - "output": 7.5 - }, - { - "model": "claude-3-5-sonnet-latest", - "type": "tokens", - "channel_type": 14, - "input": 1.5, - "output": 7.5 - }, - { - "model": "claude-instant-1.2", - "type": "tokens", - "channel_type": 14, - "input": 0.4, - "output": 1.2 - }, - { - "model": "BLOOMZ-7B", - "type": "tokens", - "channel_type": 15, - "input": 0.2857, - "output": 0.2857 - }, - { - "model": "ERNIE-3.5-8K", - "type": "tokens", - "channel_type": 15, - "input": 0.8572, - "output": 0.8572 - }, - { - "model": "ERNIE-4.0", - "type": "tokens", - "channel_type": 15, - "input": 8.572, - "output": 8.572 - }, - { - "model": "ERNIE-Bot", - "type": "tokens", - "channel_type": 15, - "input": 0.8572, - "output": 0.8572 - }, - { - "model": "ERNIE-Bot-4", - "type": "tokens", - "channel_type": 15, - "input": 8.572, - "output": 8.572 - }, - { - "model": "ERNIE-Bot-turbo", - "type": "tokens", - "channel_type": 15, - "input": 0, - "output": 0 - }, - { - "model": "ERNIE-Speed", - "type": "tokens", - "channel_type": 15, - "input": 0, - "output": 0 - }, - { - "model": "ERNIE-Speed-128K", - "type": "tokens", - "channel_type": 15, - "input": 0, - "output": 0 - }, - { - "model": "ERNIE-Lite-8K", - "type": "tokens", - "channel_type": 15, - "input": 0, - "output": 0 - }, - { - "model": "ERNIE-Tiny-8K", - "type": "tokens", - "channel_type": 15, - "input": 0, - "output": 0 - }, - { - "model": "ERNIE-Functions-8K", - "type": "tokens", - "channel_type": 15, - "input": 0.2857, - "output": 0.5714 - }, - { - "model": "Embedding-V1", - "type": "tokens", - "channel_type": 15, - "input": 0.1429, - "output": 0.1429 - }, - { - "model": "cogview-3", - "type": "tokens", - "channel_type": 16, - "input": 17.8571, - "output": 17.8571 - }, - { - "model": "embedding-2", - "type": "tokens", - "channel_type": 16, - "input": 0.0357, - "output": 0.0357 - }, - { - "model": "glm-3-turbo", - "type": "tokens", - "channel_type": 16, - "input": 0.0714, - "output": 0.0714 - }, - { - "model": "glm-4", - "type": "tokens", - "channel_type": 16, - "input": 7.143, - "output": 7.143 - }, - { - "model": "glm-4v", - "type": "tokens", - "channel_type": 16, - "input": 7.143, - "output": 7.143 - }, - { - "model": "glm-4-plus", - "type": "tokens", - "channel_type": 16, - "input": 3.5714, - "output": 3.5714 - }, - { - "model": "glm-4-0520", - "type": "tokens", - "channel_type": 16, - "input": 7.1429, - "output": 7.1429 - }, - { - "model": "glm-4-air", - "type": "tokens", - "channel_type": 16, - "input": 0.0714, - "output": 0.0714 - }, - { - "model": "glm-4-airx", - "type": "tokens", - "channel_type": 16, - "input": 0.7143, - "output": 0.7143 - }, - { - "model": "glm-4-long", - "type": "tokens", - "channel_type": 16, - "input": 0.0714, - "output": 0.0714 - }, - { - "model": "glm-4-flash", - "type": "tokens", - "channel_type": 16, - "input": 0, - "output": 0 - }, - { - "model": "glm-4-alltools", - "type": "tokens", - "channel_type": 16, - "input": 7.1429, - "output": 7.1429 - }, - { - "model": "qwen-max", - "type": "tokens", - "channel_type": 17, - "input": 1.4286, - "output": 4.2857 - }, - { - "model": "qwen-max-longcontext", - "type": "tokens", - "channel_type": 17, - "input": 2.8571, - "output": 8.5714 - }, - { - "model": "qwen-plus", - "type": "tokens", - "channel_type": 17, - "input": 0.0571, - "output": 0.1429 - }, - { - "model": "qwen-turbo", - "type": "tokens", - "channel_type": 17, - "input": 0.0214, - "output": 0.0429 - }, - { - "model": "qwen-long", - "type": "tokens", - "channel_type": 17, - "input": 0.0357, - "output": 0.1429 - }, - { - "model": "qwen-vl-max", - "type": "tokens", - "channel_type": 17, - "input": 1.4286, - "output": 1.4286 - }, - { - "model": "qwen-vl-plus", - "type": "tokens", - "channel_type": 17, - "input": 0.5715, - "output": 0.5715 - }, - { - "model": "text-embedding-v1", - "type": "tokens", - "channel_type": 17, - "input": 0.05, - "output": 0.05 - }, - { - "model": "SparkDesk-v1.1", - "type": "tokens", - "channel_type": 18, - "input": 0, - "output": 0 - }, - { - "model": "SparkDesk-v3.1", - "type": "tokens", - "channel_type": 18, - "input": 2.1429, - "output": 2.1429 - }, - { - "model": "SparkDesk-v3.5", - "type": "tokens", - "channel_type": 18, - "input": 2.1429, - "output": 2.1429 - }, - { - "model": "SparkDesk-v4.0", - "type": "tokens", - "channel_type": 18, - "input": 7.1429, - "output": 7.1429 - }, - { - "model": "360GPT_S2_V9", - "type": "tokens", - "channel_type": 19, - "input": 0.8572, - "output": 0.8572 - }, - { - "model": "embedding-bert-512-v1", - "type": "tokens", - "channel_type": 19, - "input": 0.0715, - "output": 0.0715 - }, - { - "model": "embedding_s1_v1", - "type": "tokens", - "channel_type": 19, - "input": 0.0715, - "output": 0.0715 - }, - { - "model": "semantic_similarity_s1_v1", - "type": "tokens", - "channel_type": 19, - "input": 0.0715, - "output": 0.0715 - }, - { - "model": "ChatPro", - "type": "tokens", - "channel_type": 23, - "input": 7.143, - "output": 7.143 - }, - { - "model": "ChatStd", - "type": "tokens", - "channel_type": 23, - "input": 0.7143, - "output": 0.7143 - }, - { - "model": "hunyuan", - "type": "tokens", - "channel_type": 23, - "input": 7.143, - "output": 7.143 - }, - { - "model": "gemini-1.0-pro", - "type": "tokens", - "channel_type": 25, - "input": 0.25, - "output": 0.75 - }, - { - "model": "gemini-1.0-pro-001", - "type": "tokens", - "channel_type": 25, - "input": 0.25, - "output": 0.75 - }, - { - "model": "gemini-1.0-pro-latest", - "type": "tokens", - "channel_type": 25, - "input": 0.25, - "output": 0.75 - }, - { - "model": "gemini-1.0-ultra-latest", - "type": "tokens", - "channel_type": 25, - "input": 1, - "output": 1 - }, - { - "model": "gemini-1.5-pro", - "type": "tokens", - "channel_type": 25, - "input": 1.75, - "output": 5.25 - }, - { - "model": "gemini-1.5-pro-latest", - "type": "tokens", - "channel_type": 25, - "input": 1.75, - "output": 5.25 - }, - { - "model": "gemini-1.5-pro-001", - "type": "tokens", - "channel_type": 25, - "input": 1.75, - "output": 5.25 - }, - { - "model": "gemini-1.5-pro-exp-0827", - "type": "tokens", - "channel_type": 25, - "input": 1.75, - "output": 5.25 - }, - { - "model": "gemini-1.5-pro-exp-0801", - "type": "tokens", - "channel_type": 25, - "input": 1.75, - "output": 5.25 - }, - { - "model": "gemini-1.5-flash-latest", - "type": "tokens", - "channel_type": 25, - "input": 0.0375, - "output": 0.15 - }, - { - "model": "gemini-1.5-flash", - "type": "tokens", - "channel_type": 25, - "input": 0.0375, - "output": 0.15 - }, - { - "model": "gemini-1.5-flash-001", - "type": "tokens", - "channel_type": 25, - "input": 0.0375, - "output": 0.15 - }, - { - "model": "gemini-1.5-flash-exp-0827", - "type": "tokens", - "channel_type": 25, - "input": 0.0375, - "output": 0.15 - }, - { - "model": "gemini-1.5-flash-8b-exp-0827", - "type": "tokens", - "channel_type": 25, - "input": 0.0375, - "output": 0.15 - }, - { - "model": "gemini-ultra", - "type": "tokens", - "channel_type": 25, - "input": 1, - "output": 1 - }, - { - "model": "gemini-pro", - "type": "tokens", - "channel_type": 25, - "input": 0.25, - "output": 0.75 - }, - { - "model": "gemini-pro-vision", - "type": "tokens", - "channel_type": 25, - "input": 0.25, - "output": 0.75 - }, - { - "model": "gemini-1.0-pro-vision-latest", - "type": "tokens", - "channel_type": 25, - "input": 0.25, - "output": 0.75 - }, - { - "model": "Baichuan-Text-Embedding", - "type": "tokens", - "channel_type": 26, - "input": 0.0357, - "output": 0.0357 - }, - { - "model": "Baichuan2-53B", - "type": "tokens", - "channel_type": 26, - "input": 1.4286, - "output": 1.4286 - }, - { - "model": "Baichuan2-Turbo", - "type": "tokens", - "channel_type": 26, - "input": 0.5715, - "output": 0.5715 - }, - { - "model": "Baichuan2-Turbo-192k", - "type": "tokens", - "channel_type": 26, - "input": 1.143, - "output": 1.143 - }, - { - "model": "abab5.5-chat", - "type": "tokens", - "channel_type": 27, - "input": 1.0714, - "output": 1.0714 - }, - { - "model": "abab5.5s-chat", - "type": "tokens", - "channel_type": 27, - "input": 0.3572, - "output": 0.3572 - }, - { - "model": "abab6-chat", - "type": "tokens", - "channel_type": 27, - "input": 7.1429, - "output": 7.1429 - }, - { - "model": "abab6.5-chat", - "type": "tokens", - "channel_type": 27, - "input": 2.1429, - "output": 2.1429 - }, - { - "model": "abab6.5s-chat", - "type": "tokens", - "channel_type": 27, - "input": 0.7143, - "output": 0.7143 - }, - { - "model": "embo-01", - "type": "tokens", - "channel_type": 27, - "input": 0.0357, - "output": 0.0357 - }, - { - "model": "deepseek-chat", - "type": "tokens", - "channel_type": 28, - "input": 0.0714, - "output": 0.1429 - }, - { - "model": "deepseek-coder", - "type": "tokens", - "channel_type": 28, - "input": 0.0714, - "output": 0.1429 - }, - { - "model": "moonshot-v1-128k", - "type": "tokens", - "channel_type": 29, - "input": 4.2857, - "output": 4.2857 - }, - { - "model": "moonshot-v1-32k", - "type": "tokens", - "channel_type": 29, - "input": 1.7143, - "output": 1.7143 - }, - { - "model": "moonshot-v1-8k", - "type": "tokens", - "channel_type": 29, - "input": 0.8572, - "output": 0.8572 - }, - { - "model": "mistral-embed", - "type": "tokens", - "channel_type": 30, - "input": 0.05, - "output": 0.05 - }, - { - "model": "mistral-large-latest", - "type": "tokens", - "channel_type": 30, - "input": 4, - "output": 12 - }, - { - "model": "mistral-medium-latest", - "type": "tokens", - "channel_type": 30, - "input": 1.35, - "output": 4.05 - }, - { - "model": "mistral-small-latest", - "type": "tokens", - "channel_type": 30, - "input": 1, - "output": 3 - }, - { - "model": "open-mistral-7b", - "type": "tokens", - "channel_type": 30, - "input": 0.125, - "output": 0.125 - }, - { - "model": "open-mixtral-8x7b", - "type": "tokens", - "channel_type": 30, - "input": 0.35, - "output": 0.35 - }, - { - "model": "open-mixtral-8x22b", - "type": "tokens", - "channel_type": 30, - "input": 4, - "output": 12 - }, - { - "model": "gemma-7b-it", - "type": "times", - "channel_type": 31, - "input": 0, - "output": 0 - }, - { - "model": "llama2-70b-4096", - "type": "times", - "channel_type": 31, - "input": 0, - "output": 0 - }, - { - "model": "mixtral-8x7b-32768", - "type": "times", - "channel_type": 31, - "input": 0, - "output": 0 - }, - { - "model": "llama3-8b-8192", - "type": "times", - "channel_type": 31, - "input": 0, - "output": 0 - }, - { - "model": "llama3-70b-8192", - "type": "times", - "channel_type": 31, - "input": 0, - "output": 0 - }, - { - "model": "yi-34b-chat-0205", - "type": "tokens", - "channel_type": 33, - "input": 0.1786, - "output": 0.1786 - }, - { - "model": "yi-34b-chat-200k", - "type": "tokens", - "channel_type": 33, - "input": 0.8571, - "output": 0.8571 - }, - { - "model": "yi-vl-plus", - "type": "tokens", - "channel_type": 33, - "input": 0.4286, - "output": 0.4286 - }, - { - "model": "yi-lightning", - "type": "tokens", - "channel_type": 33, - "input": 0.0707, - "output": 0.0707 - }, - { - "model": "yi-large", - "type": "tokens", - "channel_type": 33, - "input": 1.4286, - "output": 1.4286 - }, - { - "model": "yi-medium", - "type": "tokens", - "channel_type": 33, - "input": 0.1786, - "output": 0.1786 - }, - { - "model": "yi-vision", - "type": "tokens", - "channel_type": 33, - "input": 0.4286, - "output": 0.4286 - }, - { - "model": "yi-medium-200k", - "type": "tokens", - "channel_type": 33, - "input": 0.8571, - "output": 0.8571 - }, - { - "model": "yi-spark", - "type": "tokens", - "channel_type": 33, - "input": 0.0714, - "output": 0.0714 - }, - { - "model": "yi-large-rag", - "type": "tokens", - "channel_type": 33, - "input": 1.7857, - "output": 1.7857 - }, - { - "model": "yi-large-fc", - "type": "tokens", - "channel_type": 33, - "input": 1.4286, - "output": 1.4286 - }, - { - "model": "yi-large-turbo", - "type": "tokens", - "channel_type": 33, - "input": 0.8571, - "output": 0.8571 - }, - { - "model": "mj_blend", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "mj_custom_zoom", - "type": "times", - "channel_type": 34, - "input": 0, - "output": 0 - }, - { - "model": "mj_describe", - "type": "times", - "channel_type": 34, - "input": 25, - "output": 25 - }, - { - "model": "mj_high_variation", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "mj_imagine", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "mj_inpaint", - "type": "times", - "channel_type": 34, - "input": 0, - "output": 0 - }, - { - "model": "mj_low_variation", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "mj_modal", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "mj_pan", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "mj_reroll", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "mj_shorten", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "mj_upscale", - "type": "times", - "channel_type": 34, - "input": 25, - "output": 25 - }, - { - "model": "mj_variation", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "mj_zoom", - "type": "times", - "channel_type": 34, - "input": 50, - "output": 50 - }, - { - "model": "swap_face", - "type": "times", - "channel_type": 34, - "input": 25, - "output": 25 - }, - { - "model": "@cf/bytedance/stable-diffusion-xl-lightning", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "@cf/lykon/dreamshaper-8-lcm", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "@cf/openai/whisper", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "@cf/qwen/qwen1.5-14b-chat-awq", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "@cf/qwen/qwen1.5-7b-chat-awq", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "@cf/stabilityai/stable-diffusion-xl-base-1.0", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "@hf/google/gemma-7b-it", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "@hf/thebloke/deepseek-coder-6.7b-base-awq", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "@hf/thebloke/llama-2-13b-chat-awq", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "@cf/meta/llama-3-8b-instruct", - "type": "tokens", - "channel_type": 35, - "input": 0, - "output": 0 - }, - { - "model": "command-r", - "type": "tokens", - "channel_type": 36, - "input": 0.25, - "output": 0.75 - }, - { - "model": "command-r-plus", - "type": "tokens", - "channel_type": 36, - "input": 1.5, - "output": 7.5 - }, - { - "model": "sd3", - "type": "tokens", - "channel_type": 37, - "input": 32.5, - "output": 32.5 - }, - { - "model": "sd3-turbo", - "type": "tokens", - "channel_type": 37, - "input": 20, - "output": 20 - }, - { - "model": "stable-image-core", - "type": "tokens", - "channel_type": 37, - "input": 15, - "output": 15 - }, - { - "model": "hunyuan-lite", - "type": "tokens", - "channel_type": 40, - "input": 0, - "output": 0 - }, - { - "model": "hunyuan-standard", - "type": "tokens", - "channel_type": 40, - "input": 0.3214, - "output": 0.3571 - }, - { - "model": "hunyuan-standard-256k", - "type": "tokens", - "channel_type": 40, - "input": 1.0714, - "output": 4.2857 - }, - { - "model": "hunyuan-pro", - "type": "tokens", - "channel_type": 37, - "input": 2.1429, - "output": 7.1429 - }, - { - "model": "coze-*", - "type": "times", - "channel_type": 38, - "input": 0, - "output": 0 - } -] \ No newline at end of file +[{"model":"dall-e-2","type":"tokens","channel_type":1,"input":8,"output":8},{"model":"babbage-002","type":"tokens","channel_type":1,"input":0.2,"output":0.2},{"model":"babbage-002","type":"tokens","channel_type":1,"input":0.2,"output":0.2},{"model":"tts-1-hd-1106","type":"tokens","channel_type":1,"input":15,"output":15},{"model":"gpt-3.5-turbo-instruct-0914","type":"tokens","channel_type":1,"input":0.75,"output":1},{"model":"mj_reroll","type":"times","channel_type":34,"input":50,"output":50},{"model":"Baichuan2-Turbo-192k","type":"tokens","channel_type":26,"input":1.143,"output":1.143},{"model":"Baichuan-Text-Embedding","type":"tokens","channel_type":26,"input":0.0355,"output":0.0355},{"model":"gemma-7b-it","type":"times","channel_type":31,"input":0,"output":0},{"model":"gpt-4-1106-vision-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"ChatStd","type":"tokens","channel_type":23,"input":0.7145,"output":0.7145},{"model":"@hf/thebloke/llama-2-13b-chat-awq","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"glm-4-plus","type":"tokens","channel_type":16,"input":3.5715,"output":3.5715},{"model":"dall-e-3","type":"tokens","channel_type":1,"input":20,"output":20},{"model":"gpt-3.5-turbo-0125","type":"tokens","channel_type":1,"input":0.25,"output":0.75},{"model":"claude-3-haiku-20240307","type":"tokens","channel_type":14,"input":0.125,"output":0.625},{"model":"claude-2.1","type":"tokens","channel_type":14,"input":4,"output":12},{"model":"o1-mini-2024-09-12","type":"tokens","channel_type":1,"input":1.5,"output":6},{"model":"text-embedding-ada-002","type":"tokens","channel_type":1,"input":0.05,"output":0.05},{"model":"coze-*","type":"times","channel_type":38,"input":0,"output":0},{"model":"gemini-1.5-pro-exp-0801","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"stable-image-core","type":"tokens","channel_type":37,"input":15,"output":15},{"model":"moonshot-v1-auto","type":"tokens","channel_type":29,"input":4.2855,"output":4.2855},{"model":"gpt-4o-mini","type":"tokens","channel_type":1,"input":0.075,"output":0.3},{"model":"moonshot-v1-32k","type":"tokens","channel_type":29,"input":1.7145,"output":1.7145},{"model":"text-moderation-latest","type":"tokens","channel_type":1,"input":0.1,"output":0.1},{"model":"yi-large-rag","type":"tokens","channel_type":33,"input":1.7855,"output":1.7855},{"model":"gpt-4o-2024-08-06","type":"tokens","channel_type":1,"input":1.25,"output":5},{"model":"tts-1","type":"tokens","channel_type":1,"input":7.5,"output":7.5},{"model":"embedding-2","type":"tokens","channel_type":16,"input":0.0355,"output":0.0355},{"model":"ERNIE-3.5-8K","type":"tokens","channel_type":15,"input":0.857,"output":0.857},{"model":"abab5.5-chat","type":"tokens","channel_type":27,"input":1.0715,"output":1.0715},{"model":"embo-01","type":"tokens","channel_type":27,"input":0.0355,"output":0.0355},{"model":"sd3","type":"tokens","channel_type":37,"input":32.5,"output":32.5},{"model":"qwen-max-longcontext","type":"tokens","channel_type":17,"input":2.857,"output":8.5715},{"model":"glm-4-air","type":"tokens","channel_type":16,"input":0.0715,"output":0.0715},{"model":"gpt-3.5-turbo-0613","type":"tokens","channel_type":1,"input":0.75,"output":1},{"model":"gpt-4-0314","type":"tokens","channel_type":1,"input":15,"output":30},{"model":"gemini-1.5-pro-001","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"abab6-chat","type":"tokens","channel_type":27,"input":7.143,"output":7.143},{"model":"@cf/meta/llama-3-8b-instruct","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"gpt-4-0613","type":"tokens","channel_type":1,"input":15,"output":30},{"model":"@hf/meta-llama/meta-llama-3-8b-instruct","type":"times","channel_type":35,"input":0.5,"output":0.5},{"model":"mj_describe","type":"times","channel_type":34,"input":25,"output":25},{"model":"llama-guard-3-8b","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gpt-4-turbo","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"gpt-4-32k","type":"tokens","channel_type":1,"input":30,"output":60},{"model":"gpt-4o-audio-preview-2024-10-01","type":"tokens","channel_type":1,"input":1.25,"output":5},{"model":"llama3-groq-8b-8192-tool-use-preview","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gpt-3.5-turbo-1106","type":"tokens","channel_type":1,"input":0.5,"output":1},{"model":"llava-v1.5-7b-4096-preview","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gemini-1.0-pro-vision-latest","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"@cf/bytedance/stable-diffusion-xl-lightning","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"gpt-3.5-turbo-16k","type":"tokens","channel_type":1,"input":1.5,"output":2},{"model":"mistral-medium-latest","type":"tokens","channel_type":30,"input":1.35,"output":4.05},{"model":"@cf/stabilityai/stable-diffusion-xl-base-1.0","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"whisper-1","type":"tokens","channel_type":1,"input":15,"output":15},{"model":"mj_blend","type":"times","channel_type":34,"input":50,"output":50},{"model":"gpt-4-32k-0314","type":"tokens","channel_type":1,"input":30,"output":60},{"model":"text-embedding-3-large","type":"tokens","channel_type":1,"input":0.065,"output":0.065},{"model":"gemini-ultra","type":"tokens","channel_type":25,"input":1,"output":1},{"model":"qwen-vl-max","type":"tokens","channel_type":17,"input":1.4285,"output":1.4285},{"model":"claude-3-5-sonnet-latest","type":"tokens","channel_type":14,"input":1.5,"output":7.5},{"model":"mj_variation","type":"times","channel_type":34,"input":50,"output":50},{"model":"open-mixtral-8x7b","type":"tokens","channel_type":30,"input":0.35,"output":0.35},{"model":"abab6.5s-chat","type":"tokens","channel_type":27,"input":0.7145,"output":0.7145},{"model":"mj_inpaint","type":"times","channel_type":34,"input":0,"output":0},{"model":"ERNIE-Tiny-8K","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"gemini-1.0-pro","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"gemini-1.5-pro","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"semantic_similarity_s1_v1","type":"tokens","channel_type":19,"input":0.0715,"output":0.0715},{"model":"Baichuan2-Turbo","type":"tokens","channel_type":26,"input":0.5715,"output":0.5715},{"model":"llama3-8b-8192","type":"times","channel_type":31,"input":0,"output":0},{"model":"o1-preview-2024-09-12","type":"tokens","channel_type":1,"input":7.5,"output":30},{"model":"glm-4v","type":"tokens","channel_type":16,"input":7.143,"output":7.143},{"model":"o1-mini","type":"tokens","channel_type":1,"input":1.5,"output":6},{"model":"hunyuan-standard","type":"tokens","channel_type":40,"input":0.3215,"output":0.357},{"model":"yi-vision","type":"tokens","channel_type":33,"input":0.4285,"output":0.4285},{"model":"gemini-pro","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"embedding-bert-512-v1","type":"tokens","channel_type":19,"input":0.0715,"output":0.0715},{"model":"mj_zoom","type":"times","channel_type":34,"input":50,"output":50},{"model":"@cf/meta/llama-3.1-8b-instruct","type":"times","channel_type":35,"input":0.5,"output":0.5},{"model":"@hf/google/gemma-7b-it","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"mixtral-8x7b-32768","type":"times","channel_type":31,"input":0,"output":0},{"model":"glm-4","type":"tokens","channel_type":16,"input":7.143,"output":7.143},{"model":"gpt-4o-realtime-preview","type":"tokens","channel_type":1,"input":2.5,"output":10},{"model":"claude-3-5-sonnet-20241022","type":"tokens","channel_type":14,"input":1.5,"output":7.5},{"model":"claude-3-5-haiku-20241022","type":"tokens","channel_type":14,"input":0.5,"output":2.5},{"model":"abab5.5s-chat","type":"tokens","channel_type":27,"input":0.357,"output":0.357},{"model":"mj_pan","type":"times","channel_type":34,"input":50,"output":50},{"model":"gpt-4o-2024-11-20","type":"tokens","channel_type":1,"input":2.5,"output":7.5},{"model":"qwen-plus","type":"tokens","channel_type":17,"input":0.057,"output":0.143},{"model":"SparkDesk-v3.1","type":"tokens","channel_type":18,"input":2.143,"output":2.143},{"model":"gpt-3.5-turbo-16k-0613","type":"tokens","channel_type":1,"input":1.5,"output":2},{"model":"SparkDesk-v4.0","type":"tokens","channel_type":18,"input":7.143,"output":7.143},{"model":"tts-1-1106","type":"tokens","channel_type":1,"input":7.5,"output":7.5},{"model":"chatgpt-4o-latest","type":"tokens","channel_type":1,"input":2.5,"output":7.5},{"model":"tts-1-hd","type":"tokens","channel_type":1,"input":15,"output":15},{"model":"qwen-turbo","type":"tokens","channel_type":17,"input":0.0215,"output":0.043},{"model":"ChatPro","type":"tokens","channel_type":23,"input":7.143,"output":7.143},{"model":"claude-3-5-sonnet-20240620","type":"tokens","channel_type":14,"input":1.5,"output":7.5},{"model":"text-embedding-v1","type":"tokens","channel_type":17,"input":0.05,"output":0.05},{"model":"Baichuan2-53B","type":"tokens","channel_type":26,"input":1.4285,"output":1.4285},{"model":"dall-e-2","type":"tokens","channel_type":1,"input":8,"output":8},{"model":"abab6.5-chat","type":"tokens","channel_type":27,"input":2.143,"output":2.143},{"model":"gemini-1.5-pro-latest","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"ERNIE-4.0","type":"tokens","channel_type":15,"input":8.572,"output":8.572},{"model":"@cf/openai/whisper","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"open-mixtral-8x22b","type":"tokens","channel_type":30,"input":4,"output":12},{"model":"ERNIE-Speed-128K","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"gpt-4o-realtime-preview-2024-10-01","type":"tokens","channel_type":1,"input":2.5,"output":10},{"model":"gpt-3.5-turbo-instruct","type":"tokens","channel_type":1,"input":0.75,"output":1},{"model":"yi-medium-200k","type":"tokens","channel_type":33,"input":0.857,"output":0.857},{"model":"gemini-pro-vision","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"yi-large-turbo","type":"tokens","channel_type":33,"input":0.857,"output":0.857},{"model":"qwen-max","type":"tokens","channel_type":17,"input":1.4285,"output":4.2855},{"model":"@cf/qwen/qwen1.5-14b-chat-awq","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"o1-preview","type":"tokens","channel_type":1,"input":7.5,"output":30},{"model":"yi-medium","type":"tokens","channel_type":33,"input":0.1785,"output":0.1785},{"model":"command-r","type":"tokens","channel_type":36,"input":0.25,"output":0.75},{"model":"gpt-4-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"gemini-1.0-pro-latest","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"hunyuan","type":"tokens","channel_type":23,"input":7.143,"output":7.143},{"model":"ERNIE-Bot-4","type":"tokens","channel_type":15,"input":8.572,"output":8.572},{"model":"Embedding-V1","type":"tokens","channel_type":15,"input":0.143,"output":0.143},{"model":"SparkDesk-v3.5","type":"tokens","channel_type":18,"input":2.143,"output":2.143},{"model":"claude-2.0","type":"tokens","channel_type":14,"input":4,"output":12},{"model":"PaLM-2","type":"tokens","channel_type":11,"input":1,"output":1},{"model":"embedding_s1_v1","type":"tokens","channel_type":19,"input":0.0715,"output":0.0715},{"model":"gemma2-9b-it","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gemini-1.5-flash-8b-exp-0827","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"gpt-4o-mini-2024-07-18","type":"tokens","channel_type":1,"input":0.075,"output":0.3},{"model":"BLOOMZ-7B","type":"tokens","channel_type":15,"input":0.2855,"output":0.2855},{"model":"moonshot-v1-128k","type":"tokens","channel_type":29,"input":4.2855,"output":4.2855},{"model":"gpt-4o-audio-preview","type":"tokens","channel_type":1,"input":1.25,"output":5},{"model":"gpt-4-vision-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"gpt-4-turbo-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"mistral-embed","type":"tokens","channel_type":30,"input":0.05,"output":0.05},{"model":"yi-spark","type":"tokens","channel_type":33,"input":0.0715,"output":0.0715},{"model":"mistral-small-latest","type":"tokens","channel_type":30,"input":1,"output":3},{"model":"ERNIE-Functions-8K","type":"tokens","channel_type":15,"input":0.2855,"output":0.5715},{"model":"cogview-3","type":"tokens","channel_type":16,"input":17.857,"output":17.857},{"model":"gpt-3.5-turbo","type":"tokens","channel_type":1,"input":0.25,"output":0.75},{"model":"glm-4-long","type":"tokens","channel_type":16,"input":0.0715,"output":0.0715},{"model":"text-moderation-stable","type":"tokens","channel_type":1,"input":0.1,"output":0.1},{"model":"@cf/lykon/dreamshaper-8-lcm","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"deepseek-coder","type":"tokens","channel_type":28,"input":0.0715,"output":0.143},{"model":"yi-large-fc","type":"tokens","channel_type":33,"input":1.4285,"output":1.4285},{"model":"claude-3-opus-20240229","type":"tokens","channel_type":14,"input":7.5,"output":37.5},{"model":"glm-4-0520","type":"tokens","channel_type":16,"input":7.143,"output":7.143},{"model":"SparkDesk-v1.1","type":"tokens","channel_type":18,"input":0,"output":0},{"model":"mj_custom_zoom","type":"times","channel_type":34,"input":0,"output":0},{"model":"llama-3.1-8b-instant","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"llama2-70b-4096","type":"times","channel_type":31,"input":0,"output":0},{"model":"gpt-4","type":"tokens","channel_type":1,"input":15,"output":30},{"model":"yi-large","type":"tokens","channel_type":33,"input":1.4285,"output":1.4285},{"model":"glm-4-alltools","type":"tokens","channel_type":16,"input":7.143,"output":7.143},{"model":"davinci-002","type":"tokens","channel_type":1,"input":1,"output":1},{"model":"ERNIE-Speed","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"gpt-4-1106-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"gemini-1.0-pro-001","type":"tokens","channel_type":25,"input":0.25,"output":0.75},{"model":"whisper-large-v3","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"hunyuan-standard-256k","type":"tokens","channel_type":40,"input":1.0715,"output":4.2855},{"model":"mj_low_variation","type":"times","channel_type":34,"input":50,"output":50},{"model":"gpt-3.5-turbo-0301","type":"tokens","channel_type":1,"input":0.75,"output":1},{"model":"mj_upscale","type":"times","channel_type":34,"input":25,"output":25},{"model":"mj_shorten","type":"times","channel_type":34,"input":50,"output":50},{"model":"deepseek-chat","type":"tokens","channel_type":28,"input":0.0715,"output":0.143},{"model":"gpt-4-0125-preview","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"yi-34b-chat-0205","type":"tokens","channel_type":33,"input":0.1785,"output":0.1785},{"model":"hunyuan-pro","type":"tokens","channel_type":37,"input":2.143,"output":7.143},{"model":"hunyuan-lite","type":"tokens","channel_type":40,"input":0,"output":0},{"model":"claude-instant-1.2","type":"tokens","channel_type":14,"input":0.4,"output":1.2},{"model":"mj_high_variation","type":"times","channel_type":34,"input":50,"output":50},{"model":"ERNIE-Lite-8K","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"text-embedding-3-small","type":"tokens","channel_type":1,"input":0.01,"output":0.01},{"model":"open-mistral-7b","type":"tokens","channel_type":30,"input":0.125,"output":0.125},{"model":"swap_face","type":"times","channel_type":34,"input":25,"output":25},{"model":"llama-3.1-70b-versatile","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"gpt-4-turbo-2024-04-09","type":"tokens","channel_type":1,"input":5,"output":15},{"model":"@hf/thebloke/deepseek-coder-6.7b-base-awq","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"glm-4-airx","type":"tokens","channel_type":16,"input":0.7145,"output":0.7145},{"model":"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b","type":"tokens","channel_type":35,"input":0.25,"output":2.44},{"model":"gemini-1.5-flash-latest","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"ERNIE-Bot-turbo","type":"tokens","channel_type":15,"input":0,"output":0},{"model":"glm-4-flash","type":"tokens","channel_type":16,"input":0,"output":0},{"model":"command-r-plus","type":"tokens","channel_type":36,"input":1.5,"output":7.5},{"model":"gemini-1.0-ultra-latest","type":"tokens","channel_type":25,"input":1,"output":1},{"model":"gemini-1.5-pro-exp-0827","type":"tokens","channel_type":25,"input":1.75,"output":5.25},{"model":"moonshot-v1-8k","type":"tokens","channel_type":29,"input":0.857,"output":0.857},{"model":"gpt-4o-2024-05-13","type":"tokens","channel_type":1,"input":2.5,"output":7.5},{"model":"llama3-70b-8192","type":"times","channel_type":31,"input":0,"output":0},{"model":"qwen-long","type":"tokens","channel_type":17,"input":0.0355,"output":0.143},{"model":"gpt-4-32k-0613","type":"tokens","channel_type":1,"input":30,"output":60},{"model":"360GPT_S2_V9","type":"tokens","channel_type":19,"input":0.857,"output":0.857},{"model":"sd3-turbo","type":"tokens","channel_type":37,"input":20,"output":20},{"model":"gpt-4o","type":"tokens","channel_type":1,"input":2.5,"output":7.5},{"model":"claude-3-5-haiku-latest","type":"tokens","channel_type":14,"input":0.5,"output":2.5},{"model":"claude-3-sonnet-20240229","type":"tokens","channel_type":14,"input":1.5,"output":7.5},{"model":"@cf/qwen/qwen1.5-7b-chat-awq","type":"tokens","channel_type":35,"input":0,"output":0},{"model":"distil-whisper-large-v3-en","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"qwen-vl-plus","type":"tokens","channel_type":17,"input":0.5715,"output":0.5715},{"model":"gemini-1.5-flash-001","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"gemini-1.5-flash","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"mj_modal","type":"times","channel_type":34,"input":50,"output":50},{"model":"yi-34b-chat-200k","type":"tokens","channel_type":33,"input":0.857,"output":0.857},{"model":"mj_imagine","type":"times","channel_type":34,"input":50,"output":50},{"model":"ERNIE-Bot","type":"tokens","channel_type":15,"input":0.857,"output":0.857},{"model":"glm-3-turbo","type":"tokens","channel_type":16,"input":0.0715,"output":0.0715},{"model":"yi-vl-plus","type":"tokens","channel_type":33,"input":0.4285,"output":0.4285},{"model":"llama3-groq-70b-8192-tool-use-preview","type":"times","channel_type":31,"input":0.5,"output":0.5},{"model":"yi-lightning","type":"tokens","channel_type":33,"input":0.0705,"output":0.0705},{"model":"mistral-large-latest","type":"tokens","channel_type":30,"input":4,"output":12},{"model":"gemini-1.5-flash-exp-0827","type":"tokens","channel_type":25,"input":0.0375,"output":0.15},{"model":"ERNIE-3.5-8K","type":"tokens","channel_type":15,"input":0.12242857142857143,"output":0.12242857142857143},{"model":"ERNIE-3.5-8K","type":"tokens","channel_type":15,"input":0.05714285714285715,"output":0.12242857142857143},{"model":"ERNIE-3.5-8K","type":"tokens","channel_type":15,"input":0.05714285714285715,"output":0.14285714285714285}] \ No newline at end of file diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 0000000..1078207 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# 启动后端服务 +./main & + +# 启动前端服务 +cd /app/frontend && node server.js & + +# 启动 nginx +nginx -g 'daemon off;' \ No newline at end of file